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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12648447/
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 11:19:31  来源:igfitidea点击:

Why does my Android service get restarted when the process is killed, even though I used START_NOT_STICKY?

androidandroid-service

提问by Tavian Barnes

My app uses a pattern where I start a service with Context#startService()as well as bind to it with Context#bindService(). This is so that I can control the lifetime of the service independently from whether any clients are currently bound to it. However, I noticed recently that whenever my app is killed by the system, it soon restarts any services that were running. At this point the service will never be told to stop, and this is causing battery drain whenever it happens. Here's a minimal example:

我的应用程序使用一种模式,我使用Context#startService()启动服务并使用Context#bindService()绑定到它。这样我就可以独立于当前是否绑定了任何客户端来控制服务的生命周期。但是,我最近注意到,每当我的应用程序被系统杀死时,它很快就会重新启动所有正在运行的服务。在这一点上,永远不会告诉服务停止,这会导致电池耗尽。这是一个最小的例子:

I found someone with a similar issue here, but it wasn't ever diagnosed or solved.

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

Service:

服务:

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

Activity:

活动:

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

To test it, I launched the app, which started the service and bound to it. Then I backed out of the app, which unbinds (but leaves the service running). Then I did

为了测试它,我启动了应用程序,它启动了服务并绑定到它。然后我退出了解除绑定的应用程序(但保持服务运行)。然后我做了

$ adb shell am kill com.tavianator.servicerestart

and sure enough, 5 seconds later, the "onCreate" toast appears, indicating that the service started again. Logcat shows this:

果然,5秒后,出现了“onCreate”toast,说明服务又启动了。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}

If I replace the startService() pattern with BIND_AUTO_CREATE, the problem doesn't occur (even if I crash the app while it's still bound to the service). It also works if I never bind to the service. But the combination of start, bind, and unbind seems to never let my service die.

如果我用 BIND_AUTO_CREATE 替换 startService() 模式,问题就不会发生(即使我在应用程序仍然绑定到服务时崩溃)。如果我从不绑定到服务,它也可以工作。但是 start、bind 和 unbind 的组合似乎永远不会让我的服务消亡。

Using dumpsys before killing the app shows this:

在杀死应用程序之前使用 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

回答by ra.

Look to this documentsection: Service lifecycle changes (since 1.6)

查看此文档部分:服务生命周期更改(自 1.6 起)

updated

更新

after some investigations (have created project, run described command step by step, etc) i found that code is working exactly as described in "Service lifecycle changes"

经过一些调查(已创建项目,逐步运行描述的命令等)后,我发现代码的工作方式与“服务生命周期更改”中描述的完全相同

what I found:
adb shell am kill com.tavianator.servicerestartkills nothing
(try adb shell, then in shell am kill com.tavianator.servicerestart
You will be faced with errormessage Error: Unknown command: kill)

我发现
adb shell am kill com.tavianator.servicerestart什么什么也没有杀死
(尝试adb shell,然后在 shell 中am kill com.tavianator.servicerestart
您将面临错误消息Error: Unknown command: kill

so,
run your application,
run adb shell
in shell run pscommand
find PID number of Your app
in shell run command kill <app_xx_PID>
where is Your PID number
repeat killing steps for service (if it is running in its own process)
check if service is running (should not), restarted after 5-7 sec
update
one solution (not enough good, but usable in some cases) is stopSelf()e.g.:

因此,
运行应用程序,
运行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;
}


update
updated solution


更新
更新的解决方案

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

Why service get restrted when the process is killed?

为什么当进程被终止时服务会被重新启动?

According to this document:

根据这份文件

When a service is started, it has a lifecycle that's independent of the component that started it and the service can run in the background indefinitely, even if the component that started it is destroyed. As such, the service should stop itself when its job is done by calling stopSelf(), or another component can stop it by calling stopService().
Caution: It's important that your application stops its services when it's done working, to avoid wasting system resources and consuming battery power. If necessary, other components can stop the service by calling stopService(). Even if you enable binding for the service, you must always stop the service yourself if it ever receiveda call to onStartCommand()

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

from other hand document says:

从另一手文件说:

*START_NOT_STICKY* - If the system kills the service after onStartCommand() returns, do not recreate the service, unless there are pending intents to deliver. This is the safest option to avoid running your service when not necessary and when your application can simply restart any unfinished jobs.

