Android O新增的一個特性,系統會在通知欄顯示當前在後臺運行的應用,其實際是顯示啓動了前臺服務的應用,而且當前應用的Activity不在前臺。具體咱們看下源碼是怎麼實現的。html
startService
或startForegroundService
啓動一個service.startService
和startForegroundService
在Android O上主要有兩個區別:
一個是後臺應用沒法經過startService
啓動一個服務,而不管前臺應用仍是後臺應用,均可以經過startForegroundService
啓動一個服務。
此外Android規定,在調用startForegroundService
啓動一個服務後,須要在服務被啓動後5秒內調用startForeground方法,
不然會結束掉該service而且拋出一個ANR異常。關於前臺應用和後臺應用的規範見官網android
startForeground
方法,將service置爲前臺服務。其中有一個地方要注意,第一個參數id不能等於0。public final void startForeground(int id, Notification notification) { try { mActivityManager.setServiceForeground( new ComponentName(this, mClassName), mToken, id, notification, 0); } catch (RemoteException ex) { } }
接着調用了AMS的setServiceForeground
方法,該方法會調用ActiveServices
的setServiceForegroundLocked
方法。
ActiveServices是用來輔助AMS管理應用service的一個類。app
public void setServiceForegroundLocked(ComponentName className, IBinder token, int id, Notification notification, int flags) { final int userId = UserHandle.getCallingUserId(); final long origId = Binder.clearCallingIdentity(); try { ServiceRecord r = findServiceLocked(className, token, userId); if (r != null) { setServiceForegroundInnerLocked(r, id, notification, flags); } } finally { Binder.restoreCallingIdentity(origId); } }
setServiceForegroundInnerLocked
方法private void setServiceForegroundInnerLocked(ServiceRecord r, int id, Notification notification, int flags) { if (id != 0) { if (notification == null) { throw new IllegalArgumentException("null notification"); } // Instant apps need permission to create foreground services. // ...A lot of code is omitted notification.flags |= Notification.FLAG_FOREGROUND_SERVICE; r.foregroundNoti = notification; if (!r.isForeground) { final ServiceMap smap = getServiceMapLocked(r.userId); if (smap != null) { ActiveForegroundApp active = smap.mActiveForegroundApps.get(r.packageName); if (active == null) { active = new ActiveForegroundApp(); active.mPackageName = r.packageName; active.mUid = r.appInfo.uid; active.mShownWhileScreenOn = mScreenOn; if (r.app != null) { active.mAppOnTop = active.mShownWhileTop = r.app.uidRecord.curProcState <= ActivityManager.PROCESS_STATE_TOP; } active.mStartTime = active.mStartVisibleTime = SystemClock.elapsedRealtime(); smap.mActiveForegroundApps.put(r.packageName, active); requestUpdateActiveForegroundAppsLocked(smap, 0); } active.mNumActive++; } r.isForeground = true; } r.postNotification(); if (r.app != null) { updateServiceForegroundLocked(r.app, true); } getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r); mAm.notifyPackageUse(r.serviceInfo.packageName, PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE); } }
該方法主要作的事情,建立一個ActiveForegroundApp
實例,並把實例加入到smap.mActiveForegroundApps
,
調用requestUpdateActiveForegroundAppsLocked
,設置ServiceRecord的isForeground = true
.
因而可知,全部的前臺服務都會在smap.mActiveForegroundApps
列表中對應一個實例。
requestUpdateActiveForegroundAppsLocked
方法又調用了updateForegroundApps
方法,見下面代碼。
這裏有個關鍵代碼是ide
active.mAppOnTop = active.mShownWhileTop = r.app.uidRecord.curProcState <= ActivityManager.PROCESS_STATE_TOP;
下面會再次提到這段代碼。post
updateForegroundApps
方法。通知欄上面的「running in the background」就是在這個方法裏面去更新的。void updateForegroundApps(ServiceMap smap) { // This is called from the handler without the lock held. ArrayList<ActiveForegroundApp> active = null; synchronized (mAm) { final long now = SystemClock.elapsedRealtime(); long nextUpdateTime = Long.MAX_VALUE; if (smap != null) { for (int i = smap.mActiveForegroundApps.size()-1; i >= 0; i--) { ActiveForegroundApp aa = smap.mActiveForegroundApps.valueAt(i); // ...A lot of code is omitted if (!aa.mAppOnTop) { if (active == null) { active = new ArrayList<>(); } active.add(aa); } } } } final NotificationManager nm = (NotificationManager) mAm.mContext.getSystemService( Context.NOTIFICATION_SERVICE); final Context context = mAm.mContext; if (active != null) { // ...A lot of code is omitted //這裏是更新通知的地方,具體代碼太長,省略掉了。 } else { //若是active爲空,取消掉通知。 nm.cancelAsUser(null, SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES, new UserHandle(smap.mUserId)); } }
遍歷smap.mActiveForegroundApps
列表,判斷列表中的元素,若是其mAppOnTop成員屬性爲false,則加入active列表中。ui
根據active列表,更新notification。this
可見,只有在smap.mActiveForegroundApps
列表中,而且mAppOnTop爲false的前臺服務纔會顯示在通知欄中的「running in the background」中。rest
以上是一個應用啓動一個前臺服務到被顯示在通知欄中的「running in the background」中的代碼上的流程。此外咱們再瞭解些相關的邏輯。code
從上面的分析看出,mAppOnTop的值決定了一個前臺服務是否會被顯示在通知欄的「running in the background」中。mAppOnTop的狀態除了會在建立的時候賦值,還會在另外一個方法中被更新。
每次更新後,若是值有變化,就會調用requestUpdateActiveForegroundAppsLocked
,該方法上面分析過了。htm
void foregroundServiceProcStateChangedLocked(UidRecord uidRec) { ServiceMap smap = mServiceMap.get(UserHandle.getUserId(uidRec.uid)); if (smap != null) { boolean changed = false; for (int j = smap.mActiveForegroundApps.size()-1; j >= 0; j--) { ActiveForegroundApp active = smap.mActiveForegroundApps.valueAt(j); if (active.mUid == uidRec.uid) { if (uidRec.curProcState <= ActivityManager.PROCESS_STATE_TOP) { if (!active.mAppOnTop) { active.mAppOnTop = true; changed = true; } active.mShownWhileTop = true; } else if (active.mAppOnTop) { active.mAppOnTop = false; changed = true; } } } if (changed) { requestUpdateActiveForegroundAppsLocked(smap, 0); } } }
該方法傳入一個uidrecord參數,指定具體的uid及相關狀態。判斷邏輯跟前面setServiceForegroundInnerLocked
方法的邏輯一致。
其中ActivityManager.PROCESS_STATE_TOP
的官方解釋是
Process is hosting the current top activities. Note that this covers all activities that are visible to the user.
意思就是,當前進程的一個activity在棧頂,覆蓋了全部其它activity,用戶能夠真正看到的。
換句話,若是用戶不能直接看到該應用的activity,而且該應用啓動了一個前臺服務,那麼就會被顯示在「running in the background」中。
foregroundServiceProcStateChangedLocked
方法只有一處調用,AMS的updateOomAdjLocked
,該方法的調用地方太多,沒法一一分析。
除了以上兩種狀況(應用在service中調用startForeground
和mAppOnTop
的狀態變動)會觸發該通知的更新外,還有一些其它狀況會觸發更新。
從上面代碼的分析中,咱們知道,觸發更新的地方必需要調用requestUpdateActiveForegroundAppsLocked
方法。
該方法會在ActiveSercices類中的以下幾個方法中調用。
setServiceForegroundInnerLocked
(這個咱們前面分析過)
decActiveForegroundAppLocked
(減少前臺應用)
updateScreenStateLocked
(屏幕狀態變化)
foregroundServiceProcStateChangedLocked
(進程狀態變化)
forceStopPackageLocked
(強制中止應用)
咱們看下其中的decActiveForegroundAppLocked
方法
decActiveForegroundAppLocked
private void decActiveForegroundAppLocked(ServiceMap smap, ServiceRecord r) { ActiveForegroundApp active = smap.mActiveForegroundApps.get(r.packageName); if (active != null) { active.mNumActive--; if (active.mNumActive <= 0) { active.mEndTime = SystemClock.elapsedRealtime(); if (foregroundAppShownEnoughLocked(active, active.mEndTime)) { // Have been active for long enough that we will remove it immediately. smap.mActiveForegroundApps.remove(r.packageName); smap.mActiveForegroundAppsChanged = true; requestUpdateActiveForegroundAppsLocked(smap, 0); } else if (active.mHideTime < Long.MAX_VALUE){ requestUpdateActiveForegroundAppsLocked(smap, active.mHideTime); } } } }
該方法主要是移除前臺service,根據foregroundAppShownEnoughLocked
判斷,是否立刻移除仍是過一段時間移除。
該方法主要在兩個地方調用。一個是在setServiceForegroundInnerLocked
中調用,當應用調用startForeground
,第一個參數設爲0時,會走到這個路徑。
另外一個bringDownServiceLocked
,也就是當綁定到該service的數量減少時,會調用該方法。
若是是必定的話,我想系統也不必再額外顯示一條「running in the background」的通知,列出全部後臺運行的應用了。
因此答案是不必定,雖然在調用startForeground方法時,必需要傳一個notification做爲參數,但依然會有兩種狀況會致使不會在通知欄顯示應用發的通知。