理解RemoteViews
RemoteViews表示的是一个View结构,它可以在其他进程中显示,由于它可以在其他进程中显示,为了能够更新它的界面,RemoteViews提供了一组基础的操作用于跨进程更新它的界面。
RemoteViews的应用
实际开发中,主要用在通知栏和桌面小部件的开发。
通知栏主要是通过NotificationManager的notify来实现。它除了默认效果还可以自定义布局。
桌面小部件则是通过AppWidgetProvide来实现的,AppWidgetProvide本质是一个广播。
通知栏和桌面小部件都会用到RemoteViews,他们在更新界面的时候无法像在Activity里面那样去直接更新View。因为二者的界面都在系统的SystemService进程。为了夸进程,RemoteViews提供了一系列的set方法,
并且这些方法只是View全部方法的子集。另外RemoteViews所支持的View也有限。
RemoteViews在通知栏的应用
提供当前应用的包名和布局文件即可创建一个RemoteViews对象
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_simulated_notification);
它的更新View需要通过它提供的一系列的方法来更新View,如设置TextView文本,才用如下:
remoteViews.setTextViewText(R.id.msg, "chapter_5");
如果需要给一个控件加点击事件,则需要使用PendingIntent并通过setOnClickPendingIntent方法来实现。
remoteViews.setOnClickPendingIntent(R.id.item_holder, pendingIntent);
RemoteViews在桌面小部件的应用
桌面小部件的开发步骤
- 定义小部件界面
- 定义小部件配置信息
- 定义小部件的实现类。需要继承AppWidgetProvider
- 在AndroidManifest.xml中申明小部件
AndroidManifest.xml申明中的Action
这是系统规范,作为小部件的标识必须存在,如下:
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
用于识别小部件的单击行为
<action android:name="com.ryg.chapter_5.action.CLICK" />
AppWidgetProvider 除了常用的onUpdate,还有onEnable,onDisable,onDelete以及onReceive。这些方法会在合适的时间调用。
- onEnable:当该窗口小部件第一次添加到桌面时调用该方法,可添加多次但只在第一次调用。
- onUpdate:小部件被添加时或者每次小部件更新时都会调用一次该方法。小部件的更新时间由updatePeriodMillis来指定。每一个周期都会自动更新一次。
- onDelete:删除一次小部件调用一次。
- onDisable:当最后一个该类型的桌面小部件被删除时调用。
- onReceive:这是广播内置方法,用于分发具体事件给其他方法。
PendingIntent概述
PendingIntent表示一种处于Pending状态的意图
因为RemoteViews运行在其他远程进程中。所以设置点击事件,必须使用PendingIntent。PendingIntent通过set和cancel方法来发送和取消待定的Intent
PendingIntent支持三种待定意图,启动Activity,启动Service和发送广播。
getActivity(Context context, int requestCode,Intent intent, int flags)
getService(Context context, int requestCode,Intent intent, int flags)
getBroadcast(Context context, int requestCode,Intent intent, int flags)
其中requestCode表示PendingIntent发送方的请求码。多数情况设置为0即可,requestCode会影响flags的效果。
PendingInetent的匹配规则,即在什么情况下两个PendingIntent相同的。如果两个PendingIntent内部的Intent相同且requestCode相同。那么这两个PendingIntent也是相同的。
内部Intent什么时候相同呢。Intent的匹配规则:如果两个Intent的ComponentName和intent-filter都相同。那么这两个Intent就是相同的。Extras不参与匹配
flags常见的类型有:
- FLAG_ONE_SHOT : 当前描述的PendingIntent只能被使用一次,然后就会被自动Cancel,如果后续还有相同的PendingIntent,那么它们的send方法就会调用失败。对于通知栏来说,如果采用该标记位,那么同类的通知只能使用一次。后续的通知单击后无法打开。
- FLAG_NO_CREATE : 当前描述的PendingIntent不会主动创建。如果当前PendingIntent之前不存在。那么getActivity,getService和getBroadcast方法会直接返回null,这个标记位很少用。
- FLAG_CANCEL_CURRENT : 当前描述的PendingIntent如果已经存在,那么它们都会被cancel,然后系统会创建一个新的PendingIntent。对应通知栏来说,那些被cancel的消息单击后无法打开。
- FLAG_UPDATE_CURRENT : 当前描述的PendingIntent如果已经存在,那么它们会被更新,即是它们的Intent中的Exam会被替换成最新的。
RemoteViews的内部机制
构造方法:public RemoteViews(String packageName,int layoutId),表示当前应用的包名和待加载的布局文件。
RemoteViews并不能支持所有View类型,所支持的类型如下:
Layout: FrameLayout、LinearLayout、RelativeLayout、GridLayout。
View:Button、ImageButton、ImageView、TextView、ListView、GridView、ViewStub等
RemoteViews没有提供findViewById方法,因此无法直接访问里面的View元素,所以必须通过一系列的set方法来完成。
通知栏和桌面小部件分别由NotificationManager和AppWidgetManager管理,它们通过Binder分别和SystemServer进程中的NotificationManagerService以及AppWidgetService进行通信。布局文件是在NotificationManagerService以及AppWidgetService中被加载。而它们运行在SystemService中,形成了夸进程通信的场景。
- RemoteViews会通过Binder传递到SystemServer进程,因为RemoteView实现了Parcelable接口,因此可以夸进程传输,系统会根据RemoteViews的包名等值信息去得到该应用资源。然后会通过LayoutInflater去加载RemoteViews中的布局文件。
- 系统会对View进行一系列界面更新的任务,这些任务就是通过set来提交的。set的方法对View所做的更新不是立即执行的。RemoteViews内部会记录所有更新操作。具体的执行时机等到RemoteViews被加载以后才执行。
- 系统并没有通过Binder去直接支持View的夸进程访问,而是通过Action。每一个Action代表一个View操作。系统将View操作封装到Action对象并将这些对象夸进程运输到远程进程。接着在远程进程执行Action的具体操作。每次调用set,RemoteViews就会添加一个具体的Action对象。接着远程进程中执行Action对象中的具体对象。
上述的好处:不需要定义大量的Binder接口,其次通过在远程进程中批量执行RemoteViews的修改操作,避免大量的IPC操作。
Action对象的apply方法就是真正操作View的地方。
在AppWidgetManager的updateAppWidget的内部实现中,通过apply和reapply加载或者更新界面
它们的区别:apply方法会加载布局并更新界面,而reapply方法则只会更新界面。
setOnClickPendingIntent、setPendingIntentTemplate和setOnClickFillInIntent的区别和联系。setOnClickPendingIntent用于View的点击,但是不能用于集合View(ListView和StackView)的点击事件,因为这样开销太大,系统禁止。如果要用ListView和StatckView的Item添加单击事件,则必须将setPendingIntentTemplate和setOnClickFillInIntent组合使用。