导语
什么是远程view呢?它和远程service一样,RemoteViews可以在其他进程中显示。我们可以跨进程更新它的界面。在Android中,主要有两种场景:通知栏和桌面小部件。
本章先简单介绍通知栏和桌面小部件应用,接着分析RemoteViews内部机制,最后分析RemoteViews的意义并给出一个实例。
主要内容
- RemoteViews的应用
- RemoteViews的内部机制
- RemoteViews的意义
具体内容
RemoteViews的应用
通知栏主要是通过NotificationManager的notify方法实现。桌面小部件是通过APPWidgetProvider来实现。APPWidgetProvider本质是一个广播。RemoteViews运行在系统的SystemServer进程。
RemoteViews在通知栏的应用
我们用到自定义通知,首先要提供一个布局文件,然后通过RemoteViews来加载,可以自定义通知的样式。更新view时,通过RemoteViews提供的一系列方法。如果给一个控件加点击事件,要使用PendingIntent。
RemoteViews在桌面小部件的应用
AppWidgetProvider是实现桌面小部件的类,本质是一个BroadcastReceiver。开发步骤如下:
- 定义小部件界面。代码
- 定义小部件配置信息。代码
- 定义小部件实现类,继承AppWidgetProvider。代码
上面的例子实现了一个简单地桌面小部件,在小部件上显示一张图片,点击后会旋转一周。 - 在AndroidManifest.mxl中声明小部件。
receiver android:name=".MyAppWidgetProvider" >
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider_info" >
</meta-data>
<intent-filter>
<action android:name="com.ryg.chapter_5.action.CLICK" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
</receiver>
第一个action用于识别小部件的单击,第二个action作为小部件的标识必须存在。
AppWidgetProvider除了onUpdate方法,还有一系列方法。这些方法会自动被onReceive方法调用。当广播到来以后,AppWidgetProvider会自动根据广播的action通过onReceive方法分发广播。
- onEnable:该小部件第一次添加到桌面时调用,添加多次只在第一次调用。
- onUpdate:小部件被添加或者每次小部件更新时调用,更新时机由updatePeriodMillis指定,每个周期小部件都会自动更新一次。
- onDeleted:每删除一次桌面小部件都会调用一次。
- onDisabled:最后一个该类型的桌面小部件被删除时调用。
- onReceive:内置方法,用于分发具体事件给以上方法。
PendingIntent概述
PendingIntent表示一种处于待定的状态的intent。典型场景是RemoteViews添加单击事件。通过send和cancel方法来发送和取消待定intent。
PendingIntent支持三种待定意图:
- static PendingIntent:getActivity(Context context, int requestCode, Intent intent, int flags)获得一个PendingIntent,效果相当于Context.startActivity(Intent)。
- static PendingIntent:getService(Context context, int requestCode, Intent intent, int flags)获得一个PendingIntent,效果相当于Context.startService(Intent)。
- static PendingIntent:getBroadcast(Context context, int requestCode, Intent intent, int flags)获得一个PendingIntent,效果相当于Context.sendBroadcast(Intent)。
其中requestCode多数情况下设为0即可,requestCode会影响flags的效果。
PendingIntent的匹配规则:
如果两个PendingIntent,它们内部的Intent相同且requestCode也相同,那这两个PendingIntent就是相同的。
Intent的匹配规则:
如果两个intent的ComponentName和intent-filter相同,那么这两个intent相同。Extras不参与匹配过程。
flags参数的含义:
- FLAG_ONE_SHOT
当前的PendingIntent只能被使用一次,然后就会被自动cancel,如果后续还有相同的PendingIntent,它们的send方法会调用失败。对于通知栏来说,同类的通知只能使用一次,后续的通知将无法打开。 - FLAG_NO_CREATE
当前的PendingIntent不会主动创建,如果当前PendingIntent之前不存在(匹配的PendingIntent),那么获取PendingIntent失败。这个flag很少使用。 - FLAG_CANCEL_CURRENT
当前的PendingIntent如果存在(匹配的PendingIntent),那么它们都会被cancel,然后系统创建一个新的PendingIntent。对于通知栏来说,那些被cancel的消息单击后将无法打开。 - FLAG_UPDATE_CURRENT
当前PendingIntent如果已经存在(匹配的PendingIntent),那么它们都会被更新。即intent中的extras会被替换成最新的。
举例:
在manager.notify(id,notification)中,如果id是常量,那么多次调用notify只能弹出一个通知,后续的通知会把前面的通知完全替代。而如果每次id都不同,那么会弹出多个通知。
如果id每次都不同且PendingIntent不匹配,那么flags不会对通知之间造成干扰。
如果id不同且PendingIntent匹配:
- 如果采用了FLAG_ONE_SHOT标记位,那么后续通知中的PendingIntent会和第一条通知完全一致,包括extras,单击任何一条通知后,剩下的通知均无法再打开,当所有的通知被清除后,会再次重复这一过程。
- 如果采用FLAG_CANCEL_CURRENT,那么只有最新的通知可以打开。
- 如果采用FLAG_UPDATE_CURRENT,那么之前弹出的通知中的PendingIntent会被更新,与最新一条的通知完全一致,包括extras,并且这些通知都可以打开。
RemoteViews的内部机制
构造方法
public RemoteViews(String packageName, int layoutId)
第一个参数是当前应用的包名,第二个参数是待加载的布局文件。
RemoteViews并不支持所有的view类型,支持类型如下:
- Layout:FrameLayout、LinearLayout、RelativeLayout、GridLayout。
- View:AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper,ListView,GridView、StackView、AdapterViewFlipper、ViewStub。
- RemoteViews不支持以上view的子类。
访问RemoteViews的view元素,必须通过一系列set方法完成:
方法名 | 作用 |
---|---|
setTextViewText(int viewId,CharSequence text) | 设置TextView的文本内容 第一个参数是TextView的id 第二个参数是设置的内容。 |
setTextViewTextSize(int viewId, int units, float size) | 设置TextView的字体大小 第二个参数是字体的单位。 |
setTextColor(int viewId, int color) | 设置TextView字体颜色。 |
setImageViewResource(int viewId, int srcId) | 设置ImageView的图片 |
setInt(int viewId,String methodName, int value) | 反射调用View对象的参数类型为Int的方法 比如上述的setImageViewResource的方法内部就是这个方法实现 因为srcId为int型参数。 |
setLong setBoolean | 类似于setInt。 |
setOnClickPendingIntent(int viewId,PendingIntent pendingIntent) | 添加点击事件的方法 |
大部分set方法是通过反射来完成的。
RemoteViews内部机制
通知栏和小组件分别由NotificationManager(NM)和AppWidgetManager(AWM)管理,而NM和AWM通过Binder分别和SystemService进程中的NotificationManagerService以及AppWidgetService中加载的,而它们运行在系统的SystemService中,这就和我们进程构成了跨进程通讯。
首先RemoteViews会通过Binder传递到SystemService进程,因为RemoteViews实现了Parcelable接口,因此它可以跨进程传输,系统会根据RemoteViews的包名等信息拿到该应用的资源;然后通过LayoutInflater去加载RemoteViews中的布局文件。接着系统会对View进行一系列界面更新任务,这些任务就是之前我们通过set来提交的。set方法对View的更新并不会立即执行,会记录下来,等到RemoteViews被加载以后才会执行。
为了提高效率,系统没有直接通过Binder去支持所有的View和View操作。而是提供一个Action概念,Action同样实现Parcelable接口。系统首先将View操作封装到Action对象并将这些对象跨进程传输到SystemService进程,接着SystemService进程执行Action对象的具体操作。远程进程通过RemoteViews的apply方法来进行View的更新操作,RemoteViews的apply方法会去遍历所有的Action对象并调用他们的apply方法。这样避免了定义大量的Binder接口,也避免了大量IPC操作。
apply和reApply的区别在于:apply会加载布局并更新界面,而reApply则只会更新界面。RemoteViews在初始化界面时会调用apply方法,后续更新界面调用reApply方法。
关于单击事件,RemoteViews中只支持发起PendingIntent,不支持onClickListener那种模式。setOnClickPendingIntent用于给普通的View设置单击事件,不能给集合(ListView/StackView)中的View设置单击事件(开销大,系统禁止了这种方式)。如果要给ListView/StackView中的item设置单击事件,必须将setPendingIntentTemplate和setOnClickFillInIntent组合使用才可以。
RemoteViews的意义
当一个应用需要更新另一个应用的某个界面,我们可以选择用AIDL来实现,但如果更新比较频繁,效率会有问题,同时AIDL接口就可能变得很复杂。如果采用RemoteViews就没有这个问题,但RemoteViews仅支持一些常用的View,如果界面的View都是RemoteViews所支持的,那么就可以考虑采用RemoteViews。
网友评论