您的位置:首页 > 其它

NotificationListenerService使用小结

2016-03-12 12:10 441 查看
版本: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

跟高版本中出现的多用户机制有关

到此,也明白为什么会报上面的权限问题了。也能根据上面的相关内容,找出合适的解决方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: