该篇文章是在公司的一个特别奇葩的需求之下产生的,我们公司的产品需要监听一些新闻app的推送消息,从而进行自己的消息推送。作为一个Android Coder第一反应就是直接用NotificationListenerService进行推送监听不就完了嘛,So Easy。说干就干,百度示例代码,ctrl c+ctrl v一气呵成,run!这不就拿到了吗。然后屁颠的去找产品经理了。产品经理看完头都不抬的说我要的是推送携带的详细参数,不是简单的title 和content这种文字信息。我的天,这个时候我在开始细细琢磨这个需求,这个需求的本质就是获取第三方app通知栏通知中写的详细参数——再直白点就是获取第三方App的通知栏通知携带的Intent。有点方啊,坦白说这种需求不用动脑子都知道Android肯定不会给你这个权限的,要是谁都能随便拿到那那行啊。但是说归说还是要尝试一下。我们都知道Notification所携带的参数都是由Intent封装的所以找到这个Intent就行,而在生成Notification时Intent有封装进了PendingIntent里面,而这个PendingIntent是我们通过NotificationListenerService可以获取道的。所以基本思路就出来了通过PendingIntent获取Intent进而获取Intent携带的参数。
好了,废话不多说,下面进入正文,文章主要会讲到以下几个问题
利用NotificationListenerService监听获取第三App的Notification
获取Notification中的Intent
获取第三方App Intent携带的信息
解析第三方App的Serializable对象解析第三方App的Parcelable对象
这个知识点其实网上有很多教程的无非就是自己写一个Service继承NotificationListenerService,之后就可以重写NotificationListenerService的相关方法然后从中获取到第三方App的通知,这个点是比较简单的稍微贴一下代码就好不再详细说了,如果有什么问题请出门google
这是一个service,只要startService一下,然后在系统中给与相应权限就可以了,这里不再细
表。其中sbn.getNotification().extras这个api获取的只是通知携带的参数,如title,textContent等,和咱们要获取的不是一回事,这里说明一下。
从这开始就是比较重要的点了,通过常规的方法肯定是获取不到PendingIntent里边携带的Intent的,第一个思路就是从源码入手看看Android是如何在从通知跳转到activity时利用PendingIntent获取到Intent的。然后照葫芦画瓢做。好吧说干就干。由于涉及到Intent以及打开activity那么肯定会涉及到AMS。Android studio看AMS的源码有点力不从心啊,在这推荐一个Android在线源码地址
好吧,现在就开始看源码,从PendingIntent和Intent产生关联的地方开始看吧。
跟着方法找下去,发现其实是AMS处理的。
我们接着看AMS的源码
懒得翻源码了,经过我用百度一番google之后,发现答案其实就在PendingIntent里边,PendingIntent有个API getIntent()
获取的就是PendingIntent携带的Intent,简单不,就是这么Easy。
由于打了hide的注解,是不能直接调用的,但是反射处理一下就可以了。处理完之后run起来发现这个api需要系统级的权限。想想也是只有系统才能这么随便的去窥探别人家App的数据。怎么给应用加系统级的权限有两种办法
- 自己做个ROM把自己的应用打包进去成为系统App
- 把手机root,然后把自己的App放在系统App的目录下
难易程度上来讲当然是第二个比较简单,而且有专门的工具,条件只是手机root。这里不再展开细说,因为这篇文章不是主要来讲这个问题的。
好了,现在按着以上说的
- 利用NotificationListenerService获取到想要劫持的通知的PenddingIntent
- 利用反射调用隐藏API获取Intent对象
- 将自己的应用打包,通过相应方式刷成系统App
- 运行,当你监听的App推送一条通知时,在你的代码里应该就能获取这个通知的Intent了。
OK,现在可能有的童靴会说了 Intent都拿到了,还有啥难得。
naiveヽ(ー_ー)ノ下面才正式开始本篇文章干货。
回想一下,大家平时获取Intent携带的信息都是怎么做的
看起来平平无奇,其实这其中是有个隐藏条件的,你要获取一个intent携带的信息,一个必要条件就是你得知道存储这个信息的key值,就是对于第三方App的Intent来说,咱们对他内部的数据信息是一无所知的,所以这就带来了第一问题:如何在不知道key的情况下获取intent内部的数据。
遇到这种问题,第一个思路就是考虑intent数据结构。根据API可以知道intent里边的数据是以键值对的形式进行存储的,方式类似HashMap。HashMap的数据其实存放在内部的哈希表中的,本质就是一个元素为链表的数组,即使咱们不知道key值,直接对这个链表数组进行遍历也是可以拿到HashMap里边所有信息的。Intent同理,我虽然不知道key,但是只要找到内部存储的数据,然后遍历一遍也可以拿到我想要的所有信息。
查看Intent 获取数据的源码
接着看Bundle的getString
其实跟我们想的差不多,Intent的所有数据都是放在Intent内部的mExtras(Bundle)字段的mMap(ArrayMap<String, Object>)字段中。我们只要拿到mMap,遍历然后就可以了。unparcel方法是用来从native层读取数据用来填充mMap用的。
看到这大致的思路其实就有了,先获取Intent的mExtras对象,然后调用mExtras的 unparcel()方法,填充值后,直接遍历mMap,就可以拿到Intent携带的所有信息了。
这两个字段都没办法直接拿到,只能用反射,代码还是比较简单的
到这写个demo赶紧跑一下:起一个应用发一个通知然后用上边的代码,自己监听自己。一气呵成,赶紧run起来。
App A发出一个通知,App B果真正确解析了A的通知中携带的参数。
你以为这就完了?
too young!
当年我也是这么想的,当时我们业务上需要检测二十来个App。App装上之后需要等待被检测App发出通知,所以不能实时观测结果。结果第二天来查看收集结果的日志,发现一堆报错