START_STICKY不适用于Android KitKat

我的一个应用程序有一个背景服务,它使用onStartCommandSTART_STICKY返回代码在系统杀死它时自动重启。 看来,这不再适用于Android KitKat。 有没有解决scheme? 我应该在Kitkat上做些不同的事情来保持服务正常运行吗?

注意:在Android-Devlopers组中有关于从最近的应用程序列表行为中滑动应用程序的类似讨论。 这两个问题可以相关吗? https://groups.google.com/forum/#!topic/android-developers/H-DSQ4-tiac

编辑:看到Android问题跟踪器上有开放的错误:

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

编辑2:即使服务正在运行使用startForeground ,在AndroidManifest.xml文件中单独的进程和与标志android:stopWithTask="false"相同的情况发生…

编辑3:Android问题跟踪器上的更多相关的错误:

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 /安卓/问题/细节?ID = 104308

是否有某种解决方法来获得以前的行为?

似乎这是Android 4.4中存在的一个错误,用下面的代码解决了它:

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

从这个postfind了这个答案

这里的问题似乎不会发生在基于AOSP的ROM上。 也就是说,我可以很容易地在基于CyanogenMod 11的ROM上重新创build这个,但是在AOSP ROM(和仿真器)上,START_STICKY的行为与我所期望的完全相同。 也就是说,我看到Nexus 5上的人们似乎看到了这种行为,所以也许在AOSP中仍然是一个问题。

在模拟器和AOSP ROM上,当我对进程执行“kill 5838”时,我从logcat看到以下内容(正如我所期望的):

 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} 

如果我通过从最近的任务列表中“滑动”来结束任务,我会看到相同的重新启动行为。 所以这一切都很好 – 这意味着核心AOSP代码的行为与以前的级别相同。

我正在查看Cyanogenmod服务代码,试图找出为什么事情没有得到计划重新启动 – 没有运气。 看来它应该重新安排它。 Cyanogenmod使用AOSP没有的服务地图 – 但不清楚这是否是一个问题(可疑) https://github.com/CyanogenMod/android_frameworks_base/blob/cm-11.0/services/java/com/android/server /am/ActiveServices.java#L2092

你可以做的一个相当黑客的解决方法是使用类似于onTaskRemoved AlarmService的机制来在X分钟之后启用警报。 然后,每隔几分钟,当您的应用程序启动并运行时,您可以重置警报 – 所以只有在事情真的已经死亡并且没有重新启动时才会closures。 这不是万无一失的 – 使用Handler可以让您正常运行,而实时使用报警服务,因此即使设置的时间比“重置”处理程序的时间长,也可以触发报警。 但是如果你设置了一个额外的意图,你可以select忽略onStartCommand,如果你的服务已经启动并运行,把它变成一个noop。

我根本不是以下黑客的粉丝 – 但它不应该造成任何实际的伤害。 如果用户显式强制closures,则警报pipe理器将销毁所有设置的警报,以便服务不会重新启动(这是用户需要的)。

首先,创build一个帮助器方法,它将设置一个20分钟的警报,这将导致为您的服务触发onStartCommand。 每2分钟有一个处理程序将重置20分钟的警报。 如果处理程序在20分钟内运行,警报将永远不会消失。 处理程序不能保证运行,如果设备睡着了(这是很好的)。

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

在你的onCreate你可以调用这个方法。 另外 – 在你的onStartCommand,一定要忽略这个,如果你的服务已经启动并运行。 例如:

 @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; } 

这不是一个100%的工作解决scheme,但它是最好的,因为它几乎完全消除了这个问题。 到目前为止,我整合了这个解决scheme,重写onTaskRemoved (见这个答案 )和一个保持活着的通知(见这个答案 )。 额外的答案非常感谢!

经过进一步的调查,似乎bug已经存在于果冻豆,看起来像有一个解决scheme(至less在我的情况下,似乎工作,将继续testing和更新答案,如果需要的话)。

