前置文章:

《Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解》

转载请务必注明出处:http://blog.csdn.net/yihongyuelan

概况

在上一篇文章《Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解》中详细介绍了NotificationListenerService的使用方法,以及在使用过程中遇到的问题和规避方案。本文主要分析NotificationListenerService实现原理,以及详细分析在上一篇文章中提到的相关问题和产生的根本原因。在原理分析前,先看看NotificationListenerService涉及到的类以及基本作用,如图1所示:


图 1 NLS注册及回调过程

通过图1可以看到,整个通知状态获取分为三部分:

①. 监听器注册;新建一个类NotificationMonitor继承自NotificationListenerService。

②. 系统通知管理;系统通知管理由NotificationManagerService负责。

③. 通知状态回调;当系统通知状态改变之后,NotificationManagerService会通知NotificationListenerService,最后再由NotificationListenerService通知其所有子类。

在整个系统中,通知管理是由NotificationManagerService完成的,NotificationListenerService只是在通知改变时,会获得相应的通知消息,这些消息最终会回调到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 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操作,代码如下:
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_ADDEDIntent.ACTION_PACKAGE_REMOVEDIntent.ACTION_PACKAGE_RESTARTEDIntent.ACTION_PACKAGE_CHANGEDIntent.ACTION_QUERY_PACKAGE_RESTARTIntent.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方法返回了,代码如下:

@Overridepublic 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();}@Overridepublic 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则进行初始化@Overridepublic 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<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关系类图

文中图片资源,免积分下载:戳这里

更多相关文章

  1. Nginx系列教程(六)| 手把手教你搭建 LNMP 架构并部署天空网络电影
  2. Ionic开发遇到的问题和解决方法
  3. Android模块化编程——WebView使用之系统返回键设置
  4. android stdio 设置屏幕旋转后保存数据
  5. Android(安卓)Studio Mac 快捷键
  6. Android通过PendingIntent实现消息通知
  7. 在Android中获取系统正在运行的进程方法
  8. Android——new Canvas(Bitmap)中对canvas和bitmap的理解
  9. Android调用摄像头和相册

随机推荐

  1. Android学习日记(1)
  2. Android中build target,minSdkVersion,targ
  3. 获取Android正在运行的任务和服务
  4. Android SystemUI状态栏添加图标
  5. Android小技巧&Android Studio快捷键(不定
  6. 说说在 Android 如何在全局获取 Context
  7. Android猜牌小游戏(改进版)
  8. Android设置背景图像重复【整理自网络】
  9. Android - 设定更改主Activity
  10. Android中 加载一张大图片Caused by: jav