Android 應用被殺后Notification不取消問題及應用深殺和淺殺時Service生命周期情況

項目中有如下需求:后臺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) {
                    }
                }
            });
        }
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容