《Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解》java
轉載請務必註明出處:http://blog.csdn.net/yihongyuelan
android
在上一篇文章《Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解》中詳細介紹了NotificationListenerService的使用方法,以及在使用過程當中遇到的問題和規避方案。本文主要分析NotificationListenerService實現原理,以及詳細分析在上一篇文章中提到的相關問題和產生的根本緣由。在原理分析前,先看看NotificationListenerService涉及到的類以及基本做用,如圖1所示:git
圖 1 NLS註冊及回調過程github
經過圖1能夠看到,整個通知狀態獲取分爲三部分:數據庫
①. 監聽器註冊;新建一個類NotificationMonitor繼承自NotificationListenerService。app
②. 系統通知管理;系統通知管理由NotificationManagerService負責。異步
③. 通知狀態回調;當系統通知狀態改變以後,NotificationManagerService會通知NotificationListenerService,最後再由NotificationListenerService通知其全部子類。ide
在整個系統中,通知管理是由NotificationManagerService完成的,NotificationListenerService只是在通知改變時,會得到相應的通知消息,這些消息最終會回調到NotificationListenerService的全部子類中。post
NotificationListenerService雖然繼承自Service,但系統中實際上啓動的是其子類,爲了表述方便,後文統一使用NotificationListenerService啓動來指代。其子類的啓動有三個途徑,分別是:開機啓動、接收PACKAGE相關廣播(安裝、卸載等)啓動、SettingsProvider數據變動啓動。this
既然NotificationListenerService是一個service,那其子類啓動方式天然就是bindService或者startService,在SourceCode/frameworks/base/services/java/com/android/server/NotificationManagerService.java中能夠找到,實際上NotificationListenerService的啓動是經過bindServiceAsUser來實現的,而bindServiceAsUser與bindService做用一致。
由於NotificationListenerService最終是在NotificationManagerService中啓動的,所以當系統在開機第一次啓動時,會進行NotificationManagerService初始化,以後會調用其SystemReady方法,繼而調用rebindListenerServices以及registerListenerService(),最後使用bindServiceAsUser實現NotificationListenerService的啓動。rebindListenerServices代碼以下:
[java] view plaincopy
void rebindListenerServices() {
final int currentUser = ActivityManager.getCurrentUser();
//獲取系統中哪些應用開啓了Notification access
String flat = Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
currentUser);
NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
final ArrayList<ComponentName> toAdd;
synchronized (mNotificationList) {
// unbind and remove all existing listeners
toRemove = mListeners.toArray(toRemove);
toAdd = new ArrayList<ComponentName>();
final HashSet<ComponentName> newEnabled = new HashSet<ComponentName>();
final HashSet<String> newPackages = new HashSet<String>();
// decode the list of components
if (flat != null) {
String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
for (int i=0; i<components.length; i++) {
final ComponentName component
= ComponentName.unflattenFromString(components[i]);
if (component != null) {
newEnabled.add(component);
toAdd.add(component);
newPackages.add(component.getPackageName());
}
}
mEnabledListenersForCurrentUser = newEnabled;
mEnabledListenerPackageNames = newPackages;
}
}
//對全部NotificationListenerService所有unbindService操做
for (NotificationListenerInfo info : toRemove) {
final ComponentName component = info.component;
final int oldUser = info.userid;
Slog.v(TAG, "disabling notification listener for user " + oldUser + ": " + component);
unregisterListenerService(component, info.userid);
}
//對全部NotificationListenerService進行bindService操做
final int N = toAdd.size();
for (int i=0; i<N; i++) {
final ComponentName component = toAdd.get(i);
Slog.v(TAG, "enabling notification listener for user " + currentUser + ": "
+ component);
registerListenerService(component, currentUser);
}
}
在該方法中將獲取系統中全部NotificationListenerService,並進行registerListenerService操做,代碼以下:
[java] view plaincopy
private void registerListenerService(final ComponentName name, final int userid) {
//... ...省略
Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
intent.setComponent(name);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
R.string.notification_listener_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0));
try {
if (DBG) Slog.v(TAG, "binding: " + intent);
//使用bindService啓動NotificationListenerService
if (!mContext.bindServiceAsUser(intent,
new ServiceConnection() {
INotificationListener mListener;
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mNotificationList) {
mServicesBinding.remove(servicesBindingTag);
try {
mListener = INotificationListener.Stub.asInterface(service);
NotificationListenerInfo info = new NotificationListenerInfo(
mListener, name, userid, this);
service.linkToDeath(info, 0);
//service啓動成功以後將相關信息添加到mListeners列表中,後續經過該列表觸發回調
mListeners.add(info);
} catch (RemoteException e) {
// already dead
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Slog.v(TAG, "notification listener connection lost: " + name);
}
},
Context.BIND_AUTO_CREATE,
new UserHandle(userid)))
//... ...省略
}
}
整個啓動流程如圖2所示:
圖 2 NLS開機啓動時序圖
當系統安裝或者卸載應用的時候,也會觸發NotificationListenerService的啓動。當一個使用NotificationListenerService的應用被卸載掉後,須要在Notification access界面清除相應的選項,或者當多用戶切換時,也會更新NotificationListenerService的狀態。在NotificationManagerService中監聽了如下廣播:
[java] view plaincopy
Intent.ACTION_PACKAGE_ADDED
Intent.ACTION_PACKAGE_REMOVED
Intent.ACTION_PACKAGE_RESTARTED
Intent.ACTION_PACKAGE_CHANGED
Intent.ACTION_QUERY_PACKAGE_RESTART
Intent.ACTION_USER_SWITCHED
這些廣播在應用變動時由系統發出,好比安裝、卸載、覆蓋安裝應用等等。當NotificationManagerService接收這些廣播後編會調用rebindListenerServices,以後的流程就與前面同樣。啓動流程以下:
圖 3 NLS廣播啓動
在NotificationManagerService中使用了ContentObserver監聽SettingsProvider數據庫變化,當Notification access有更新時,會更新NotificationListenerService的狀態。例如,當用戶進入Notification access界面,手動開啓或關閉相關應用的Notification access權限時便會觸發這種啓動方式。當數據庫中NotificationListenerService關聯的信息改變後,會觸發ContentObserver的onChange方法,繼而調用update方法更新系統中NotificationListenerService的服務狀態,最後調用到rebindListenerServices中。整個流程以下:
圖 4 NLS數據庫變動啓動
在系統中實際上運行的是NotificationListenerService的子類,這些子類的啓動方式分爲三種:開機啓動時NotificationManagerService初始化回調;接收相關廣播後執行;數據庫變動後執行。這些啓動方式歸根到底仍是bindService的操做。
前面提到了NotificationListenerService的啓動流程,當啓動完成以後就是調用,整個調用流程分爲兩種狀況,即:新增通知和刪除通知。
當系統收到新的通知消息時,會調用NotificationManager的notify方法用以發起系統通知,在notify方法中則調用關鍵方法enqueueNotificationWithTag:
[java] view plaincopy
service.enqueueNotificationWithTag(......)
這裏的service是INotificationManager的對象,而NotificationManagerService繼承自INotificationManager.Stub。也就是說NotificationManager與NotificationManagerService實際上就是client與server的關係,這裏的service最終是NotificationManagerService的對象。這裏便會跳轉到NotificationManagerService的enqueueNotificationWithTag方法中,實際調用的是enqueueNotificationInternal方法。在該方法中就涉及到Notification的組裝,以後調用關鍵方法notifyPostedLocked():
[java] view plaincopy
private void notifyPostedLocked(NotificationRecord n) {
final StatusBarNotification sbn = n.sbn.clone();
//這裏觸發mListeners中全部的NotificationListenerInfo回調
for (final NotificationListenerInfo info : mListeners) {
mHandler.post(new Runnable() {
@Override
public void run() {
info.notifyPostedIfUserMatch(sbn);
}});
}
}
到這裏就開始準備回調了,由於前面通知已經組裝完畢準備顯示到狀態欄了,以後就須要將相關的通知消息告訴全部監聽者。繼續看到notifyPostedIfUserMatch方法:
[java] view plaincopy
public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
//... ...省略
try {
listener.onNotificationPosted(sbn);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
上面的listener對象是NotificationListenerInfo類的全局變量,那是在哪裏賦值的呢?還記得前面註冊NotificationListenerService的時候bindServiceAsUser,其中new了一個ServiceConnection對象,並在其onServiceConnected方法中有以下代碼:
[java] view plaincopy
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mNotificationList) {
mServicesBinding.remove(servicesBindingTag);
try {
//mListener就是NotificationListenerService子類的對象
//service是INotificationListenerWrapper的對象,INotificationListenerWrapper
//繼承自INotificationListener.Stub,是NotificationListenerService的內部類
mListener = INotificationListener.Stub.asInterface(service);
//使用mListener對象生成對應的NotificationListenerInfo對象
NotificationListenerInfo info = new NotificationListenerInfo(
mListener, name, userid, this);
service.linkToDeath(info, 0);
mListeners.add(info);
} catch (RemoteException e) {
// already dead
}
}
}
也就是說在NotificationListenerService啓動並鏈接的時候,將binder對象保存到了NotificationListenerInfo中。這裏就得看看NotificationListenerService的onBind方法返回了,代碼以下:
[java] view plaincopy
@Override
public IBinder onBind(Intent intent) {
if (mWrapper == null) {
mWrapper = new INotificationListenerWrapper();
}
//這裏返回的是INotificationListenerWrapper對象
return mWrapper;
}
private class INotificationListenerWrapper extends INotificationListener.Stub {
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
try {
//onNotificationPosted是抽象方法之一
NotificationListenerService.this.onNotificationPosted(sbn);
} catch (Throwable t) {
Log.w(TAG, "Error running onNotificationPosted", t);
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
try {
//onNotificationRemoved是另外一個抽象方法
NotificationListenerService.this.onNotificationRemoved(sbn);
} catch (Throwable t) {
Log.w(TAG, "Error running onNotificationRemoved", t);
}
}
}
經過以上代碼能夠知道,當在notifyPostedIfUserMatch執行listener.onNotificationPosted方法時,實際上會調用到NotificationListenerService.INotificationListenerWrapper的onNotificationPosted方法。
NotificationListenerService是一個Abstract類,其中的Abstract方法是onNotificationPosted和onNotificationRemoved。當觸發NotificationListenerService.INotificationListenerWrapper的onNotificationPosted方法時,繼續調用了NotificationListenerService.this.onNotificationPosted(sbn)。這樣會繼續調用全部NotificationListenerService子類中的onNotificationPosted方法,系統通知新增的消息便傳到了全部NotificationListenerService中。
從整個流程來看,新增通知的發起點是NotificationManager,處理通知則是由NotificationManagerService完成,傳輸過程是經過NotificationListenerService,最後回調方法是各個繼承自NotificationListenerService的子類。整個過程的調用時序圖以下:
圖 5 onNotificationPosted觸發流程
與"新增通知"相似的流程是"刪除通知",發起點在NotificationManager,以後經由NotificationManagerService處理和NotificationListenerService傳遞,最後到達各個繼承自NotificationListenerService的子類中,只不過最後的處理方法變成了onNotificationRemoved。調用時序圖下:
圖 6 onNotificationRemoved觸發流程
簡單來看,NotificationListenerService在系統通知的消息傳遞過程當中,起到了代理的做用。繼承自NotificationListenerService的類做爲client端,真正的server端則是NotificationManagerService,由它負責整個Notification的控制與管理。NotificationManagerService將處理以後的結果經過NotificationListenerService返回給client端,最終各個client端經過onNotificationPosted和onNotificationRemoved方法拿到系統通知狀態變動的相關信息。
前文分析了整個NotificationListenerService的啓動和調用,經過以上分析能夠很清楚的瞭解NotificationListenerService的工做流程。在上一篇文章《Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解》中,文末分析了在NotificationListenerService在使用過程當中的遇到的一些問題,但並無深究出現這些問題的根本緣由,下文會對這些問題進行詳細分析。
當手機上沒有安裝任何使用NotificationListenerService的應用時,系統默認不會顯示"Notification access"選項。只有手機中安裝了使用NotificationListenerService的應用,才能夠在"Settings > Security > Notification access" 找到對應的設置頁面。在SourceCode/packages/apps/Settings/src/com/android/settings/SecuritySettings.java中能夠看到以下初始化代碼:
[java] view plaincopy
//... ...省略
mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS);
if (mNotificationAccess != null) {
final int total = NotificationAccessSettings.getListenersCount(mPM);
if (total == 0) {
if (deviceAdminCategory != null) {
//若是系統中沒有安裝使用NLS的應用則刪除顯示
deviceAdminCategory.removePreference(mNotificationAccess);
}
} else {
//獲取系統中有多少啓動了Notification access的應用
final int n = getNumEnabledNotificationListeners();
//根據啓用的數量顯示不一樣的Summary
if (n == 0) {
mNotificationAccess.setSummary(getResources().getString(
R.string.manage_notification_access_summary_zero));
} else {
mNotificationAccess.setSummary(String.format(getResources().getQuantityString(
R.plurals.manage_notification_access_summary_nonzero,
n, n)));
}
}
}
//... ...省略
有不少人在使用getActiveNotifications方法時返回爲null,多數狀況下是由於在onCreate或者在onBind中調用了getActiveNotifications方法。好比NotificaionMonitor extends NotificationListenerService:
[java] view plaincopy
public class NotificationMonitor extends NotificationListenerService {
@Override
public void onCreate() {
//getActiveNotifications();
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
getActiveNotifications();
return super.onBind(intent);
}
}
找到NotificationListenerService中的getActiveNotifications方法實現,代碼以下:
[java] view plaincopy
public StatusBarNotification[] getActiveNotifications() {
try {
//getActiveNotifications成功執行的兩個關鍵點:
//1.getNotificationInterface方法返回正常
//2.mWrapper對象不爲null
return getNotificationInterface().getActiveNotificationsFromListener(mWrapper);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
return null;
}
//經過查看能夠知道,getNotificationInterface沒有問題,若是爲null會進行初始化
private final INotificationManager getNotificationInterface() {
if (mNoMan == null) {
mNoMan = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
}
return mNoMan;
}
//若是mWrapper爲null則進行初始化
@Override
public IBinder onBind(Intent intent) {
if (mWrapper == null) {
mWrapper = new INotificationListenerWrapper();
}
return mWrapper;
}
經過上面的代碼能夠知道getActiveNotifications方法調用失敗的緣由:
1. service的生命週期會先從onCraete->onBind逐步執行;
2. 此時調用getActiveNotifications方法會使用NotificationListenerService中的mWrapper對象;
3. mWrapper對象必須在NotificationMonitor完成super.onBind方法以後纔會初始化;
綜上所述,當在onCreate或者onBind方法中使用getActiveNotifications方法時,會致使mWrapper沒有初始化,即mWrapper == null。解決方案能夠在onCreate或者onBind方法中使用handler異步調用getActiveNotification方法,具體可參考《Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解》。
若是NotificationMonitor在onCreate或onBind方法中出現crash,則該NotificationMonitor已經失效。就算修改了NotificationMonitor的代碼不會再crash,但NotificationMonitor仍是不能收到onNotificationPosted和onNotificationRemoved回調,除非重啓手機。
這個問題是google設計上的缺陷致使,出現NotificationListenerService失效的必要條件: 在NotificationMonitor的onCreate或者onBind中出現異常,致使service crash,也就是說service尚未徹底啓動的狀況下出現了異常致使退出。
這裏須要回到NotificationManagerService中,NotificationListenerService的註冊方法registerListenerService中:
[java] view plaincopy
private void registerListenerService(final ComponentName name, final int userid) {
//servicesBindingTag能夠理解爲須要啓動的service的標籤
final String servicesBindingTag = name.toString() + "/" + userid;
//若是mServicesBinding中已經包含正在處理的service則直接return退出
if (mServicesBinding.contains(servicesBindingTag)) {
// stop registering this thing already! we're working on it
return;
}
//將準備啓動的service標籤添加到mServicesBinding中
mServicesBinding.add(servicesBindingTag);
//... ...省略
//使用bindServiceAsUser啓動service
Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
intent.setComponent(name);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
R.string.notification_listener_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0));
try {
if (DBG) Slog.v(TAG, "binding: " + intent);
if (!mContext.bindServiceAsUser(intent,
new ServiceConnection() {
INotificationListener mListener;
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mNotificationList) {
//服務成功啓動以後刪除標籤
mServicesBinding.remove(servicesBindingTag);
try {
mListener = INotificationListener.Stub.asInterface(service);
NotificationListenerInfo info = new NotificationListenerInfo(
mListener, name, userid, this);
service.linkToDeath(info, 0);
mListeners.add(info);
} catch (RemoteException e) {
// already dead
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Slog.v(TAG, "notification listener connection lost: " + name);
}
},
Context.BIND_AUTO_CREATE,
new UserHandle(userid)))
{
//綁定服務失敗後刪除標籤
mServicesBinding.remove(servicesBindingTag);
Slog.w(TAG, "Unable to bind listener service: " + intent);
return;
}
} catch (SecurityException ex) {
Slog.e(TAG, "Unable to bind listener service: " + intent, ex);
return;
}
}
}
當調用registerListenerService方法時,使用了一個mServicesBinding的ArrayList<String>用來記錄當前正在啓動的服務。在啓動以前會判斷當前service是否在mServicesBinding之中,若是是則代表正在執行bindServiceAsUser操做,直接退出,不然就繼續執行bindServiceAsUser流程。調用bindServiceAsUser以前會在mServicesBinding中會添加標籤,當鏈接成功以後也就是onServiceConnected返回後,以及綁定失敗後會在mServicesBinding中刪除標籤。
google這樣設計的目的多是爲了不同一個service屢次啓動,所以在執行bindServiceAsUser以前就打上標籤,當處理完成以後(onServiceConnected回調)就刪掉這個標籤,代表這個service 綁定完成。可是,若是執行bindServiceAsUser以後,NotificationMonitor在onCreate或者onBind的時候crash了,也就是NotificationMonitor尚未完成啓動,所以就不會去調用onServiceConnected方法,並最終致使不會調用 mServicesBinding.remove(servicesBindingTag)方法,從而使得NotificationMonitor的標籤被一致記錄在mServicesBinding中。那麼當下一次想再次註冊該服務的時候,系統發現該服務已經在mServicesBinding中了,因此直接return,後面的bindServiceAsUser就不會被調用了。
雖然代碼已經更新,但service沒法正常啓動,那麼onNotificationPosted和onNotificationRemoved的回調天然就沒法使用,此時的解決辦法就只能重啓手機,清空mServicesBinding的值。
NotificationListenerService在系統通知獲取的流程中,自身並無啓動,而是起到了一個代理的做用,每個繼承自NotificationListenerService的類,當系統通知變化後最終都會收到onNotificationPosted和onNotificationRemoved的回調。
bindService方法的返回與service是否成功啓動無關,所以纔會致使NotificationListenerService失效。
最後再看一下整個NotificationListenerService的關係類圖:
圖 7 NLS關係類圖
示例代碼:
https://github.com/yihongyuelan/NotificationListenerServiceDemo