Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

START_STICKY does not work on Android KitKat

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 ?

like image 537
Muzikant Avatar asked Dec 17 '13 14:12

Muzikant


2 Answers

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

like image 91
Rakeeb Rajbhandari Avatar answered Sep 17 '22 18:09

Rakeeb Rajbhandari


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; } 
like image 35
George Tanner Avatar answered Sep 17 '22 18:09

George Tanner