START_STICKY无法在Android KitKat上运行

时间:2013-12-17 14:08:30

标签: android android-service sticky

我的一个应用程序有一个backgrouod服务,它使用START_STICKY的{​​{1}}返回代码在系统终止时自动重启。 似乎这不再适用于Android KitKat。 这有什么解决方案吗? 我应该在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

Edit2:即使服务使用onStartCommand在一个单独的进程中运行,并且AndroidManifest.xml文件中的标记为startForeground,也会发生同样的情况......

Edit3: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/android/issues/detail?id=104308

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

4 个答案:

答案 0 :(得分:28)

似乎这是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);

}

this post

找到了这个答案

答案 1 :(得分:22)

此处的问题似乎不会出现在基于AOSP的ROM上。也就是说,我可以在基于CyanogenMod 11的ROM上轻松地重新创建它,但是在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分钟后启用警报。然后,在您的应用程序启动并运行的每隔几分钟,您就可以重置警报 - 因此只有在事情确实已被杀死且未重新启动时它才会关闭。这不是万无一失的 - 使用Handler可以提供正常运行时间与使用实时的警报服务,因此即使设置的时间比“重置”处理程序长,也可以触发警报。但是如果你设置了额外的意图,你可以选择忽略onStartCommand,如果你的服务已经启动并运行,将其变成noop。

我根本不是以下黑客的粉丝 - 但它不应该造成任何真正的伤害。如果用户执行显式强制关闭,则警报管理器将销毁任何警报集,以便服务不会重新启动(这是用户想要的)。

首先,创建一个帮助方法,将警报设置为20分钟,这将导致为您的服务触发onStartCommand。每2分钟有一个Handler,它将重置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中,如果你的服务已经启动并运行,请务必忽略它。 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;
}

答案 2 :(得分:16)

这不是一个100%可行的解决方案,但它几乎完全消除了问题,它是最好的。到目前为止,我整合了此解决方案以及覆盖onTaskRemoved(请参阅this answer)和保持活动通知(请参阅this answer)。 非常感谢其他答案!

经过进一步调查,看来这个bug已经存在于Jelly Bean中,看起来有一个解决方案(至少在我的情况下似乎有效。如果需要,将继续测试并更新答案)。< / p>

根据我的观察,这只发生在接收AlarmManager设置的广播的服务中。

要重现错误,请按以下步骤操作:

  1. 启动应用
  2. 从应用程序
  3. 中启动服务作为前台服务(使用startForeground
  4. 从“最近的应用”列表
  5. 中滑动应用
  6. 发送由服务处理的广播
  7. 服务被杀!
  8. 使用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)表示服务正在作为前台服务运行(有关如何在此链接上分析dumps的更多详细信息:https://stackoverflow.com/a/14293528/624109)。< / p>

    在第4步之后,您将无法在Process LRU list中看到您的服务。 相反,您可以查看设备logcat,您将看到以下内容:

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

    似乎导致该行为的原因是接收的广播将服务从其前景状态中取出然后被杀死。

    为避免这种情况,您可以在为PendingIntent创建AlarmManager时使用此简单解决方案(来源: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);
    

    请注意以下步骤:

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

      如果你退出任何一个步骤,它将无效。

      请注意,{16}在API 16(Jelly Bean)上添加了FLAG_RECEIVER_FOREGROUND,所以这是错误首次出现的时候......

      KitKat最有可能在杀死进程时更具侵略性,这就是KitKat强调它的原因,但看起来这已经与Jelly Bean有关了。

      注意2:请注意有关服务配置的问题中的详细信息 - 作为前台服务在单独的进程中运行,并在清单中将endWithTask设置为false。

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

答案 3 :(得分:8)

我发现这个简单的技巧可以在不使用AlarmManager的情况下解决这个问题。

  1. 创建一个广播接收器,每次调用服务中的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. onDestroy()

  5. 致电onTaskRemoved(Intent intent)

    这个技巧会在每次用户从任务管理器关闭服务并强制关闭设置时重启你的服务,我希望这对你也有帮助