从我观察到的情况来看,这只发生在接收由AlarmManager设置的广播的服务上。

重现错误请按照下列步骤操作:

  1. 启动应用程序
  2. 作为一个前台服务启动服务(使用startForeground )从应用程序内
  3. 从“最近使用的应用程序”列表中滑动应用程序
  4. 发送由服务处理的广播
  5. 该服务被杀害!

使用adb shell dumpsys >C:\dumpsys.txt您可以监视不同步骤之间的服务状态。 (在dumpsys输出中查找Process LRU list )在步骤2和步骤3中,您将看到如下所示的内容:

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

具体来说,请注意F/S/IF(fg-service)指示服务正在作为前台服务运行(有关如何分析此链接上的dumpsys的更多详细信息, 请访问https://stackoverflow.com/a/14293528 / 624109 )。

在步骤4之后,您不会在Process LRU list看到您的服务。 相反,你可以看看设备logcat,你会看到以下内容:

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

看起来导致这种行为的原因是接收到的广播将服务从其前景状态中取出,然后被杀死。

为了避免这种情况,您可以在为AlarmManager创buildPendingIntent时使用这个简单的解决scheme(来源: https : //code.google.com/p/android/issues/detail? AlarmManager = AlarmManager

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

请注意以下步骤:

  1. 在intent上调用addFlags并使用FLAG_RECEIVER_FOREGROUND
  2. 在PendingIntent.getBroadcast中使用非零请求代码

如果您将这些步骤中的任何一个都排除在外,将无法使用。

请注意, FLAG_RECEIVER_FOREGROUND被添加到API 16(果冻豆),所以这是有道理的,这是当第一次出现错误…

最有可能的是,KitKat在杀死进程方面更具侵略性,这就是为什么它被KitKat强调,但看起来这已经与Jelly Bean相关。

注2:注意关于服务configuration的问题中的细节 – 在单独的进程中运行,作为前台服务,在清单中将endWithTask设置为false。

注3:同样的事情发生时,应用程序收到android.appwidget.action.APPWIDGET_CONFIGURE消息,并显示一个新的部件的configuration活动(replace上面的第4步创build一个新的部件)。 我发现,只有当小部件提供者(处理android.appwidget.action.APPWIDGET_UPDATE的接收者)被设置为在与活动进程不同的进程上运行时才会发生。 更改后,configuration活动和小部件提供程序都在同一个进程中,这不再发生。

我发现这个简单的技巧来解决这个问题,而不使用AlarmManager。

  1. 创build一个广播接收器,每次监听广播onDestroy()方法在服务中被调用:

     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. 将自定义的广播意图添加到您的清单

     <receiver android:name=".RestartService" android:enabled="true" > <intent-filter> <action android:name="restartApps" /> </intent-filter> </receiver> 
  3. 然后,从onDestroy()发送广播,可能是这样的:

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

这个技巧将重新启动您的服务,每次用户closures服务,从任务pipe理器和强制closures设置,我希望这也能帮助你

从android服务.START_STICKY

从onStartCommand(Intent,int,int)返回常量:如果此服务的进程在启动时(从onStartCommand(Intent,int,int)返回后被终止),则将其保留在启动状态,但不保留交付的意图。 稍后系统将尝试重新创build服务。 因为它处于开始状态,所以在创build新的服务实例之后,保证调用onStartCommand(Intent,int,int) 如果没有待处理的启动命令被传送到服务,它将被调用一个空的intent对象,所以你必须小心检查这一点。

这种模式对于明确启动和停止运行任意时间段的事情是有意义的,例如执行背景音乐播放的服务。

它并没有说你的服务在被杀之后会被恢复,它告诉你开始你的服务的情况,并且在onStartCommand被调用之前已经被销毁了。

如果你想保持你的服务被杀死,你应该运行在前台模式。 此外,它还build议通过在自己的进程中运行它来将您的服务与应用程序生命周期分开。