52

One of my apps has a backgrouod service that uses the START_STICKY return code from onStartCommand to automatically restart when the system kills it. It seems that this is no longer working on Android KitKat. Is there any solution for this ? Should I be doing something different on Kitkat to keep the service running ?

Note: There is a similar discussion on the Android-Devlopers group about swiping the app from the recent apps list behaves. Could this two issues be related ? https://groups.google.com/forum/#!topic/android-developers/H-DSQ4-tiac

Edit: Saw that there are open bugs on Android issue tracker:

https://code.google.com/p/android/issues/detail?id=63793 https://code.google.com/p/android/issues/detail?id=63618

Edit2: The same happens even if service is running using startForeground, in a separate process and with the flag android:stopWithTask="false" in the AndroidManifest.xml file...

Edit3: More related bugs on Android issue tracker:

https://code.google.com/p/android/issues/detail?id=62091 https://code.google.com/p/android/issues/detail?id=53313 https://code.google.com/p/android/issues/detail?id=104308

Is there some sort of workaround to get the previous behavior ?

Muzikant
  • 8,070
  • 5
  • 54
  • 88
  • First of all, does your Service run on the same process of your app? If it does then it's right when your app is killed, the Service is killed too. So try to run your Service in a different process, here is the link for you: http://www.vogella.com/articles/AndroidServices/article.html – user2652394 Dec 17 '13 at 14:24
  • Tried running it on same process and on a different process. same result – Muzikant Dec 17 '13 at 14:50
  • It looks like they copied iOS 7's behaviour. If you swipe away an app, then its process is terminated and it will not longer be allowed to run anything in the background including its services until you manually launch the app again or reboot the device. – Monstieur Feb 07 '14 at 11:20
  • From what [I can tell](http://stackoverflow.com/questions/20636330/start-sticky-does-not-work-on-android-kitkat-edit-and-jelly-bean#comment32857673_20735519), the bug description of AOSP [#63793](https://code.google.com/p/android/issues/detail?id=63793) is correct: Starting with 4.4(.2), `START_STICKY` services will not be restarted, there will be no "`Scheduling restart of crashed service`" log entry. 4.3 is not affected, but this is an AOSP issue, not something mods (e.g. CM) introduced, as I was able to reproduce this with the AOSP emulator. I don't believe that this was an intended change. – Flow Feb 17 '14 at 11:22
  • After weeks of researcheas I found the solution in this answer: http://stackoverflow.com/a/29351792/5247630 – Manza Oct 19 '15 at 19:09

4 Answers4

29

Seems that this is a bug present in Android 4.4, got around it with the following:

@Override
public void onTaskRemoved(Intent rootIntent) {
    Intent restartService = new Intent(getApplicationContext(),
            this.getClass());
    restartService.setPackage(getPackageName());
    PendingIntent restartServicePI = PendingIntent.getService(
            getApplicationContext(), 1, restartService,
            PendingIntent.FLAG_ONE_SHOT);
    AlarmManager alarmService = (AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE);
    alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() +1000, restartServicePI);

}

Found this answer from this post

ElOjcar
  • 301
  • 2
  • 4
  • 12
