项目中有如下需求:后台service进行导入操作,要更新Notification。当运行系统清理使应用被杀时,Notification无法取消,仍然在通知栏显示。为解决这个问题进行了如下探索:
首先想到利用service的startForeground()来更新通知栏,这样当应用被杀掉时候Notification可以一起被去掉。但针对项目的需求:service可以同时导入多个文件,并且会对应显示多个通知。这种情况下用service.startForeground()更新通知栏时候,当应用被杀时候之后cancel掉最后一次调用startForeground对应id的Notification,而其他通知仍然不能被取消。
继续探索用其他方式取消通知栏:在进程被杀掉的时候,会调用service的哪些生命周期函数呢?service的onDestroy()方法只有在调用Context的stopService()或Service的stopSelf()后才会被调用,在应用被杀时候Service的onDestroy()不会被执行。
我们发现service的 onTaskRemoved()方法,该方法何时被调用呢?方法的注释说明是这么写的:
/**
* This is called if the service is currently running and the user has
* removed a task that comes from the service's application. If you have
* set {@linkandroid.content.pm.ServiceInfo#FLAG_STOP_WITH_TASK ServiceInfo.FLAG_STOP_WITH_TASK}
* then you will not receive this callback; instead, the service will simply
* be stopped.
*
*@paramrootIntentThe original root Intent that was used to launch
* the task that is being removed.
*/
public voidonTaskRemoved(Intent rootIntent) {
}
注释表明onTaskRemoved()方法在当用户移除应用的一个Task栈时被调用。也就是当用户在最近任务界面把该应用的一个task划掉时,或者在最近任务界面进行清理时。这两种情况下onTaskRemoved()都会被调用,但在大多Android机型上,这两种情况有所不同:第一种情况即应用被浅杀(用户只划掉这一个Task),该Task栈会被清理,但如果有后台service在运行,该应用的进程不会被杀掉,后台service仍然在运行。第二种即应用被深杀(用户在最近任务界面直接按清理按钮),该应用的进程会被直接杀掉,后台的service当然也停止了。对于不同的手机品牌和机型在最近任务进行各种清理时过程可能不太一样,但应用浅杀和深杀对于所有Android手机都是有普遍意义的。
下面我们分析在应用被浅杀和被深杀以及先浅杀再深杀后的生命周期:
浅杀:
04-21 17:55:13.733 8264-8264/com.qintong.test D/qintong: vCardService onTaskRemoved.
深杀:
会出现两种情况:
(a).
04-26 16:20:00.349 32674-32674/? D/qintong: Service onTaskRemoved.
04-26 16:21:01.621 2936-2936/? D/qintong: Service is being created.
04-26 16:21:01.628 2936-2936/? D/qintong: Service onStartCommand.
(b).
04-21 17:59:58.397 8264-8264/com.qintong.test D/qintong: Service onCreate.
04-21 17:59:58.404 8264-8264/com.qintong.test D/qintong: Service onTaskRemoved.
浅杀+深杀 (service 的 onStartCommand 返回 STICKY):
04-21 18:05:12.717 8264-8264/com.qintong.test D/qintong: Service onTaskRemoved.
04-21 18:05:29.214 9207-9207/com.qintong.test D/qintong: Service onCreate.
04-21 18:05:29.223 9207-9207/com.qintong.test D/qintong: Service onStartCommand.
我们来分析这几种情况:
(1).浅杀时:应用进程没被杀掉,service仍然在执行,service的onTaskRemoved()立即被调用。
(2).深杀时:有两种情况:第一种情况是深杀后直接调用onTaskRemoved()且service停止,过段时间后service重启调用其onCreate()和onStartCommand()。第二种是应用的进程被杀掉,过一会后service的onCreate()方法被调用,紧接着onTaskRemoved()被调用。由于被深杀后应用的进程立刻停止了,所以service的onTaskRemoved()无法被立即调用。而过若干秒后,service重启,onCreate()被调用,紧接着onTaskRemoved()被调用。而这里service的其他方法并没有被调用,即使onStartCommand()返回STICKY,service重启后onStartCommand()方法也没有被调用。
(3).浅杀+深杀时(service 的 onStartCommand 返回 STICKY):onTaskRemoved()立刻被调用(浅杀后),深杀后过段时间onCreate()和onStartCommand()相继被调用。执行浅杀Task被清理,应用的进程还在,onTaskRemoved()被调用,过程与(1)一样。再执行深杀:由于该应用的Task栈已经没有了,所有再深杀onTaskRemoved()不会再被调用,深杀后service停止。而由于实验时候onStartCommand()返回STICKY,所有service过段时间会被再次启动,执行了onCreate()方法和onStartCommand()方法。
所以综上所述,service的onTaskRemoved()在应用浅杀后会被立即调用而在service被深杀后,会直接调用onTaskRemoved或service会被重启并调用onTaskRemoved()。
回到我们的问题:应用被杀后,如何取消Notification:
我们先看最后的解决方案,在来分析为何能work。
service的代码如下:
@Override
public void onCreate() {
super.onCreate();
mBinder=newMyBinder();
if(DEBUG) Log.d(LOG_TAG,"vCardService is being created.");
mNotificationManager= ((NotificationManager)getSystemService(NOTIFICATION_SERVICE));
initExporterParams();
}
@Override
public int onStartCommand(Intent intent, intflags, intid) {
if(DEBUG) Log.d(LOG_TAG,"vCardService onStartCommand.");
mNotificationManager.cancelAll();
returnSTART_STICKY;
}
@Override
public void onTaskRemoved(Intent rootIntent) {
if(DEBUG) Log.d(LOG_TAG,"vCardService onTaskRemoved.");
mNotificationManager.cancelAll();
super.onTaskRemoved(rootIntent);
}
如上代码,在浅杀时候:只执行onTaskRemoved(),通知被取消,但service仍然在运行,所以还会继续发通知,正常运行。
深杀时:第一种情况直接调用onTaskRemoved()且service停止,通知被取消。第二种情况,进程被杀掉,几秒后service重启,onCreate() -> onTaskRemoved(),运行结果就是深杀后过几秒后Notification被取消。
浅杀+深杀时:浅杀后onTaskRemoved()被调用,service仍在运行,通知仍然在更新。深杀时,onCreate() -> onStartCommand(),在onStartCommand()时候取消通知。
另外,mNotificationManager.cancelAll()会清除应用的所有通知,如果应用想保留和该service无关其他通知,可以调用mNotificationManager.cancel(String tag, int id)或cancel(int id)清除指定通知。
当然,还可以有另一种方式:浅杀时后就把service后台执行的任务停止,并清理notification,我们可以根据需求来选择。
补充:
疑问:1.为啥有时候深杀不立即调用onTaskRemoved(),而是在重启之后调用的呢?
stackoverflow上的答复:https://stackoverflow.com/questions/32224233/ontaskremoved-called-after-oncreate-in-started-service-on-swipe-out-from-recent/41506752
大意是service执行较重UI操作时候service不会立即停止,而新的service会启动。不太确定这个解释的正确性......
2.为何servive.startForeground()添加的Notification可以在service被杀死后去掉呢?我们分析源码:ActiveServices中killServicesLocked()->scheduleServiceRestartLocked()中调用了r.cancelNotification(),清除了notification:
public void cancelNotification() {
if (foregroundId != 0) {
// Do asynchronous communication with notification manager to
// avoid deadlocks.
final String localPackageName = packageName;
final int localForegroundId = foregroundId;
ams.mHandler.post(new Runnable() {
public void run() {
INotificationManager inm = NotificationManager.getService();
if (inm == null) {
return;
}
try {
inm.cancelNotificationWithTag(localPackageName, null,
localForegroundId, userId);
} catch (RuntimeException e) {
Slog.w(TAG, "Error canceling notification for service", e);
} catch (RemoteException e) {
}
}
});
}
}