49

I have an IntentService that is started from an Activity and I would like to be able to stop the service immediately from the activity with a "cancel" button in the activity. As soon as that "cancel" button is pressed, I want the service to stop executing lines of code.

I've found a number of questions similar to this (i.e. here, here, here, here), but no good answers. Activity.stopService() and Service.stopSelf() execute the Service.onDestroy() method immediately but then let the code in onHandleIntent() finish all the way through before destroying the service.

Since there is apparently no guaranteed way to terminate the service's thread immediately, the only recommended solution I can find (here) is to have a boolean member variable in the service that can be switched in the onDestroy() method, and then have just about every line of the code in onHandleIntent() wrapped in its own "if" clause looking at that variable. That's an awful way to write code.

Does anybody know of a better way to do this in an IntentService?

Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
pvans
  • 1,017
  • 2
  • 10
  • 15
  • check this link [ProperWay to stop intent service][1] [1]: http://stackoverflow.com/questions/10250745/proper-way-to-stop-intentservice – Muhammad Usman Ghani Feb 24 '14 at 11:51
  • no. there's no better way. And that's not an issue with the `IntentService`. But an issue the way threads works. Any thread you have running the only safe way to stop it is by checking a `boolean` (or some condition) and returning from the thread. – Budius Mar 02 '14 at 21:59
  • There is a better way. You cannot kill a thread in Java, but Android allows you to kill processes. See my answer for details. – kupsef May 03 '14 at 18:39

9 Answers9

36

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;
Sadegh
  • 2,669
  • 1
  • 23
  • 26
17

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;
    }
}
Louis CAD
  • 10,965
  • 2
  • 39
  • 58
kupsef
  • 3,357
  • 1
  • 21
  • 31
  • This looks like it might work. It's not easy for me to test this any more, but if anybody else can verify that this solution is correct, I'll accept it. – pvans May 05 '14 at 20:56
  • 7
    replace Process.killProcess(next.pid); with android.os.Process.killProcess(next.pid); java Process is different from Android Process. – M D P Jul 23 '14 at 14:37
  • 2
    Of course it is different, but you don't have to type the fully qualified name with the revelant import --> `import android.os.Process`. Just like with standard Java --> `import java.lang.Process`. – kupsef Jul 23 '14 at 16:50
  • This is working but unfortunately, EventBus can't be used anymore with this approach. Does anyone has a solution for killing the running intentservice? – gbero Mar 10 '15 at 11:51
  • What EventBus has to do with this topic? – kupsef Mar 10 '15 at 18:25
  • 2
    You never shoudn't terminate any concurrent task like this. Or you risk have any updated data corrupted. The best technique is really separate your task by transactional chunks and check cancel variable before go further (as Sirlate code, bellow). If you don't like "ifs" make your conditional code looks better. But IS conditional code, anyway. :) – Renascienza Jul 18 '15 at 18:31
  • This is what the OP wanted to accomplish (to terminate it instantly, not caring about any kind of consequences). In the other hand, it should be all right, if the service is stateless, as I mentioned in my answer also. – kupsef Jul 21 '15 at 06:12
  • this works fine, yet I dont know why dont use it I replace Process.killProcess(next.pid); with android.os.Process.killProcess(next.pid); – angel Mar 10 '16 at 22:24
  • @kupsef how about checking pending tasks passed to the service and decrement them during the process, and after it equals 0, the service excutes this 'android.os.Process.killProcess(android.os.Process.myPid())', usually serviceIntent terminates itsself after there is no longer work to do, but in my case, it implements a handler which connects it to the main thread, and will keep the service working even if you make the handler null or remove call backs, what do you think ? is it still a dirty approach ? – Tamim Attafi Mar 12 '19 at 15:42
  • @TamimProduction What is the reason for implementing a Handler? If you want to manipulate the UI from the Service, there are many suggested ways of doing so. – kupsef Mar 12 '19 at 16:31
  • @kupsef , the first reason is to update the notification's progressBar every second and not instantly, the second reason is to display Toasts – Tamim Attafi Mar 13 '19 at 04:28
7

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.

JoeyCK
  • 1,384
  • 4
  • 19
  • 29
  • I understand what you do but It seems to be not an official way to communicate between activity and intentservice. right? It costs 2 broadcasts in activity and intentservice – Slim_user71169 Aug 08 '16 at 03:44
2
@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.

Nikhil Agrawal
  • 26,128
  • 21
  • 90
  • 126
cycDroid
  • 915
  • 10
  • 16
2

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!

Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
1

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).
rtsai2000
  • 338
  • 1
  • 5
0

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.

tolgap
  • 9,629
  • 10
  • 51
  • 65
-1

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);

RPB
  • 16,006
  • 16
  • 55
  • 79
  • Thanks, but as I noted, stopService() does not cause the service to stop executing lines of code immediately. – pvans Jul 02 '12 at 18:49
  • since IntentService runs on it's own thread, stopService is not sufficient - the IntentService's code will keep running. – Cookienator Sep 23 '16 at 01:24
-1
context.stopService(GPSService);
Szymon
  • 42,577
  • 16
  • 96
  • 114
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post - you can always comment on your own posts, and once you have sufficient [reputation](http://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](http://stackoverflow.com/help/privileges/comment). – Girish Nair Mar 13 '14 at 10:31