Android 應(yīng)用被殺后Notification不取消問題及應(yīng)用深殺和淺殺時(shí)Service生命周期情況

項(xiàng)目中有如下需求:后臺(tái)service進(jìn)行導(dǎo)入操作,要更新Notification。當(dāng)運(yùn)行系統(tǒng)清理使應(yīng)用被殺時(shí),Notification無法取消,仍然在通知欄顯示。為解決這個(gè)問題進(jìn)行了如下探索:

首先想到利用service的startForeground()來更新通知欄,這樣當(dāng)應(yīng)用被殺掉時(shí)候Notification可以一起被去掉。但針對(duì)項(xiàng)目的需求:service可以同時(shí)導(dǎo)入多個(gè)文件,并且會(huì)對(duì)應(yīng)顯示多個(gè)通知。這種情況下用service.startForeground()更新通知欄時(shí)候,當(dāng)應(yīng)用被殺時(shí)候之后cancel掉最后一次調(diào)用startForeground對(duì)應(yīng)id的Notification,而其他通知仍然不能被取消。

繼續(xù)探索用其他方式取消通知欄:在進(jìn)程被殺掉的時(shí)候,會(huì)調(diào)用service的哪些生命周期函數(shù)呢?service的onDestroy()方法只有在調(diào)用Context的stopService()或Service的stopSelf()后才會(huì)被調(diào)用,在應(yīng)用被殺時(shí)候Service的onDestroy()不會(huì)被執(zhí)行。

我們發(fā)現(xiàn)service的 onTaskRemoved()方法,該方法何時(shí)被調(diào)用呢?方法的注釋說明是這么寫的:

/**
* 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()方法在當(dāng)用戶移除應(yīng)用的一個(gè)Task棧時(shí)被調(diào)用。也就是當(dāng)用戶在最近任務(wù)界面把該應(yīng)用的一個(gè)task劃掉時(shí),或者在最近任務(wù)界面進(jìn)行清理時(shí)。這兩種情況下onTaskRemoved()都會(huì)被調(diào)用,但在大多Android機(jī)型上,這兩種情況有所不同:第一種情況即應(yīng)用被淺殺(用戶只劃掉這一個(gè)Task),該Task棧會(huì)被清理,但如果有后臺(tái)service在運(yùn)行,該應(yīng)用的進(jìn)程不會(huì)被殺掉,后臺(tái)service仍然在運(yùn)行。第二種即應(yīng)用被深殺(用戶在最近任務(wù)界面直接按清理按鈕),該應(yīng)用的進(jìn)程會(huì)被直接殺掉,后臺(tái)的service當(dāng)然也停止了。對(duì)于不同的手機(jī)品牌和機(jī)型在最近任務(wù)進(jìn)行各種清理時(shí)過程可能不太一樣,但應(yīng)用淺殺和深殺對(duì)于所有Android手機(jī)都是有普遍意義的。

下面我們分析在應(yīng)用被淺殺和被深殺以及先淺殺再深殺后的生命周期:

淺殺:

04-21 17:55:13.733 8264-8264/com.qintong.test D/qintong: vCardService onTaskRemoved.

深殺:
會(huì)出現(xiàn)兩種情況:
(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).淺殺時(shí):應(yīng)用進(jìn)程沒被殺掉,service仍然在執(zhí)行,service的onTaskRemoved()立即被調(diào)用。

(2).深殺時(shí):有兩種情況:第一種情況是深殺后直接調(diào)用onTaskRemoved()且service停止,過段時(shí)間后service重啟調(diào)用其onCreate()和onStartCommand()。第二種是應(yīng)用的進(jìn)程被殺掉,過一會(huì)后service的onCreate()方法被調(diào)用,緊接著onTaskRemoved()被調(diào)用。由于被深殺后應(yīng)用的進(jìn)程立刻停止了,所以service的onTaskRemoved()無法被立即調(diào)用。而過若干秒后,service重啟,onCreate()被調(diào)用,緊接著onTaskRemoved()被調(diào)用。而這里service的其他方法并沒有被調(diào)用,即使onStartCommand()返回STICKY,service重啟后onStartCommand()方法也沒有被調(diào)用。

(3).淺殺+深殺時(shí)(service 的 onStartCommand 返回 STICKY):onTaskRemoved()立刻被調(diào)用(淺殺后),深殺后過段時(shí)間onCreate()和onStartCommand()相繼被調(diào)用。執(zhí)行淺殺Task被清理,應(yīng)用的進(jìn)程還在,onTaskRemoved()被調(diào)用,過程與(1)一樣。再執(zhí)行深殺:由于該應(yīng)用的Task棧已經(jīng)沒有了,所有再深殺onTaskRemoved()不會(huì)再被調(diào)用,深殺后service停止。而由于實(shí)驗(yàn)時(shí)候onStartCommand()返回STICKY,所有service過段時(shí)間會(huì)被再次啟動(dòng),執(zhí)行了onCreate()方法和onStartCommand()方法。

所以綜上所述,service的onTaskRemoved()在應(yīng)用淺殺后會(huì)被立即調(diào)用而在service被深殺后,會(huì)直接調(diào)用onTaskRemoved或service會(huì)被重啟并調(diào)用onTaskRemoved()。

回到我們的問題:應(yīng)用被殺后,如何取消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);
}

如上代碼,在淺殺時(shí)候:只執(zhí)行onTaskRemoved(),通知被取消,但service仍然在運(yùn)行,所以還會(huì)繼續(xù)發(fā)通知,正常運(yùn)行。
深殺時(shí):第一種情況直接調(diào)用onTaskRemoved()且service停止,通知被取消。第二種情況,進(jìn)程被殺掉,幾秒后service重啟,onCreate() -> onTaskRemoved(),運(yùn)行結(jié)果就是深殺后過幾秒后Notification被取消。
淺殺+深殺時(shí):淺殺后onTaskRemoved()被調(diào)用,service仍在運(yùn)行,通知仍然在更新。深殺時(shí),onCreate() -> onStartCommand(),在onStartCommand()時(shí)候取消通知。
另外,mNotificationManager.cancelAll()會(huì)清除應(yīng)用的所有通知,如果應(yīng)用想保留和該service無關(guān)其他通知,可以調(diào)用mNotificationManager.cancel(String tag, int id)或cancel(int id)清除指定通知。
當(dāng)然,還可以有另一種方式:淺殺時(shí)后就把service后臺(tái)執(zhí)行的任務(wù)停止,并清理notification,我們可以根據(jù)需求來選擇。

補(bǔ)充:
疑問:1.為啥有時(shí)候深殺不立即調(diào)用onTaskRemoved(),而是在重啟之后調(diào)用的呢?
stackoverflow上的答復(fù):https://stackoverflow.com/questions/32224233/ontaskremoved-called-after-oncreate-in-started-service-on-swipe-out-from-recent/41506752
大意是service執(zhí)行較重UI操作時(shí)候service不會(huì)立即停止,而新的service會(huì)啟動(dòng)。不太確定這個(gè)解釋的正確性......
2.為何servive.startForeground()添加的Notification可以在service被殺死后去掉呢?我們分析源碼:ActiveServices中killServicesLocked()->scheduleServiceRestartLocked()中調(diào)用了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) {
                    }
                }
            });
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容