編輯:關於Android編程
《Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解》
在上一篇文章《Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解》中詳細介紹了NotificationListenerService的使用方法,以及在使用過程中遇到的問題和規避方案。本文主要分析NotificationListenerService實現原理,以及詳細分析在上一篇文章中提到的相關問題和產生的根本原因。在原理分析前,先看看NotificationListenerService涉及到的類以及基本作用,如圖1所示:
圖 1 NLS注冊及回調過程
通過圖1可以看到,整個通知狀態獲取分為三部分:
①. 監聽器注冊;新建一個類NotificationMonitor繼承自NotificationListenerService。
②. 系統通知管理;系統通知管理由NotificationManagerService負責。
③. 通知狀態回調;當系統通知狀態改變之後,NotificationManagerService會通知NotificationListenerService,最後再由NotificationListenerService通知其所有子類。
在整個系統中,通知管理是由NotificationManagerService完成的,NotificationListenerService只是在通知改變時,會獲得相應的通知消息,這些消息最終會回調到NotificationListenerService的所有子類中。
NotificationListenerService雖然繼承自Service,但系統中實際上啟動的是其子類,為了表述方便,後文統一使用NotificationListenerService啟動來指代。其子類的啟動有三個途徑,分別是:開機啟動、接收PACKAGE相關廣播(安裝、卸載等)啟動、SettingsProvider數據變更啟動。
既然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代碼如下:
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 ArrayListtoAdd; synchronized (mNotificationList) { // unbind and remove all existing listeners toRemove = mListeners.toArray(toRemove); toAdd = new ArrayList (); final HashSet newEnabled = new HashSet (); final HashSet newPackages = new HashSet (); // decode the list of components if (flat != null) { String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); for (int i=0; i 在該方法中將獲取系統中所有NotificationListenerService,並進行registerListenerService操作,代碼如下: 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中監聽了以下廣播:
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啟動小結
在系統中實際上運行的是NotificationListenerService的子類,這些子類的啟動方式分為三種:開機啟動時NotificationManagerService初始化回調;接收相關廣播後執行;數據庫變更後執行。這些啟動方式歸根到底還是bindService的操作。
NotificationListenerService調用流程
前面提到了NotificationListenerService的啟動流程,當啟動完成之後就是調用,整個調用流程分為兩種情況,即:新增通知和刪除通知。
新增通知
當系統收到新的通知消息時,會調用NotificationManager的notify方法用以發起系統通知,在notify方法中則調用關鍵方法enqueueNotificationWithTag:
service.enqueueNotificationWithTag(......)這裡的service是INotificationManager的對象,而NotificationManagerService繼承自INotificationManager.Stub。也就是說NotificationManager與NotificationManagerService實際上就是client與server的關系,這裡的service最終是NotificationManagerService的對象。這裡便會跳轉到NotificationManagerService的enqueueNotificationWithTag方法中,實際調用的是enqueueNotificationInternal方法。在該方法中就涉及到Notification的組裝,之後調用關鍵方法notifyPostedLocked():
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方法: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方法中有如下代碼:
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方法返回了,代碼如下:
@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在系統通知的消息傳遞過程中,起到了代理的作用。繼承自NotificationListenerService的類作為client端,真正的server端則是NotificationManagerService,由它負責整個Notification的控制與管理。NotificationManagerService將處理之後的結果通過NotificationListenerService返回給client端,最終各個client端通過onNotificationPosted和onNotificationRemoved方法拿到系統通知狀態變更的相關信息。
NotificationListenerService重點分析
前文分析了整個NotificationListenerService的啟動和調用,通過以上分析可以很清楚的了解NotificationListenerService的工作流程。在上一篇文章《Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解》中,文末分析了在NotificationListenerService在使用過程中的遇到的一些問題,但並沒有深究出現這些問題的根本原因,下文會對這些問題進行詳細分析。
Notification access頁面不存在
當手機上沒有安裝任何使用NotificationListenerService的應用時,系統默認不會顯示Notification access選項。只有手機中安裝了使用NotificationListenerService的應用,才可以在Settings > Security > Notification access 找到對應的設置頁面。在SourceCode/packages/apps/Settings/src/com/android/settings/SecuritySettings.java中可以看到如下初始化代碼:
//... ...省略 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
有很多人在使用getActiveNotifications方法時返回為null,多數情況下是因為在onCreate或者在onBind中調用了getActiveNotifications方法。比如NotificaionMonitor extends NotificationListenerService:
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方法實現,代碼如下:
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使用詳解與原理分析(一)__使用詳解》。
NotificationListenerService失效
如果NotificationMonitor在onCreate或onBind方法中出現crash,則該NotificationMonitor已經失效。就算修改了NotificationMonitor的代碼不會再crash,但NotificationMonitor還是不能收到onNotificationPosted和onNotificationRemoved回調,除非重啟手機。
這個問題是google設計上的缺陷導致,出現NotificationListenerService失效的必要條件: 在NotificationMonitor的onCreate或者onBind中出現異常,導致service crash,也就是說service還沒有完全啟動的情況下出現了異常導致退出。
這裡需要回到NotificationManagerService中,NotificationListenerService的注冊方法registerListenerService中:
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
用來記錄當前正在啟動的服務。在啟動之前會判斷當前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關系類圖
文中圖片資源,免積分下載:戳這裡
Earthquake(地震顯示器) 項目 詳解 環境: Android Studio 0.5.2, Gradle 1.11, kindle f
首先明確一下概念,WebSocket協議是一種建立在TCP連接基礎上的全雙工通信的協議。概念強調了兩點內容:TCP基礎上 全雙工通信那麼什麼是全雙工通信呢? 全雙工就是指
前言 經常接觸Android網絡編程的我們,對於Volley肯定不陌生,但我們不禁要問,對於Volley我們真的很了解嗎?Volley的內部是怎樣實現的?為什麼幾行代碼
微信紅包自打出世以來就極其受歡迎,搶紅包插件可謂紅極一時.今天,我們重新談談搶紅包插件的哪些事兒.本質上,搶紅包插件的原理不難理解,其過程就是在收到紅包時,自動模擬點擊.