Android 4.4 KitKat icationmanagmanagerservice usage and Principle Analysis (2) _ Principle Analysis
Pre-article:
Android 4.4 KitKat NotificationManagerService usage and Principle Analysis (1) _ detailed usage
Overview
In the previous article "Android 4.4 KitKat NotificationManagerService usage and Principle Analysis (1) _ Usage Details", the usage of icationicationlistenerservice is described in detail, and the problems and solutions encountered during use. This article mainly analyzes the implementation principle of icationicationlistenerservice and analyzes in detail the problems mentioned in the previous article and the root causes. Before analyzing the principles, let's take a look at the classes involved in icationicationlistenerservice and their basic functions, as shown in 1:
Figure 1 NLS registration and callback Process
As shown in figure 1, the entire notification Status is obtained in three parts:
①. Register the listener. Create a New icationicationmonitor class that inherits from icationlistlistenerservice.
②. System notification management; notifmanagmanagerservice is responsible for system notification management.
③. Notification Status callback. When the system Notification status changes, icationicationmanagerservice notifies icationlistenerservice, and then NotificationListenerService notifies all its sub-classes.
In the entire system, notification management is completed by icationicationmanagerservice. icationicationlistenerservice only receives corresponding notification messages when the notification changes. These messages are eventually called back to all subclasses of icationicationlistenerservice.
NotificationListenerService startup
NotificationListenerService inherits from the Service, but the sub-class is actually started in the system. To facilitate the expression, icationicationlistenerservice is used to start NotificationListenerService. The sub-classes can be started in three ways: startup, reception of PACKAGE broadcast (installation, uninstallation, etc.) startup, and SettingsProvider Data Change startup.
Since icationicationlistenerservice is a service, the sub-class startup method is bindService or startService, in SourceCode/frameworks/base/services/java/com/android/server/NotificationManagerService. it can be found in java. In fact, icationicationlistenerservice is started through bindServiceAsUser, and bindServiceAsUser is consistent with bindService.
Start
Because icationicationlistenerservice is eventually started in icationicationmanagerservice, when the system is started for the first time, icationmanagmanagerservice will be initialized, and then its SystemReady method will be called, and rebindListenerServices and registerListenerService () will be called (), finally, bindServiceAsUser is used to start icationicationlistenerservice. The rebindListenerServices code is as follows:
Void rebindListenerServices () {final int currentUser = ActivityManager. getCurrentUser (); // obtain which applications in the system enable Notification access String flat = Settings. secure. getStringForUser (mContext. getContentResolver (), Settings. secure. ENABLED_NOTIFICATION_LISTENERS, currentUser); NotificationListenerInfo [] toRemove = new NotificationListenerInfo [mListeners. size ()]; final ArrayList
ToAdd; 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_icationication_listeners_separator); for (int I = 0; I
In this method, all icationicationlistenerservices in the system are obtained and registerListenerService operations are performed. The Code is as follows:
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_NOTIFICA TION_LISTENER_SETTINGS), 0); try {if (DBG) Slog. v (TAG, binding: + intent); // start icationicationlistenerservice if (! MContext. bindServiceAsUser (intent, new ServiceConnection () {inotiflistener mListener; @ Override public void onServiceConnected (ComponentName, 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); // after the service is successfully started, add the relevant information to the mListeners list. In the future, callback mListeners will be triggered through this list. 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 )))//...... omitted }}
The entire startup process 2 is shown below:
Figure 2 sequence of NLS startup
Broadcast startup
When the system installs or detaches an application, the NotificationListenerService is started. After an application that uses icationicationlistenerservice is uninstalled, you must clear the corresponding options on the Notification access interface or update the NotificationListenerService status when switching between multiple users. Icationicationmanagerservice listens to the following broadcasts:
Intent.ACTION_PACKAGE_ADDEDIntent.ACTION_PACKAGE_REMOVEDIntent.ACTION_PACKAGE_RESTARTEDIntent.ACTION_PACKAGE_CHANGEDIntent.ACTION_QUERY_PACKAGE_RESTARTIntent.ACTION_USER_SWITCHED
These broadcasts are sent by the system during application changes, such as installation, uninstallation, and overwrite application installation. When icationicationmanagerservice receives these broadcasts, it calls rebindListenerServices, and the subsequent process is the same as the previous one. The startup process is as follows:
Figure 3 NLS broadcast startup
Start Database Change
In icationicationmanagerservice, ContentObserver is used to listen for changes to the SettingsProvider database. When Notification access is updated, the NotificationListenerService status is updated. For example, when you enter the Notification access interface and manually enable or disable the Notification access permission of the application, this startup method is triggered. When the information associated with icationicationlistenerservice in the database changes, the onChange method of ContentObserver is triggered, the update method is called to update the service status of icationicationlistenerservice in the system, and then the rebindListenerServices is called. The process is as follows:
Figure 4 NLS database startup change
NotificationListenerService startup Summary
The system actually runs the NotificationListenerService subclass. These subclasses can be started in three ways: icationicationmanagerservice initialization callback at startup; receive related broadcasts and execute them after database changes. In the final analysis, these startup methods are still bindService operations.
NotificationListenerService call Process
As mentioned above, the icationicationlistenerservice startup process is called after it is started. The entire invocation process is divided into two types: Add notification and delete notification.
Add notification
When the system receives a new notification message, it calls icationicationmanager's notify method to initiate a system notification. In the notify method, it calls the key method enqueuenotifwithtag:
service.enqueueNotificationWithTag(......)
The service here is the object of inoicationicationmanager, and icationicationmanagerservice inherits from INotificationManager. Stub. That is to say, the relationship between icationicationmanager and icationmanagmanagerservice is actually the relationship between client and server. The service here is the object of icationicationmanagerservice. Here, we will jump to the enqueuenotifwithtag method of icationicationmanagerservice, and actually call the enqueuenotifinternal method. This method involves Notification assembly, and then calls the key method policypostedlocked ():
Private void policypostedlocked (icationicationrecord n) {final StatusBarNotification sbn = n. sbn. clone (); // This triggers all icationicationlistenerinfo callbacks in mListeners for (final NotificationListenerInfo info: mListeners) {mHandler. post (new Runnable () {@ Override public void run () {info. notifyPostedIfUserMatch (sbn );}});}}
Now you have prepared the callback, because the preceding notifications have been assembled and are ready to be displayed in the status bar. Then, you need to notify all the listeners of the notifications. Continue to see the yypostedifusermatch method:
Public void policypostedifusermatch (StatusBarNotification sbn ){//...... omit try {listener. onicationpostposted (sbn);} catch (RemoteException ex) {Log. e (TAG, unable to listen y listener (posted): + listener, ex );}}
The above listener object is a global variable of the NotificationListenerInfo class. Where is the value assigned? Remember bindServiceAsUser when registering icationicationlistenerservice, where a new ServiceConnection object is added, and the onServiceConnected method contains the following code:
Public void onServiceConnected (ComponentName name, IBinder service) {synchronized (mNotificationList) {mServicesBinding. remove (servicesBindingTag); try {// mListener is the object of the NotificationListenerService subclass. // service is the object of inotiflistlistenerwrapper, and inotiflistlistenerwrapper // inherits from inotiflistener. stub is the internal class mListener = INotificationListener of icationlistlistenerservice. stub. asInterface (service); // use the mListener object to generate the corresponding icationicationlistenerinfo object icationlistlistenerinfo info = new NotificationListenerInfo (mListener, name, userid, this); service. linkToDeath (info, 0); mListeners. add (info);} catch (RemoteException e) {// already dead }}}
That is to say, when icationicationlistenerservice is started and connected, the binder object is saved to icationlistlistenerinfo. Here we have to look at the onBind method returned by icationicationlistenerservice. The Code is as follows:
@ Overridepublic IBinder onBind (Intent intent) {if (mWrapper = null) {mWrapper = new inotiflistlistenerwrapper ();} // The returned inotiflistenerwrapper object return mWrapper ;} private class INotificationListenerWrapper extends INotificationListener. stub {@ Override public void onNotificationPosted (StatusBarNotification sbn) {try {// onNotificationPosted is one of the abstract methods NotificationListenerService. this. onicationicationposted (sbn);} catch (Throwable t) {Log. w (TAG, Error running onNotificationPosted, t) ;}@ Override public void onicationicationremoved (StatusBarNotification sbn) {try {// onNotificationRemoved is another abstract method NotificationListenerService. this. onicationicationremoved (sbn);} catch (Throwable t) {Log. w (TAG, Error running onNotificationRemoved, t );}}}
The code above shows that when the listener. onicationicationposted method is executed in notifyPostedIfUserMatch, The onnotificationlistenerservice. inotiflistlistenerwrapper onicationposted method is actually called.
NotificationListenerService is an Abstract class, where the Abstract methods are onNotificationPosted and onNotificationRemoved. When the onnotificationicationposted method of icationicationlistenerwrapper is triggered, NotificationListenerService. this. onNotificationPosted (sbn) is called ). In this way, the onicationicationposted method of all icationicationlistenerservice sub-classes will be called, and new messages will be sent to all icationlistlistenerservices.
From the perspective of the whole process, the starting point of the new notification is icationicationmanager, the process of processing the notification is completed by icationicationmanagerservice, the transmission process is through icationlistlistenerservice, And the callback method is the subclass inherited from icationlistlistenerservice. The Calling sequence diagram of the entire process is as follows:
Figure 5 onNotificationPosted trigger Process
Delete notification
A process similar to the process of adding a notification is to delete the notification. The process starts at icationicationmanager, and is then transmitted through icationicationmanagerservice and notiflistlistenerservice. Finally, the process is passed to the subclass inherited from icationlistlistenerservice, however, the final processing method becomes onicationicationremoved. Call sequence:
Figure 6 onicationicationremoved trigger Process
NotificationListenerService call Process summary
To put it simply, icationicationlistenerservice plays a role as a proxy in the message transmission process of system notifications. The class inherited from icationicationlistenerservice serves as the client, and the real server is icationicationmanagerservice, which controls and manages the entire Notification. Icationicationmanagerservice returns the processed results to the client through icationicationlistenerservice. Finally, each client obtains information about the system Notification status change through the onicationicationposted and onnotifremoremoved methods.
NotificationListenerService Key Analysis
The previous article analyzed the start and call of the entire icationicationlistenerservice. Through the above analysis, we can clearly understand the workflow of notiflistlistenerservice. In the previous article "Android 4.4 KitKat icationicationmanagerservice usage and Principle Analysis (I) _ Usage Details", some problems encountered during the use of icationicationlistenerservice were analyzed, however, the root cause of these problems is not discussed in detail.
The Notification access page does not exist.
When no application using icationicationlistenerservice is installed on the mobile phone, the Notification access option is not displayed by default. Only applications using icationicationlistenerservice installed on the mobile phone can find the corresponding Settings page in Settings> Security> Notification access. The following initialization code is displayed in SourceCode/packages/apps/Settings/src/com/android/settings/SecuritySettings. java:
// ...... Omitting mNotificationAccess = findPreference (KEY_NOTIFICATION_ACCESS); if (mNotificationAccess! = Null) {final int total = icationicationaccesssetunt. getListenersCount (mPM); if (total = 0) {if (deviceAdminCategory! = Null) {// Delete the display deviceAdminCategory if the NLS application is not installed in the system. removePreference (micationicationaccess);} else {// obtain the number of applications in the system that started Notification access. final int n = getNumEnabledNotificationListeners (); // display different Summary if (n = 0) {mNotificationAccess. setSummary (getResources (). getString (R. string. manage_icationication_access_summary_zero);} else {mNotificationAccess. setSummary (String. format (getResources (). getQuantityString (R. plurals. manage_icationication_access_summary_nonzero, n, n )));}}}//...... omitted
The getActiveNotifications () method returns null.
Many people return null when using the getActiveNotifications method. In most cases, the getActiveNotifications method is called in onCreate or onBind. For example, NotificaionMonitor extends NotificationListenerService:
public class NotificationMonitor extends NotificationListenerService {@Override public void onCreate() { //getActiveNotifications(); super.onCreate();}@Overridepublic IBinder onBind(Intent intent) { getActiveNotifications(); return super.onBind(intent);}}
Find the getActiveNotifications method implementation in icationicationlistenerservice. The Code is as follows:
Public StatusBarNotification [] getActiveNotifications () {try {// two key points for successful getActiveNotifications execution: // 1. the geticationicationinterface method returns normal // 2. the mWrapper object is not null return getNotificationInterface (). getActiveNotificationsFromListener (mWrapper);} catch (android. OS. remoteException ex) {Log. v (TAG, Unable to contact notification manager, ex);} return null;} // you can check whether the geticationicationinterface is correct, if it is null, private final INotificationManager getNotificationInterface () {if (mNoMan = null) {mNoMan = INotificationManager will be initialized. stub. asInterface (ServiceManager. getService (Context. NOTIFICATION_SERVICE);} return mNoMan;} // if mWrapper is null, initialize @ Overridepublic IBinder onBind (Intent intent) {if (mWrapper = null) {mWrapper = new INotificationListenerWrapper ();} return mWrapper ;}
The code above shows the reason why the getActiveNotifications method fails to be called:
1. The service life cycle will be executed step by step from onCraete-> onBind;
2. Call the getActiveNotifications method to use the mWrapper object in icationicationlistenerservice;
3. The mWrapper object must be initialized after icationicationmonitor completes the super. onBind method;
To sum up, when the getActiveNotifications method is used in the onCreate or onBind method, mWrapper is not initialized, that is, mWrapper = null. The solution can use handler in the onCreate or onBind method to call the getActiveNotification method asynchronously. For details, see Android 4.4 KitKat icationicationmanagerservice usage and Principle Analysis (1) _ usage details.
NotificationListenerService invalid
If icationicationmonitor appears crash in the onCreate or onBind method, the NotificationMonitor has expired. Even if the NotificationMonitor code is modified, the NotificationMonitor still cannot receive the onicationpostposted and onNotificationRemoved callbacks unless you restart your phone.
This problem is caused by a design defect on google and is necessary to invalidate icationicationlistenerservice: An exception occurs in onCreate or onBind of icationicationmonitor, resulting in service crash, that is to say, if the service is not fully started, an exception occurs and the service exits.
Return to icationicationmanagerservice. The registration method of NotificationListenerService is registerListenerService:
Private void registerListenerService (final ComponentName name, final int userid) {// servicesBindingTag can be understood as the final String servicesBindingTag = name of the service to be started. toString () +/+ userid; // if mServicesBinding already contains a service being processed, return and exit if (mServicesBinding. contains (servicesBindingTag) {// stop registering this thing already! We're working on it return;} // Add the service tag to be started to mServicesBinding. add (servicesBindingTag );//...... omit // use bindServiceAsUser to start service Intent intent = new Intent (icationicationlistenerservice. SERVICE_INTERFACE); intent. setComponent (name); intent. putExtra (Intent. EXTRA_CLIENT_LABEL, R. string. notification_listener_binding_label); intent. putExtra (Intent. EXTRA_CLIENT_INTENT, PendingInte Nt. getActivity (mContext, 0, new Intent (Settings. action_icationication_listener_settings), 0); try {if (DBG) Slog. v (TAG, binding: + intent); if (! MContext. bindServiceAsUser (intent, new ServiceConnection () {inotiflistener mListener; @ Override public void onServiceConnected (ComponentName, IBinder service) {consumer (micationicationlist) {// Delete the mServicesBinding tag after the service is successfully started. 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) {// Delete the mServicesBinding label after binding fails. 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 ;}}}
When the registerListenerService method is called, an mServicesBinding ArrayList is used.
Used to record the currently starting service. Before starting the service, the system checks whether the current service is in mServicesBinding. If yes, it indicates that the bindServiceAsUser operation is being executed, and the system exits directly. Otherwise, the bindServiceAsUser process continues. Before calling bindServiceAsUser, tags will be added to mServicesBinding. After the connection is successful, that is, after onServiceConnected is returned, and after binding fails, tags will be deleted from mServicesBinding.
Google is designed to avoid multiple startup of the same service. Therefore, tags are added before bindServiceAsUser is executed. After the processing is complete (onServiceConnected callback), the tag is deleted, indicates that the service binding is complete. However, after bindServiceAsUser is executed, icationicationmonitor crash in onCreate or onBind, that is, icationicationmonitor is not started yet, so it will not call the onServiceConnected method, and eventually will not call mServicesBinding. remove (servicesBindingTag) method, so that the icationicationmonitor tag is recorded in mServicesBinding. Then when you want to register the service again, the system finds that the service is already in mServicesBinding. Therefore, if you directly return the service, the bindServiceAsUser will not be called.
Although the Code has been updated, but the service cannot be started normally, the onicationicationposted and onNotificationRemoved Callbacks are naturally unavailable. In this case, the solution is to restart the mobile phone and clear the mServicesBinding value.
Summary
NotificationListenerService does not start itself in the process of obtaining system notifications, but acts as a proxy. Every class inherited from icationicationlistenerservice, after a system notification changes, the system will eventually receive the onicationicationposted and onNotificationRemoved callbacks.
The return of the bindService method is irrelevant to whether the service is successfully started. Therefore, icationicationlistenerservice becomes invalid.
Finally, let's look at the graph of the entire NotificationListenerService link class:
Figure 7 NLS relationship class diagram
Image Resources in the text, point-free download: click here