NotificationListenerService使用小结

版本:android5.1.2。  

     近期,做了通知相关的内容。按照任务需求,把状态栏裁剪掉,但对应的通知需要另外进行处理。

     状态栏这一块内容集成在SystemUI这个应用中。故而,我先期研究了一段事件SystemUI,当然只是其中的StatusBar部分。对于这个方面,本人推荐几个Blog,看完之后,可以对大致的框架有个了解。

     《深入理解Android 卷III》第七章 深入理解SystemUI

     Android 4.0 ICS SystemUI浅析——SystemUI启动流程 

     Android 4.0 ICS SystemUI浅析——StatusBar结构分析 

     Android 4.0 ICS SystemUI浅析——StatusBar加载流程分析 

     Android 4.0 ICS SystemUI浅析——StatusBar加载流程之Notification 

     Android 4.0 ICS SystemUI浅析——StatusBar工作流程之时间日期设置


     看完上面的几篇Blog,相信可以详细了解SystemUI的StatusBar,但对于android5.0后,新出现的Heads Up风格,可能不是十分清楚,不过这个不是重点。

     现在了解StatusBar的工作流程,我们可以自己手动写一个类似应用,可以简单实现系统通知数据的获取、处理,加载等。

     在高版本中,提供了NotificationListenerService这个类。网上也有关于这个的讲解,本人也推荐两篇Blog

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

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

   

     看完上面的两篇Blog,根据其对于的样例Demo,能够正常获取系统通知。但是问题来了:

     Que1:

         跟平常在状态栏的下拉卷帘中显示的通知不同,那么该怎样做,才能使得显示相同?

     Que2:

         Notification Access。开发一个应用,用户体验是重中之重,故出现手动授权,是非常low的,那么怎样跨过这个问题?



    Ans1:

        Notification这个类提供了这两个变量contentView、bigContentView :RemoteViews。这两个变量放置的就是状态栏的下拉卷帘中显示的通知内容。对此,我们只需要调用RemoteViews.apply 方法即可,如下所示:

RemoteViews contentViews = notification.contentView;
View view = contentViews.apply(context, glGallery);
        得到的View对象view,我们可以随意的添加到任意一个布局中去。这样我们就可以获取到跟状态栏的下拉卷帘中获取的通知相同效果。


    Ans2:

        在NotificationListenerService这个类提供了registerAsSystemService 这个方法,但是它是@SystemApi,so在Eclipse中开发时找不到,对此,本人只好copy,不过这样又有新问题出现了:不能在Eclipse中编译、运行了。好在这中问题so easy,放在源码环境在编译就能完美解决了。

try {
	registerAsSystemService(mContext,
		new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()), UserHandle.USER_ALL);
} catch (RemoteException e) {

}
        针对UserHandle.USER_ALL,这是在高版本中采用多用户机制的原因。

        在这里得多说一句,继承NotificationListenerService,但本质上还是一个Service,故我们可以通过两种方法启动这个Service。启动这个Service的时候,我们要去调用registerAsSystemService 这个方法。这样,才能正常跨过Notification Access这个问题。

        感觉到此问题似乎都已经解决了。现在测试开始:发现一运行,就game over了。看看它报什么问题:

————————————————————————————————————————————————————————————————————————————>

E/AndroidRuntime( 1444): java.lang.RuntimeException: Unable to create service com.seven.notificationlistenerdemo.NotificationMonitor: java.lang.SecurityException: INotificationManager.registerListener: uid 10055 does not have android.permission.STATUS_BAR_SERVICE.
E/AndroidRuntime( 1444):     at com.seven.notificationlistenerdemo.NotificationMonitor.onCreate(NotificationMonitor.java:91)
W/InputDispatcher(  515): channel '262c61e9 com.example.notificationlistenerdemo/com.seven.notificationlistenerdemo.MainActivity (server)' ~ Consumer closed input channel or an error occurred.  events=0x9
E/InputDispatcher(  515): channel '262c61e9 com.example.notificationlistenerdemo/com.seven.notificationlistenerdemo.MainActivity (server)' ~ Channel is unrecoverably broken and will be disposed!
W/InputDispatcher(  515): Attempted to unregister already unregistered input channel '262c61e9 com.example.notificationlistenerdemo/com.seven.notificationlistenerdemo.MainActivity (server)'
I/ActivityManager(  515): Start proc 1471:com.example.notificationlistenerdemo/u0a55 for service com.example.notificationlistenerdemo/com.seven.notificationlistenerdemo.NotificationMonitor
E/AndroidRuntime( 1471): Process: com.example.notificationlistenerdemo, PID: 1471
E/AndroidRuntime( 1471): java.lang.RuntimeException: Unable to create service com.seven.notificationlistenerdemo.NotificationMonitor: java.lang.SecurityException: INotificationManager.registerListener: uid 10055 does not have android.permission.STATUS_BAR_SERVICE.
E/AndroidRuntime( 1471):     at com.seven.notificationlistenerdemo.NotificationMonitor.onCreate(NotificationMonitor.java:91)

<————————————————————————————————————————————————————————————————————————————

        一下子蒙了,跟这个权限有哪门子的关系。没办法,只能跟源码去了。在registerAsSystemService中调用registerListener方法。这个方法在INotificationManager.stub中声明了,而INotificationManager.stub 跟NotificationManagerService进行了绑定。so 跟进了NotificationManagerService.java


----> NotificationManagerService.java

    在实例化INotificationManager.stub 对象的时候,对registerListener 进行了override。在这个方法中,它首先去调用enforceSystemOrSystemUI,接着才去registerService。很显然,想要的答案就在enforceSystemOrSystemUI 中。so 跟进enforceSystemOrSystemUI。这个方法呢,也是在实例化INotificationManager.stub 对象mService的时候,对其 进行了override。


----> NotificationManagerService.java

private void enforceSystemOrSystemUI(String message) {
    if (isCallerSystem()) return;
        
    getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE, message);
}

    显然一下子就发现了这个权限。不过明显的是isCallerSystem 返回的是false,才去执行下面的内容,需要调用者拥有permission.STATUS_BAR_SERVICE。那什么情况下,isCallerSystem 返回true呢?so 跟进isCallerSystem


----> NotificationManagerService.java

private static boolean isCallerSystem() {
    return isUidSystem(Binder.getCallingUid());
}
    显然还得去跟进isUidSystem


----> NotificationManagerService.java

private static boolean isCallerSystem() {
    return isUidSystem(Binder.getCallingUid());
}
    就是这种情况,一层一层地往下追,然后追到最后都不知道要干嘛了。好头疼的感觉。so Dia是必须的,时序图是必须的。


----> NotificationManagerService.java

private static boolean isUidSystem(int uid) {
    final int appid = UserHandle.getAppId(uid);
    return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
}
    最终的答案要浮出水面了,有点激动啊。只要满足下面三个条件中的一个,就return true

        1、Process.SYSTEM_UID
            Defines the UID/GID under which system code runs.
   

        2、Process.PHONE_UID -->
            Defines the UID/GID under which the telephony code runs.
       上面的这两个与android系统sharedUserId有关。说到底在Manifest.xml 中进行如下设置即可:

android:sharedUserId="android.uid.system"

       3、uid == 0 => USER_OWNER  

           USER_ALL = -1
       跟高版本中出现的多用户机制有关

到此,也明白为什么会报上面的权限问题了。也能根据上面的相关内容,找出合适的解决方法。



你可能感兴趣的:(Android)