为什么当进程被杀死时,即使我使用了START_NOT_STICKY,我的Android服务仍会重新启动?

59

我的应用程序采用以下方式启动服务:Context#startService(),同时也使用 Context#bindService()进行绑定。这么做是为了能够独立地控制服务的生命周期,而不受任何客户端当前是否已绑定到该服务的影响。然而,我最近注意到,每当系统杀死我的应用程序时,它会很快重新启动任何正在运行的服务。此时,服务将永远不会被告知停止,这会导致电池耗尽。以下是一个最小示例:

我在这里找到了一个类似问题的人,链接,但从未得到诊断或解决。

服务:

@Override
public void onCreate() {
    Toast.makeText(this, "onCreate", Toast.LENGTH_LONG).show();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_NOT_STICKY;
}

@Override
public IBinder onBind(Intent intent) {
    return new Binder();
}

活动:

@Override
protected void onStart() {
    super.onStart();
    Intent service = new Intent(this, BoundService.class);
    startService(service);
    bindService(service, mServiceConnection, 0);
}

@Override
protected void onStop() {
    unbindService(mServiceConnection);
    Toast.makeText(this, "unbindService", Toast.LENGTH_SHORT).show();
    super.onStop();
}

为了进行测试,我启动了应用程序,并启动了服务并绑定到它。然后我退出了应用程序,这将取消绑定(但保持服务运行)。然后我执行了

$ adb shell am kill com.tavianator.servicerestart

果然,5秒钟后,“onCreate”提示出现了,表明服务再次启动。 Logcat 显示如下:

$ adb logcat | grep BoundService
W/ActivityManager(  306): Scheduling restart of crashed service com.tavianator.servicerestart/.BoundService in 5000ms
I/ActivityManager(  306): Start proc com.tavianator.servicerestart for service com.tavianator.servicerestart/.BoundService: pid=20900 uid=10096 gids={1028}
如果我将 startService() 模式替换为 BIND_AUTO_CREATE,则问题不会出现(即使在仍绑定到服务的情况下崩溃应用程序)。如果我从未绑定到服务,它也可以正常工作。但是启动、绑定和解除绑定的组合似乎永远不会让我的服务死亡。
在杀死应用程序之前使用 dumpsys 显示如下:
$ adb shell dumpsys activity services com.tavianator.servicerestart
ACTIVITY MANAGER SERVICES (dumpsys activity services)
  Active services:
  * ServiceRecord{43099410 com.tavianator.servicerestart/.BoundService}
    intent={cmp=com.tavianator.servicerestart/.BoundService}
    packageName=com.tavianator.servicerestart
    processName=com.tavianator.servicerestart
    baseDir=/data/app/com.tavianator.servicerestart-2.apk
    dataDir=/data/data/com.tavianator.servicerestart
    app=ProcessRecord{424fb5c8 20473:com.tavianator.servicerestart/u0a96}
    createTime=-20s825ms lastActivity=-20s825ms
    executingStart=-5s0ms restartTime=-20s825ms
    startRequested=true stopIfKilled=true callStart=true lastStartId=1
    Bindings:
    * IntentBindRecord{42e5e7c0}:
      intent={cmp=com.tavianator.servicerestart/.BoundService}
      binder=android.os.BinderProxy@42aee778
      requested=true received=true hasBound=false doRebind=false

你尝试过从代码中停止它吗?这会有所不同吗? - Mohammad Ersan
文档说明如下,如果存在未决 Intent,则会重新启动,因此可能在未解绑服务时将其置于挂起状态... - Mohammad Ersan
你尝试过测试输入的Intent在服务自动重新启动时是否真的为null吗?PS:这里有相同的问题https://dev59.com/9GDVa4cB1Zd3GeqPifqc - Rémi F
adb shell am kill com.tavianator.servicerestart 打印错误信息 Error: Unknown command: kill - ra.
@buzeeg 没有输入意图;onCreate()被调用,但不会调用onStartCommand()。 - Tavian Barnes
显示剩余2条评论
3个回答

32
请看这个document部分:服务生命周期更改(自1.6版本以来)
已更新
进行一些调查后(创建项目,逐步运行描述的命令等),我发现代码的工作方式与“服务生命周期更改”中所述完全相同。
我发现:
adb shell am kill com.tavianator.servicerestart并没有杀死任何东西
(尝试adb shell ,然后在shell中键入am kill com.tavianator.servicerestart
你将面临错误消息Error: Unknown command: kill)

所以,
运行你的应用程序,
运行 adb shell
在shell中运行 ps 命令
找到您的应用程序的PID号码
在shell中运行命令 kill <app_xx_PID>
其中 是您的PID号码
如果服务在自己的进程中运行,请重复杀死步骤
检查服务是否正在运行(不应该),5-7秒后重新启动
更新
一个解决方案(不够好,但在某些情况下可用)是 stopSelf(),例如:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(this, "onStartCommand", Toast.LENGTH_LONG).show();
    stopSelf();
    return START_NOT_STICKY;
}


更新
更新的解决方案

