相信大家如果在项目中使用过插件框架或是对插件框架有一些了解,会知道插件框架有一个很无奈的问题,就是无法让插件自主发送通知,特别是那些自定义RemoteViews的通知.
就连大名鼎鼎的DroidPlugin也在实现中只让插件发送那些使用系统自带布局的通知,可查看具体的实现代码, –蹭个关键字 哈哈
为什么通知不好实现
1. small icon
发送通知需要一个的drawable id,系统在接收到发送通知请求后,会根据包名和resource id检查该icon是否存在,不存在就crash.
而插件的包名是没有存在系统已安装应用列表内的,所以直接crash
有人会说,我hook后使用宿主的包名不就行了,这就又有一个问题,宿主里面不一定存在这个id的drawable,
即使存在,也不太容易保证是你想要的那个id,毕竟id的生成工作是ide来协助完成的
当然我知道有项目真的在自己来控制生成resource id,但这个不是我们今天讨论的话题,就不过于深入了
2. 通知的显示由系统负责
这部分主要包含两个意思:
- 显示的进程是系统进程
- View的构造是由系统来负责
也带来了以下两个问题:
- 无法通过插件框架核心的hook技术来拦截系统实现,在低版本的rom中,甚至在通知显示时,宿主程序都没有启动
- 插件自定义布局系统找不到,也就无从构造
3. 插件通知的点击事件一般直接指向插件,系统在处理这些事件时,因为找不到插件也会引发crash
主要问题是因为点击事件触发后是由系统来处理的,系统处理时,插件甚至宿主都不一定启动,就算启动了,根据插件提供的信息系统也找不到相应的应用的
解决该问题的核心思路
1. 截图
提取插件即将要发送的通知的RemoteViews,转化成View,然后根据通知类型(是否包含bigContentView)截图成一个图片,然后宿主直接发送纯图片通知
2. 事件封包转发,统一处理
对插件通知的点击事件进行二次封包,当用户点击首先接收到消息的是插件框架(宿主),然后插件框架再解包,找出原始的真正意图,再通过插件框架将其跳转指定页面/启动service/发送通知等等
3. 使用占位的方式解决事件点击问题
宿主在发送插件通知的时候(实际是一张图片),如果该通知布局内包含按钮(开始/暂停,跳转之类的),那在该图片的下方or上方指定位置显示一个透明的按钮,参照思路2进行点击事件封装,然后供用户点击
具体的解决步骤
1.Hook系统的NotificationManager
实际是通过getService提取其INotificationManager,然后hook INotificationManager的以下几个方法:
- enqueueNotification
- enqueueNotificationWithTag
- cancelNotification
- cancelNotificationWithTag
- cancelAllNotifications
当然,开始的时候按需hook就成,后期再去完善
2.将插件的RemoteViews转成View
核心方法是RemoteViews的apply方法,但需要关注一个细节:
在使用系统布局进行显示通知的时候,布局中会涉及到large icon和small icon.
然而在apply的时候,如果指定的是resource id,还是会根据包名