*START_NOT_STICKY* - 如果系统在 onStartCommand() 返回后终止服务,请勿重新创建服务,除非有待交付的意图。这是避免在不必要时运行服务的最安全选项,并且当您的应用程序可以简单地重新启动任何未完成的作业时。

So, after reading this document and some experiments i think system treats manually killed services as unfinished (crashed: @see W/ActivityManager(306): Scheduling restart of crashed service) and restarts it despite of value returned by onStartCommand.

因此,在阅读本文档和一些实验后,我认为系统将手动W/ActivityManager(306): Scheduling restart of crashed service终止的服务视为未完成(崩溃:@see )并重新启动它,尽管 onStartCommand 返回了值。


stopSelf() or stopService() - no restarts, why not if job done ?


stopSelf() 或 stopService() -没有重启,如果工作完成为什么不呢?

回答by Joe

I believe the problem here is that for a Service that was started by calling startService(), the internal accounting for its bindings (affected by calls to bindService()/unbindService()) are somehow preventing the service to be brought down correctly after it is scheduled for restart when the process is killed.

我相信这里的问题是,对于通过调用启动的服务,startService()其绑定的内部帐户(受对bindService()/的调用影响unbindService())以某种方式阻止服务在计划在进程终止时重新启动后正确关闭.

Depending on your preference, it seems that there are three alternatives to avoid this issue (you mentioned the first two already in your question):

根据您的偏好,似乎有三种替代方法可以避免此问题(您在问题中已经提到了前两种):

  • Use startService()/stopService()only, do not use bindService()/unbindService()at all.

  • Use bindService()/unbindService()only (with BIND_AUTO_CREATE), do not use startService()/stopService()at all.

  • If you need startService()/stopService()andbindService()/unbindService(), override the onUnbind()method in your service and call stopSelf()from it:

    @Override
    public boolean onUnbind(Intent intent) {
        stopSelf();
        return super.onUnbind(intent);
    }
    
  • 仅使用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);
    }
    

From my testing:

从我的测试:

  • Using the first alternative will print this line in the log:

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

    but the service will not be really restarted after that.

  • The second alternative will cause your service to be destroyed after you unbind (in the Activity's onStop()in your example).

  • The third alternative will basically make your code behave like the second alternative (without needing to change it by much).

  • 使用第一个选项将在日志中打印这一行:

    W/ActivityManager(nnn):在 5000 毫秒内安排崩溃服务 xxxx 的重启

    但在那之后服务不会真正重新启动。

  • 第二种选择将导致您的服务在您解除绑定后被销毁(onStop()在您的示例中的 Activity中)。

  • 第三种选择基本上会使您的代码表现得像第二种选择(无需对其进行太多更改)。

Hope this helps!

希望这可以帮助!

回答by full.stack.ex

How about reconsidering your pattern?

重新考虑你的模式怎么

The START_NOT_STICKY flag appeared at some point during Android evolution (after 1.6?). Most probably, you are facing some subtle regression in service life cycle, which was introduced at that time. So, even if an equally subtle solution is found for now - and magically verified to work on all possible devices - it will not necessarily work under new Android versions.

START_NOT_STICKY 标志出现在 Android 进化过程中的某个时刻(1.6 之后?)。最有可能的是,您正面临着当时引入的服务生命周期中的一些微妙的回归。因此,即使现在找到了一个同样微妙的解决方案 - 并且经过神奇验证可以在所有可能的设备上运行 - 它也不一定适用于新的 Android 版本。

Anyway, that life cycle pattern looks unusual, and that might be the very reason for your running into this exotic problem.

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

Let's consider two cases.

让我们考虑两种情况。

  1. Normal (?). Some activity starts the service. Soon after that it gets bound, used, unbound - then what? Stopped somehow? How come there's no significant battery drain in this case?

  2. Abnormal. Service killed at some (random?) point. When restarted, it implements some wrong assumption and stays active, doing something that drains the battery?

  1. 普通的 (?)。某些活动启动服务。不久之后它被绑定、使用、解除绑定——然后呢?不知何故停止了?在这种情况下,为什么没有明显的电池消耗?

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

It all looks rather strange.

这一切看起来都很奇怪。

I would analyze the concurrent processes in your product. All significant cases. Some diagrams, etc. Most probably, as a result, the very need for such a pattern would be eliminated.

我会分析您产品中的并发流程。所有重大案件。一些图表等。结果很可能,对这种模式的需求将被消除。

Otherwise, it looks like any workaround will be a fragile hack.

否则,看起来任何解决方法都将是一个脆弱的黑客。