void writeState(int state) {
    Editor editor = getSharedPreferences("serviceStart", MODE_MULTI_PROCESS)
            .edit();
    editor.clear();
    editor.putInt("normalStart", state);
    editor.commit();
}

int getState() {
    return getApplicationContext().getSharedPreferences("serviceStart",
            MODE_MULTI_PROCESS).getInt("normalStart", 1);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (getState() == 0) {
        writeState(1);
        stopSelf();
    } else {
        writeState(0);
        Toast.makeText(this, "onStartCommand", Toast.LENGTH_LONG).show();
    }
    return START_NOT_STICKY;
}

为什么进程被杀死后服务会重新启动?

根据这个document

当一个服务被启动时,它有一个独立于启动它的组件的生命周期,即使启动它的组件被销毁,服务也可以无限期地在后台运行。因此,服务应该通过调用stopSelf()来停止自己,或者另一个组件可以通过调用stopService()来停止它。
注意:重要的是,应用程序在完成工作后停止其服务,以避免浪费系统资源和消耗电池电量。如有必要,其他组件可以通过调用stopService()来停止服务。即使您为服务启用了绑定,如果它曾经收到onStartCommand()的调用,则必须始终自己停止服务。

另一方面,另一个文档说:

如果系统在onStartCommand()返回后杀死服务,则不要重新创建服务,除非有挂起的意图需要传递。这是最安全的选项,可以避免在不必要时运行您的服务,并且当您的应用程序可以简单地重新启动任何未完成的作业时。
因此,在阅读此文档和进行一些实验后,我认为系统将手动杀死的服务视为未完成的(崩溃:@see W/ActivityManager(306): Scheduling restart of crashed service),并且重新启动它,尽管onStartCommand返回的值。
stopSelf()或stopService() - 没有重新启动,为什么不是工作完成了吗?

1
至少指出重要部分。请记住他使用了“START_NOT_STICKY”。 - Trung Nguyen
从那份文档中可以看到,“START_NOT_STICKY表示,在从onStartCreated()返回后,如果进程被杀死且没有剩余的启动命令需要传送,则服务将停止而不是重新启动。”但是实际上并未发生这种情况。 - Tavian Barnes
关于您编辑后的回答,“am kill <package>”在我的手机上可以正常工作。可能取决于手机?我的是已 root 的 Galaxy Nexus。无论如何,在 onStartCommand() 中调用 stopSelf() 将立即停止服务,对吗?或者至少在它解绑之后立即停止,但这对我行不通。 - Tavian Barnes
我已在模拟器和GT-I9003上进行了测试,未经root。 - ra.
MODE_MULTI_PROCESS 在 SDK23 及以上版本中已弃用。 - Madhukar Hebbar
显示剩余2条评论

10
我认为这里的问题是,对于通过调用startService()启动的服务,其绑定的内部记账(受到对bindService()/unbindService()的调用的影响)在进程被杀死后阻止了服务正确地重新启动。
根据您的喜好,似乎有三种选择可避免此问题(您已在问题中提到前两种):
  • 只使用startService()/stopService(),不使用bindService()/unbindService()
  • 仅使用bindService()/unbindService()(使用BIND_AUTO_CREATE),不使用startService()/stopService()
  • 如果您需要同时使用startService()/stopService()bindService()/unbindService(),请在您的服务中覆盖onUnbind()方法,并从中调用stopSelf()
  • @Override
    public boolean onUnbind(Intent intent) {
        stopSelf();
        return super.onUnbind(intent);
    }
    
    从我的测试中得出以下结论:
    • 使用第一种替代方案将会在日志中打印这行信息:

      W/ActivityManager( nnn): Scheduling restart of crashed service xxxx in 5000ms

      但是服务并不会真正重新启动。

    • 使用第二种替代方案将会导致你的服务在解绑后被销毁(例如在 Activity 的 onStop() 中)。

    • 使用第三种替代方案基本上会使你的代码表现得像第二种替代方案一样(不需要太多修改)。

    希望这能帮到你!

2

你考虑重新审视一下你的模式怎么样?

START_NOT_STICKY标志在Android发展过程中的某个时间点出现(1.6之后?)。很可能,您面临的是服务生命周期中引入的一些微妙的退化。因此,即使现在找到了同样微妙的解决方案,并且神奇地验证了在所有可能的设备上运行,它也不一定会在新的Android版本下起作用。

无论如何,那种生命周期模式看起来很不寻常,这可能是你遇到这个奇特问题的原因。

让我们考虑两种情况。

  1. 正常(?)。某个活动启动了服务。之后不久,它就被绑定、使用、解除绑定-然后呢?以某种方式停止了吗?为什么在这种情况下没有显着的电池耗尽?

  2. 异常。服务在某个(随机?)时刻被杀死。当重新启动时,它实现了一些错误的假设,并保持活动状态,做一些消耗电池的事情?

这一切看起来相当奇怪。

我会分析您产品中的并发进程。所有重要的情况。一些图表等。很可能,结果是消除这种模式的必要性。

否则,看起来任何解决方法都将是脆弱的hack。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接