How to force an IntentService to stop immediately with a cancel button from an Activity?

时光怂恿深爱的人放手 提交于 2019-11-27 11:33:35
kupsef

Stopping a thread or a process immediately is often a dirty thing. However, it should be fine if your service is stateless.

Declare the service as a separate process in the manifest:

<service
     android:process=":service"
     ...

And when you want to stop its execution, just kill that process:

ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses();

Iterator<RunningAppProcessInfo> iter = runningAppProcesses.iterator();

while(iter.hasNext()){
    RunningAppProcessInfo next = iter.next();

    String pricessName = getPackageName() + ":service";

    if(next.processName.equals(pricessName)){
        Process.killProcess(next.pid);
        break;
    }
}

Here is the trick, make use of a volatile static variable and check continue condition in some of lines in your service that service continue should be checked:

class MyService extends IntentService {
    public static volatile boolean shouldContinue = true;
    public MyService() {
        super("My Service");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        doStuff();
    }

    private void doStuff() {
        // do something 

        // check the condition
        if (shouldContinue == false) {
            stopSelf();
            return;
        }

       // continue doing something

       // check the condition
       if (shouldContinue == false) {
           stopSelf();
           return;
       }

       // put those checks wherever you need
   }
}

and in your activity do this to stop your service,

 MyService.shouldContinue = false;

I've used a BroadcastReceiver inside the service that simply puts a stop boolean to true. Example:

private boolean stop=false;

public class StopReceiver extends BroadcastReceiver {

   public static final String ACTION_STOP = "stop";

   @Override
   public void onReceive(Context context, Intent intent) {
       stop = true;
   }
}


@Override
protected void onHandleIntent(Intent intent) {
    IntentFilter filter = new IntentFilter(StopReceiver.ACTION_STOP);
    filter.addCategory(Intent.CATEGORY_DEFAULT);
    StopReceiver receiver = new StopReceiver();
    registerReceiver(receiver, filter);

    // Do stuff ....

    //In the work you are doing
    if(stop==true){
        unregisterReceiver(receiver);
        stopSelf();
    }
}

Then, from the activity call:

//STOP SERVICE
Intent sIntent = new Intent();
sIntent.setAction(StopReceiver.ACTION_STOP);
sendBroadcast(sIntent);

To stop the service.

PD: I use a boolean because In my case I stop the service while in a loop but you can probably call unregisterReceiver and stopSelf in onReceive.

PD2: Don't forget to call unregisterReceiver if the service finishes it's work normally or you'll get a leaked IntentReceiver error.

Manish DEWAN

In case of IntentService it does not stop or takes any other request through some intent action until its onHandleIntent method completes the previous request.

If we try to start IntentService again with some other action, onHandleIntent will be called only when previous intent / task is finished.

Also stopService(intent); or stopSelf(); does not work until the onHandleIntent() method finishes its task.

So I think here better solution is to use normal Service here.

I hope it will help!

cycDroid
@Override
protected void onHandleIntent(Intent intent) {
    String action = intent.getAction();
    if (action.equals(Action_CANCEL)) {
        stopSelf();
    } else if (action.equals(Action_START)) {
        //handle
    }
}

Hope it works.

If using an IntentService, then I think you are stuck doing something like you describe, where the onHandleIntent() code has to poll for its "stop" signal.

If your background task is potentially long-running, and if you need to be able to stop it, I think you are better off using a plain Service instead. At a high level, write your Service to:

  • Expose a "start" Intent to start an AsyncTask to perform your background work, saving off a reference to that newly-created AsyncTask.
  • Expose a "cancel" Intent to invoke AsyncTask.cancel(true), or have onDestroy() invoke AsyncTask.cancel(true).
  • The Activity can then either send the "cancel" Intent or just call stopService().

In exchange for the ability to cancel the background work, the Service takes on the following responsibilities:

  • The AsyncTask doInBackground() will have to gracefully handle InterruptedException and/or periodically check for Thread.interrupted(), and return "early".
  • The Service will have to ensure that stopSelf() is called (maybe in AsyncTask onPostExecute/onCancelled).

As @budius already mentioned in his comment, you should set a boolean on the Service when you click that button:

// your Activity.java
public boolean onClick() {
   //...
   mService.performTasks = false;
   mService.stopSelf();
}

And in your Intent handling, before you do the important task of committing/sending the intent information, just use that boolean:

// your Service.java
public boolean performTasks = true;

protected void onHandleIntent(Intent intent) {
   Bundle intentInfo = intent.getBundle();
   if (this.performTasks) {
      // Then handle the intent...
   }
}

Otherwise, the Service will do it's task of processing that Intent. That's how it was meant to be used, because I can't quite see how you could solve it otherwise if you look at the core code.

Here is some sample code to start/stop Service

To start,

Intent GPSService = new Intent(context, TrackGPS.class);
context.startService(GPSService);

To stop,

context.stopService(GPSService);

Pooja Singh
context.stopService(GPSService);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!