56

我的应用程序使用一种模式,我使用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”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}

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

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

3 回答 3

31

查看此文档部分:服务生命周期更改(从 1.6 开始)

更新

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

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

所以,
运行你的应用程序, 在 shell 运行命令中
运行adb shell
在 shell 运行ps命令 中
找到你的应用程序的 PID 号 你的 PID 号
kill <app_xx_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;
}

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

根据这份文件

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

从另一手文件说:

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

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


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

于 2012-10-02T12:37:56.603 回答
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):计划在 5000 毫秒内重新启动崩溃的服务 xxxx

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

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

  • 第三种选择基本上会让你的代码表现得像第二种选择(不需要做太多的改变)。

希望这可以帮助!

于 2012-10-04T20:59:35.627 回答
2

重新考虑你的模式怎么样?

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

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

让我们考虑两种情况。

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

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

这一切看起来都很奇怪。

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

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

于 2012-10-07T20:22:26.583 回答