START_STICKY 不适用于 Android KitKat

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20636330/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 03:11:47  来源:igfitidea点击:

START_STICKY does not work on Android KitKat

androidandroid-servicesticky

提问by Muzikant

One of my apps has a backgrouod service that uses the START_STICKYreturn code from onStartCommandto 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 ?

我的一个应用程序有一个后台服务,当系统杀死它时,它使用START_STICKY返回码onStartCommand自动重新启动。似乎这不再适用于 Android KitKat。有什么解决办法吗?我应该在 Kitkat 上做一些不同的事情来保持服务运行吗?

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

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

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

编辑:看到 Android 问题跟踪器上存在未解决的错误:

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

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...

Edit2:即使服务正在运行startForeground,也会发生同样的情况,在单独的进程android:stopWithTask="false"中使用 AndroidManifest.xml 文件中的标志......

Edit3: More related bugs on Android issue tracker:

Edit3:Android 问题跟踪器上的更多相关错误:

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

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 ?

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

采纳答案by Muzikant

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 !

这不是一个 100% 有效的解决方案,但它是最好的,因为它几乎完全消除了问题。到目前为止,我将此解决方案与覆盖onTaskRemoved(请参阅此答案)和保持活动通知(请参阅此答案)一起集成。非常感谢其他答案!

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

经过进一步调查,该错误似乎已存在于 Jelly Bean 中,并且似乎有解决方案(至少在我看来似乎有效。如果需要,将继续测试并更新答案)。

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

据我观察,这只发生在接收由AlarmManager.

To reproduce the bug follow these steps:

要重现该错误,请执行以下步骤:

  1. Start the app
  2. start the service as a foreground service (use startForegroundfor 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 !
  1. 启动应用程序
  2. startForeground从应用程序内将服务作为前台服务(用于该服务)启动
  3. 从“最近的应用程序”列表中滑动应用程序
  4. 发送由服务处理的广播
  5. 服务被杀了!

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

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

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

Specifically, notice the F/S/IFand 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).

具体来说,请注意F/S/IF(fg-service)表示该服务作为前台服务运行(有关如何在此链接中分析 dumpsys 的更多详细信息: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:

在第 4 步之后,您将不会在Process LRU list. 相反,您可以查看设备 logcat,您将看到以下内容:

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 simplesolution when creating your PendingIntentfor the AlarmManager(Source: https://code.google.com/p/android/issues/detail?id=53313#c7)

为避免这种情况,您可以在为(来源:https: //code.google.com/p/android/issues/detail?id=53313#c7)创建时使用这个简单的解决方案PendingIntentAlarmManager

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
  1. 在意图上调用 addFlags 并使用 FLAG_RECEIVER_FOREGROUND
  2. 在 PendingIntent.getBroadcast 中使用非零请求代码

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

如果您忽略任何这些步骤,它将无法正常工作。

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

请注意,这FLAG_RECEIVER_FOREGROUND是在 API 16 (Jelly Bean) 上添加的,因此这是错误首次出现的时间...

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.

很可能 KitKat 在杀死进程时更具侵略性,这就是 KitKat 强调它的原因,但看起来这已经与 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.

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

Note 3: The same thing happens when the app receives the android.appwidget.action.APPWIDGET_CONFIGUREmessage 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.

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

回答by Rakeeb Rajbhandari

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

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

}

Found this answer from this post

这篇文章中找到了这个答案

回答by George Tanner

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.

这里的问题似乎不会发生在基于 AOSP 的 ROM 上。也就是说,我可以在基于 CyanogenMod 11 的 ROM 上轻松地重新创建它,但在 AOSP ROM(和模拟器)上,START_STICKY 的行为与我预期的完全一样。也就是说,我看到 Nexus 5 上的人的报告似乎看到了这种行为,所以这可能仍然是 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):

在模拟器和 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}

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.

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

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

我正在查看 Cyanogenmod 服务代码,试图找出为什么没有安排重启的原因 - 还没有运气。看来它应该重新安排它。Cyanogenmod 使用 AOSP 没有的服务地图 - 但不清楚这是否是一个问题(可疑) 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.

您可以做的一个相当黑客的解决方法是使用与您的 onTaskRemoved AlarmService 类似的机制来启用 X 分钟后的警报。然后,当您的应用程序启动并运行时,每隔几分钟,您就可以重置警报 - 只有当事情真的被杀死并且没有重新启动时它才会响起。这并非万无一失 - 与使用实时的警报服务相比,使用处理程序可以为您提供正常运行时间,因此即使设置的时间比“重置”处理程序的时间长,您的警报也有可能触发。但是如果你设置了一个额外的意图,如果你的服务已经启动并运行,你可以选择忽略 onStartCommand,把它变成一个 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).

首先,创建一个辅助方法,该方法将设置 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);  
    }
}

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:

在您的 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;
}

回答by baskara

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

我发现这个简单的技巧可以在不使用 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)

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

每次用户从任务管理器关闭服务并从设置中强制关闭时,此技巧将重新启动您的服务,我希望这也能帮助您