为什么我的Android服务在进程被终止时重新启动,即使我使用了START_NOT_STICKY?
我的应用程序使用了一种模式,我使用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(); }
为了testing它,我启动了应用程序,启动服务并绑定到它。 然后,我退出了应用程序,解除绑定(但保持服务运行)。 然后我做了
$ 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}
如果我用BIND_AUTO_CREATEreplacestartService()模式,问题不会发生(即使我仍然绑定到服务时崩溃应用程序)。 如果我从不与服务绑定,它也可以工作。 但是,开始,绑定和解除绑定的组合似乎永远不会让我的服务死亡。
杀死应用程序之前使用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
看看这个文档部分:服务生命周期的变化(从1.6开始)
更新
经过一些调查(已创build项目,运行描述命令一步一步等),我发现代码完全按照“服务生命周期变化”
我发现:
adb shell am kill com.tavianator.servicerestart
什么都不 adb shell am kill com.tavianator.servicerestart
(尝试adb shell
,然后在shell中am kill com.tavianator.servicerestart
您将遇到错误消息Error: Unknown command: kill
)
所以,
运行你的程序,
运行adb shell
在shell运行ps
命令
find您的应用程序的PID号码
在shell运行命令kill <app_xx_PID>
你的PID号码在哪里?
重复查杀服务步骤(如果在自己的进程中运行)
检查服务是否正在运行(不应该),5-7秒后重新启动
更新
一个解决scheme(不够好,但在某些情况下可用)是stopSelf()
例如:
@Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "onStartCommand", Toast.LENGTH_LONG).show(); stopSelf(); return START_NOT_STICKY; }
更新
更新解决scheme
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()返回后终止服务,则不要重新创build该服务,除非有待处理的意图传递。 这是最安全的select,以避免在不必要的时候运行你的服务,当你的应用程序可以简单地重新启动任何未完成的工作。
因此,在阅读本文档和一些实验后,我认为系统把手动终止的服务视为未完成(崩溃:@see W/ActivityManager(306): Scheduling restart of crashed service
)并重新启动它,尽pipeonStartCommand返回值。
stopSelf()或stopService() – 不重新启动 ,为什么不完成工作?
我相信这里的问题是,对于通过调用startService()
启动的服务,绑定的内部记帐(受到对bindService()
/ unbindService()
的调用的影响)以某种方式阻止服务被正确地closures计划在进程中止时重新启动。
根据你的喜好,似乎有三种方法可以避免这个问题(你提到的前两个问题已经在你的问题中了):
-
只使用
startService()
/stopService()
,根本不要使用bindService()
/unbindService()
。 -
仅使用
bindService()
/unbindService()
(使用BIND_AUTO_CREATE
),根本不使用startService()
/stopService()
。 -
如果您需要
startService()
/stopService()
和bindService()
/unbindService()
,请覆盖服务中的onUnbind()
方法并stopSelf()
调用stopSelf()
:@Override public boolean onUnbind(Intent intent) { stopSelf(); return super.onUnbind(intent); }
从我的testing:
-
使用第一个替代方法将在日志中打印此行:
W / ActivityManager(nnn):在5000ms内调度崩溃服务xxxx的重启
但之后服务将不会真正重新启动。
-
第二种select会导致您的服务在解除绑定后被销毁(在示例中的Activity的
onStop()
中)。 -
第三个select基本上会使你的代码像第二个select一样(不需要太多的改变)。
希望这可以帮助!
如何重新考虑你的模式 ?
在Android进化(1.6之后)的某个时候出现了START_NOT_STICKY的标志。 很可能,您正在面临当时引入的服务生命周期中的一些微妙的衰退。 所以,即使现在发现了一个同样微妙的解决scheme,并且通过魔法validation可以在所有可能的设备上运行,但是在新的Android版本下,它并不一定能够正常工作。
无论如何,生命周期模式看起来不寻常,这可能是你陷入这个奇特问题的原因。
我们来考虑两个案例。
-
正常(?)。 一些活动启动服务。 不久之后,它被绑定,使用,解除绑定 – 那又怎样? 不知何故停下来? 在这种情况下怎么没有显着的电池消耗?
-
不正常。 服务在一些(随机?)点被杀害。 当重新启动,它实现了一些错误的假设,并保持活跃,做一些耗尽电池?
这一切看起来很奇怪。
我会分析你的产品中的并发进程。 所有重大案件。 一些图表等等。最可能的是,结果是,这种模式的需求将被消除。
否则,看起来像任何解决方法将是一个脆弱的黑客。