开发中经常使用的 Notification 以及 桌面小部件都是讲应用进程的 view 现实在系统进程中,这么神奇的事情是如何实现的,这篇文章会说明白,主要是对 PendingIntent 和 RemoteViews 的使用。
一、PendingIntent
首先,在使用 Notification 时如果我们要为 View 添加点击事件,都会使用到 PendingIntent 类,那么 PendingIntent 是什么,PendingIntent 是一种在远程进程中响应的 Intent,关于 PendingIntent 的 flag 参数有几点需要注意,下面细看
PendingIntent 的 flag
1. PendingIntent.FLAG_ONE_SHOT 这个标记的表现为,如果两个 RemoteViews 使用了同一个 PendingIntent,如果一个 RemoteViews 的点击事件被激活,则 PendingIntent 则会失效,其他的 Remoteviews 的响应事件将会消失,表现为点击没有反应。
2. PendingIntent.FLAG_CANCEL_CURRENT 这个标记表现为,如果两个 RemoteViews 使用的 PendingIntent 是相同的(相同的定义为 Intent 相同,并且 requestCode 相同,并不是同一个),那么之前使用这个 PendingIntent 的会 cancel,第二个的 PendingIntent 为重新创建的,可以正常工作,旧的 RemoteViews 设置的 PendingIntent 会失效
3. PendingIntent.FLAG_UPDATE_CURRENT 这个标记位,只要有相同的 PendingInten 其都会更新
PendingIntent 的主要方法
1. get 系列方法 在 NMS 中注册 PendingIntent,注册之后,PendingIntent 的 send 方法被调用时会执行 PendingIntent 初始化时设定的操作
2. send 方法,激活已注册的 PendingIntent,在远程进程中注册的事件被激活时,则会通过 IPC 调用 PendingIntent 中指定的操作
3. cancel 取消注册的 PendingIntent ,取消之后 send 方法将失效
二、RemoteViews
RemoteViews 继承了 Parcelable 接口,是可以跨进程传递的
创建时需要需要指明 View 的布局 id,以及该布局所在的进程的包名,创建 RemoteViews
调用 RemoteViews.setOnClickPendingIntent 方法,为 RemoteViews 添加点击事件
RemoteViews 使用
本地进程创建 RemoteViews 对象,并在 set 系列方法时创建 Action 对象,并加入 RemoteViews 内部的集合中
通过 NMS 将 RemoteViews 传递到系统进程
系统进程得到 RemoteViews ,根据包名和 layoutId 得到 View,调用 performApply 方法,其中遍历 Action 集合中的 Action 对象并调用其 applay/reApply,为 View 中的子 View 初始化显示,最后再系统进程将 view 显示
1. Action 原理
Action 在构造方法中保存了待操作的 View 的 id,要操作方法名,以及要设置的属性值,需要显示时调用 Action 对象的 applay/reApply 方法通过反射调用相应 id 的控件的对应方法,并将要设置的值传入,实现在远程进程中的设置。
注意,在布局文件中应该指定控件的颜色等信息,如果跨进程,这些如果是默认状态可能效果不同预期,所以需要确定这些信息时要对 RemoteViews 显式设置
2. RemoteViews 单击事件
RemoteViews 中只支持 PendingIntent,不支持 OnClickListener,所以需要为 RemoteViews 设置单机事件时需要通过 setPendingIntent 方法设置添加
如果要给 ListView 添加 item 的点击事件,必须将 setPendingIntentTemplate 和 setOnClickFillIntent 组合使用
三、RemoteViews 的使用及意义
1、Notification 顶部通知
Nofication 在创建时其实就是通过 RemoteViews 来设置要显示的布局,再调用 NotificationManager 的 notify 方法,notify 方法中会通过 IPC 将 RemoteViews 传递到 NotificationManagerService 系统服务中,NMS 中根据 id 和包名得到 layout 布局,在调用 RemoteViews 的 applay/reApplay 方法初始化布局显示,最后将布局显示到屏幕上
2、AppWidget 桌面小部件点击时显示修改
AppWidget 同样使用 RemoteViews 来实现,AppWidgetProvider 是一个广播接收者,运行在应用进程,通过特定的注册方式系统可以将其关联的小部件显示到桌面上并添加单击响应事件。在收到响应时系统会通过广播的形式将对应事件发送到AppWidgetProvider 中,相对于 AppWidgetProvider 桌面小部件所在的系统进程是远程进程,所以要修改桌面小部件的显示时需要通过 RemoteViews 来将需要显示的布局传递到系统进程中
在 AppWidgetProvider 中收到单击事件时,首先构建 RemoteViews ,并通过 set 系列方法设置布局显示以及添加点击响应 PendingIntent,然后调用 AppWidgetManager 的 updateAppWidget 方法。updateAppWidget 该方法中会通过 IPC 调用 AppWidgetManagerService 的 updateAppWidgetProvider 方法将 RemoteViews 传递到系统进程,AppWidgetManagerService 中会根据 RemoteViews 的布局来更新旧的桌面小部件实现单击时桌面小部件显示的变化。