Android O新增的一個特性,系統會在通知欄顯示當前在後臺運行的應用,其實際是顯示啟動了前臺服務的應用,並且當前應用的Activity不在前臺。具體我們看下源碼是怎麼實現的。 1 APP調用 或`startForegroundService`啟動一個service. 和`startForegro ...
Android O新增的一個特性,系統會在通知欄顯示當前在後臺運行的應用,其實際是顯示啟動了前臺服務的應用,並且當前應用的Activity不在前臺。具體我們看下源碼是怎麼實現的。
1 APP調用startService
或startForegroundService
啟動一個service.
startService
和startForegroundService
在Android O上主要有兩個區別:
一個是後臺應用無法通過startService
啟動一個服務,而無論前臺應用還是後臺應用,都可以通過startForegroundService
啟動一個服務。
此外Android規定,在調用startForegroundService
啟動一個服務後,需要在服務被啟動後5秒內調用startForeground方法,
否則會結束掉該service並且拋出一個ANR異常。關於前臺應用和後臺應用的規範見官網
2 Service被啟動後,需要調用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的一個類。
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);
}
}
3 調用了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
方法,見下麵代碼。
這裡有個關鍵代碼是
active.mAppOnTop = active.mShownWhileTop =
r.app.uidRecord.curProcState <= ActivityManager.PROCESS_STATE_TOP;
下麵會再次提到這段代碼。
4 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列表中。根據active列表,更新notification。
可見,只有在smap.mActiveForegroundApps
列表中,並且mAppOnTop為false的前臺服務才會顯示在通知欄中的“running in the background”中。
以上是一個應用啟動一個前臺服務到被顯示在通知欄中的“running in the background”中的代碼上的流程。此外我們再瞭解些相關的邏輯。
mAppOnTop的狀態
從上面的分析看出,mAppOnTop的值決定了一個前臺服務是否會被顯示在通知欄的“running in the background”中。mAppOnTop的狀態除了會在創建的時候賦值,還會在另一個方法中被更新。
每次更新後,如果值有變化,就會調用requestUpdateActiveForegroundAppsLocked
,該方法上面分析過了。
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
,該方法的調用地方太多,無法一一分析。
"running in the background"通知的更新。
除了以上兩種情況(應用在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的數量減小時,會調用該方法。