Rakeeb Rajbhandari
  • 5,043
  • 6
  • 43
  • 74
  • 4
    Thanks for posting MY solution from the link above (Could have at least pointed out the source of it...) https://groups.google.com/forum/#!topic/android-developers/H-DSQ4-tiac As stated on this link, this is not a complete solution and still has its flaws – Muzikant Dec 19 '13 at 12:44
  • 1
    why don't you post it ? I'll delete this post :) – Rakeeb Rajbhandari Dec 19 '13 at 12:46
  • 1
    No need to delete your post. I haven't posted it myself as its not a compete solution. It only handles the case when a user swipes the app from the "Recent apps" list but not when the process is killed by Android. Also, after further testing seems like the `+1000` time buffer is sometimes not sufficient and the process is killed after that one seconds buffer so I changed it to `+10000`. Still, looks like an ugly (and not always working) workaround. Not that I have anything better for now... – Muzikant Dec 19 '13 at 12:50
  • @Muzikant Thanks for this temporary solution. My apps suffers from the same problem, I'm going to use this until we get some news from Android about this strange KitKat behavior. – jujujuijk Jan 01 '14 at 04:40
  • this onTaskRemoved method is not working, when tested it on [micromax unite 2] (http://www.micromaxinfo.com/mobiles/smartphones/canvas/Unite-2-A106) can anybody tell me alternative solution for this?! – AndroidManifester Apr 07 '15 at 07:44
  • when to call `super.onTaskRemoved(rootIntent);`. – Phaneendra Charyulu Kanduri Oct 01 '19 at 05:44
21

The problem here appears to not to occur on AOSP based ROMs. That is, I can easily recreate this on a CyanogenMod 11 based ROM, but on an AOSP ROM (and on an Emulator), START_STICKY behaves exactly as I'd expect. That said, I am seeing reports from folks on Nexus 5's that appear to be seeing this behavior, so perhaps it is still an issue in AOSP.

On an emulator and on an AOSP ROM, I see the following from a logcat when I do a 'kill 5838' against the process (as I'd expect):

12-22 18:40:14.237 D/Zygote  (   52): Process 5838 terminated by signal (15)
12-22 18:40:14.247 I/ActivityManager(  362): Process com.xxxx (pid 5838) has died.
12-22 18:40:14.247 W/ActivityManager(  362): Scheduling restart of crashed service com.xxxx/com.xxxx.NotifyingService in 5000ms
12-22 18:40:19.327 I/ActivityManager(  362): Start proc com.xxxx for service xxxx.pro/com.xxxx.NotifyingService: pid=5877 uid=10054 gids={50054, 3003, 3002, 1028}

I see the same restart behavior if I end the task by 'swiping' from the recent tasks list. So this is all good - it means that the core AOSP code is behaving as it has in previous levels.

I am looking at the Cyanogenmod service code to try and figure out why things aren't getting scheduled for restart - no luck yet. It appears that it should reschedule it. Cyanogenmod uses a service map which AOSP doesn't - but unclear whether that is an issue or not (doubtful) https://github.com/CyanogenMod/android_frameworks_base/blob/cm-11.0/services/java/com/android/server/am/ActiveServices.java#L2092

A rather hackish workaround you can do is to use a similar mechanism as your onTaskRemoved AlarmService to enable an alarm for X minutes later. Then every few minutes while your app is up and running, you can reset the alarm - so it only goes off if things really have been killed and not restarted. This isn't foolproof - using a Handler gives you uptime vs the alarm service which uses realtime, so it's possible for your alarm to trigger even though it was set at a longer time than your 'reset' handler. But if you set an intent extra you can chose to ignore the onStartCommand if your service was already up and running, turning this into a noop.

I'm not a fan of the following hack at all - but it shouldn't do any real harm. If the user does an explicit Force Close, then the alarm manager will destroy any alarms set so that the service won't restart (which is what the user wants).

First, create a helper method that will set an alarm for 20 minutes which will cause onStartCommand to be triggered for your service. Every 2 minutes have a Handler which will reset the 20 minute alarm. If the handler runs within the realtime 20 minutes, the alarm will never go off. The handler isn't guaranteed to run though if the device is asleep (which is good).

private void ensureServiceStaysRunning() {
    // KitKat appears to have (in some cases) forgotten how to honor START_STICKY
    // and if the service is killed, it doesn't restart.  On an emulator & AOSP device, it restarts...
    // on my CM device, it does not - WTF?  So, we'll make sure it gets back
    // up and running in a minimum of 20 minutes.  We reset our timer on a handler every
    // 2 minutes...but since the handler runs on uptime vs. the alarm which is on realtime,
    // it is entirely possible that the alarm doesn't get reset.  So - we make it a noop,
    // but this will still count against the app as a wakelock when it triggers.  Oh well,
    // it should never cause a device wakeup.  We're also at SDK 19 preferred, so the alarm
    // mgr set algorithm is better on memory consumption which is good.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
    {
        // A restart intent - this never changes...        
        final int restartAlarmInterval = 20*60*1000;
        final int resetAlarmTimer = 2*60*1000;
        final Intent restartIntent = new Intent(this, NotifyingService.class);
        restartIntent.putExtra("ALARM_RESTART_SERVICE_DIED", true);
        final AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
        Handler restartServiceHandler = new Handler()
        {
            @Override
            public void handleMessage(Message msg) {
                // Create a pending intent
                PendingIntent pintent = PendingIntent.getService(getApplicationContext(), 0, restartIntent, 0);
                alarmMgr.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + restartAlarmInterval, pintent);
                sendEmptyMessageDelayed(0, resetAlarmTimer);
            }            
        };
        restartServiceHandler.sendEmptyMessageDelayed(0, 0);  
    }
}

In your onCreate you can call this method. Also - in your onStartCommand, be sure to ignore this if your service is already up and running. EG:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    ...
    if ((intent != null) && (intent.getBooleanExtra("ALARM_RESTART_SERVICE_DIED", false)))
    {
        Log.d(TAG, "onStartCommand after ALARM_RESTART_SERVICE_DIED");
        if (IS_RUNNING)
        {
            Log.d(TAG, "Service already running - return immediately...");
            ensureServiceStaysRunning();
            return START_STICKY;
        }
    }
    // Do your other onStartCommand stuff..
    return START_STICKY;
}
George Tanner
  • 211
  • 1
  • 4
  • Thanks for the detailed response. That's what I was thinking as last resort but was hoping to get a more "standard" solution. If no other solution comes up, (sadly) this might be the way to go... – Muzikant Dec 24 '13 at 06:41
  • I was able to reproduce this behavior, i.e. not restarting a sticky service, with an emulator running Android 4.4. While the same service was restarted with `W/ActivityManager(2707): Scheduling restart of crashed service org.foo.bar/.FooService in 5000ms` on an emulator running Android 4.2. Both services where killed with SIGKILL by using `kill `. So it sadly **is** a bug in AOSP. – Flow Feb 12 '14 at 12:24
  • Further info: My service was also restarted on an emulator running Android 4.3, so it's not also a Jelly Bean issue. – Flow Feb 12 '14 at 16:45
  • can anybody please tell me what is restartAlarmInterval & resetAlarmTimer ? suppose if i want to start a service in every hour half what time should i give in those both? (my exact requirement is every one hour i'll check if the service is not running, though this alarm manager i'm starting the service again) – AndroidManifester Apr 08 '15 at 15:00
  • using FLAG_CANCEL_CURRENT when creating pending intent will cancel previous intents and keeps only one active pending intent at a time – abedfar Jan 03 '16 at 13:59
  • I still get the "I/ActivityManager: Force stopping service ServiceRecord..." The Alarm Manager is killed together with the service, so nobody recreates the service – user2924714 Jul 21 '16 at 15:51
17

This is not a 100% working solution but it's the best so far as it almost completely eliminates the problem. So far I integrated this solution along with overriding onTaskRemoved (See this answer) and a keep-alive notification (See this answer). Additional answers are very appreciated !

After further investigation, it seems that the bug already exists in Jelly Bean and looks like there is a solution for that (At least in my case that seems to work. will keep on testing and update the answer if required).

From what I observed this only happens with services that receive broadcasts set by AlarmManager.

To reproduce the bug follow these steps:

  1. Start the app
  2. start the service as a foreground service (use startForeground for that) from within the app
  3. Swipe the app from "Recent Apps" list
  4. Send a broadcast that is handled by the service
  5. The service is killed !

Using adb shell dumpsys >C:\dumpsys.txt you can monitor the state of the service between the different steps. (look for Process LRU list in the dumpsys output) on steps 2 and 3 you will see something like this:

Proc # 2: prcp  F/S/IF trm: 0 11073:<your process name>/u0a102 (fg-service)

Specifically, notice the F/S/IF and the (fg-service) that indicate the service is running as a foreground service (more details on how to analyze the dumpsys at this link: https://stackoverflow.com/a/14293528/624109).

After step 4 you will not see your service in the Process LRU list. Instead, you can look at the device logcat and you will see the following:

I/ActivityManager(449): Killing 11073:<your process name>/u0a102 (adj 0): remove task

What seems to be causing that behavior is the fact that the received broadcast takes the service out of its foreground state and then killed.

To avoid that, you can use this simple solution when creating your PendingIntent for the AlarmManager (Source: https://code.google.com/p/android/issues/detail?id=53313#c7)

AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent("YOUR_ACTION_NAME");
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent, 0);

Pay attention to the following steps:

  1. Call addFlags on the intent and use FLAG_RECEIVER_FOREGROUND
  2. Use a non-zero request code in PendingIntent.getBroadcast

If you leave any of those steps out it will not work.

Note that the FLAG_RECEIVER_FOREGROUND was added on API 16 (Jelly Bean) so it makes sense that this is when the bug first appeared...

Most likely that KitKat is just more aggressive when it comes to killing processes and this is why it was emphasized with KitKat, but looks like this was already relevant on Jelly Bean.

Note 2: Notice the details in the question about the service configuration - running in a separate process, as a foreground service, with endWithTask set to false in the manifest.

Note 3: The same thing happens when the app receives the android.appwidget.action.APPWIDGET_CONFIGURE message and shows a configuration activity for a new widget (Replace step 4 above with creating a new widget). I found that only happens when the widget provider (the receiver that handles android.appwidget.action.APPWIDGET_UPDATE) is set to run on a different process than the activity process. After changing that so both the configuration activity and the widget provider are on the same process, this no longer happens.

Community
  • 1
  • 1
Muzikant
  • 8,070
  • 5
  • 54
  • 88
  • 1
    In my case this didn't help, but I'm not using a foreground service. (http://stackoverflow.com/questions/21073136/kitkat-service-is-null-after-exiting-main-activity) – Angelo Tricarico Jan 20 '14 at 15:18
  • What do you mean? I don't understand! Even if I don't use receivers for alarm manager, swiping app from recent apps will close my service, even sometimes system closes my service, without swiping. @Muzikant – AVEbrahimi Jan 28 '14 at 14:56
  • 1
    [I was able to reproduce the bug](http://stackoverflow.com/questions/20636330/start-sticky-does-not-work-on-android-kitkat-edit-and-jelly-bean#comment32857673_20735519) with a service that *does not* receive a broadcast from AlarmManager. – Flow Feb 12 '14 at 12:27
  • @Muzikant Is it possible to use `Intent.FLAG_RECEIVER_FOREGROUND` for incoming broadcasts? I have this problem with a reciver which get `android.intent.action.PACKAGE_ADDED` from the OS! – Ali Sep 08 '14 at 23:51
  • @NullPointer you can't. the `Intent.FLAG_RECEIVER_FOREGROUND` flag is set on the sender side and not on the receiver side – Muzikant Sep 14 '14 at 08:38
  • @Muzikant what is keep alive notification, I didnot get it from the answer, Can you please explain in brief what are you trying to say. – Shreyans jain Feb 29 '16 at 11:28
  • @Muzikant if the FLAG_RECEIVER_FOREGROUND thing works why you need the other solutions? please throw some light. – Shreyans jain Mar 01 '16 at 08:01
9

i found this simple trick to solve this problem without using AlarmManager.

  1. create a broadcast receiver that listens broadcast everytime onDestroy() method in service is called:

    public class RestartService extends BroadcastReceiver {
    
    private static final String TAG = "RestartService";
    
    public RestartService() {
    }
    
    @Override
    public void onReceive(Context context, Intent intent) {
    Log.e(TAG, "onReceive");
    context.startService(new Intent(context, YourService.class));
    }
    }
    
  2. add customized broadcast intent to your manifest

    <receiver
        android:name=".RestartService"
        android:enabled="true" >
        <intent-filter>
            <action android:name="restartApps" />
        </intent-filter>
    </receiver>
    
  3. then, send broadcast from onDestroy(), probably like this:

    @Override
    public void onDestroy() {
    Intent intent = new Intent("restartApps");
    sendBroadcast(intent);
    super.onDestroy();
    stopThread();
    }
    
  4. call onDestroy() from onTaskRemoved(Intent intent)

this trick will restart your service everytime user close service from both task manager and force close from settings, i hope this will help you too

baskara
  • 1,256
  • 11
  • 7
  • This looks almost too easy compared to all the AlarmManager tricks. I'm giving this one a quick try. – Hless Oct 06 '15 at 11:39
  • 1
    this solution is not working on 4.4.2 since onDestroy() is not being called at all, when I swipe off the app from recent apps list – abedfar Nov 12 '15 at 13:11
  • 1
    @abedfar then all inside `onDestroy()` should be moved to a new method and call it from `onTaskRemoved()` – baskara Nov 24 '15 at 09:09
  • have two device one Moto g4 plus(Api 23) and another Samsung Note 3(Api 22), now this works in Moto but do not in samsung... any idea..?? – Nikhil Borad Aug 24 '16 at 12:06
  • @NikhilBorad i presume it is a ROM issue. i think different roms handle service level things differently. but, it still needs to be investigated more – baskara Sep 06 '16 at 18:07
  • Samsung smart manager kill services for better performance..thats why..but if u find any solution let me know.. – Nikhil Borad Sep 07 '16 at 04:59