作者简介 原创微信公众号郭霖 WeChat ID: guolin_blog
本篇是王月半子的第三篇投稿,各方面地讲解了RemoteViews,所以文章篇幅不短,感兴趣的朋友需要仔细读一读了。
王月半子的博客地址:
http://blog.csdn.net/wrg_20100512
RemoteViews是什么
先从表层意思理解 RemoteViews 感觉它是一个view的集合,而且和远程有关系。那事实上它是什么呢?请看官方对它的说明:
从说明可以看出,RemoteViews 是用来描述一个视图的,它描述的这个视图将显示在另外一个进程中,这也就符合了 RemoteViews 中 Remote 这层含义。同时说明里也说了 RemoteViews 提供了一些基本的操作方法来修改它描述的那个视图的内容。听起来它还真像是个“控件”,那它真的是吗?
看一下 RemoteViews 的类继承关系:
从图中发现,RemoteViews 与 View 没有半毛钱的关系,它仅仅就是 Object 的一个子类,实现了 Parcelable 接口(这就为 RemoteViews 能够实现跨进程提供了条件)。所以从严格意义上来说,RemoteViews 并不是一个控件,它仅仅是为生成控件和修改控件属性提供一系列的方法。
总结:RemoteViews 就是为跨进程生成控件和修改控件属性提供一系列方法的一个类。
说了 RemoteViews 是什么之后,咱们来看看为什么要用 RemoteViews!
为什么要用RemoteViews
既然 RemoteViews 是用于跨进程更新UI的,那咱们就来创造这么一个场景:
同一个应用中有两个 Activity,这两个 Activity 分别处在不同的进程中:
MainActivity
TempActivity
其中MainActivity所属的进程为 com.example.bjwangruigang.remoteviewstudy,TempActivity所属的进程为 com.example.bjwangruigang.remoteviewstudy:remote。现在需要通过 TempActivity 来改变 MainActivity 中的视图,也就是实现跨进程更新UI这么一个功能。具体来说就是在 MainActivity 中添加两个 Button。
传统方式实现跨进程更新UI
拿到这个场景需求,结合跨进程和更新UI的知识,有以下几个方案:
1.TempActivity 把要添加的两个 Button 的布局的ID值通过 BroadcastRecriver 发送,在 MainActivity 中注册该广播,同时获取其中的布局ID值,通过 LayoutInflater 来绘制那两个 Button,最后添加到 MainActivity 的布局中去。
2.TempActivity 通过 AIDL 这种方式将要添加的两个 Button 的布局的ID值发送到 AIDLService 中,通过 Handler 来发送消息、处理消息。处理过程同样是通过 LayoutInflater 来绘制那两个 Button,最后添加到 MainActivity 的布局中去。
其实这两种方案大同小异,无非采用的进程间通信方式不同,后续的添加视图是一模一样的。方案一采用广播的形式来进行IPC通信,而方案二则采用AIDL这种相对原生的IPC方式。为了重温AIDL,这里我采用 AIDL 的方式来实现上述效果。
首先建立IViewManager.aidl
rebuild project 让IDE工具自己生成 AIDL接口 对应的Java文件。
建立ViewAIDLService文件
在TempActivity中绑定服务,并在绑定成功后,针对实现的功能调用不同的远程方法。
最终在MainActivity中处理消息,实现功能。(传统的实现方式对应着case 2和case 3)
大功告成,看一下效果吧!
这里我在绑定服务成功之后 ,相继调用了两次远程服务来实现两种远程UI更新(修改 MainActivity 中 TextView 的内容和为 MainActivity 中添加两个 Button)。
那问题来了,如果这时候我们有其他的需求,比如我要为 Button 中修改内容,这时候我们还需要在 IViewManager 添加新的接口,在 ViewAIDLService 实现接口,当然 MainActivity 中对应的代码同样要修改,牵一发而动全身。除此以外,多次 IPC 带来的开销问题不容小觑。
终上所述,传统方式实现跨进程更新UI是可行的,但不得不提有以下弊端:
View 中的方法数比较多,在IPC中需要增加对应的方法比较繁琐。
View 的每一个方法都会涉及到IPC操作,多次IPC带来的开销问题不容小觑。
View 中方法的某些参数可能不支持IPC传输。例如:OnClickListener,它仅仅是个接口没有序列化。
接下来我们来看看 RemoteViews 在实现上述功能有什么优势。
RemoteViews实现跨进程更新UI
RemoteViews 实现跨进程更新UI同样既可以通过 AIDL 也可以使用 BroadcastReceiver,这里为了和传统方式做下对比,只贴出AIDL方式的代码。
首先建立IremoteViewsManager.aidl
rebuild project 让IDE工具自己生成 AIDL接口 对应的java文件。
建立RemoteViewsAIDLService文件。
在 TempActivity 中绑定服务
最终在 MainActivity 中处理消息,实现功能。(代码在上面已经贴出 对应着case 1) 实现效果如下:
细心的同学可能发现,TempActivity 在绑定服务中的代码中似乎为两个 button 做了监听。
是的,这里是对 button 做了监听,妈妈再也不用担心 OnClickListener 不能在 IPC 中传递了。
当然 RemoteViews 的强大之处还不止体现在这,如果想修改 button 中的内容,这时候你也不需要修改 IremoteViewsManager.aidl、RemoteViewsAIDLService 文件啦!你只需在传递 RemoteViews 之前添加一行代码:
remoteViews.setCharSequence(R.id.firstButton,"setText","想改就改");
这里就不贴效果图啦,anyway这都不重要。
最重要的是:整个过程只有一次IPC,只有一次哦,一次哦。
整体来说,RemoteViews 就是为跨进程更新UI而生的,内部封装了多种方法用来跨进程更新UI。但这也不代表 RemoteViews 是宇宙强无敌,因为它也有软肋,它目前支持的布局和View有限:
layout:
FrameLayout LinearLayout RelativeLayout GridLayout
View:
AnalogClock button Chronometer ImageButton ImageView ProgressBar TextView ViewFlipper ListView GridView StackView AdapterViewFlipper ViewStub
不支持自定义View所以传统的方式依旧是有用武之地的。
深入理解RemoteViews
按着是什么、为什么的规矩,接下来就是怎么用啦。其实上面在介绍为什么用 RemoteViews 的时候已经介绍了如何使用,但是并不是开发中常用的方式,仅仅是为了说明它相对于传统的跨进程更新UI的优势在哪。
RemoteViews 最常用的两个场景是Notification和AppWidget小部件,因为这两者的界面都运行在其他进程进程,确切来说它们所属 systemServer 进程,所以 RemoteViews 是它两的不二之选。
那这部分就结合着 AppWidget 使用 RemoteViews,深入学习 RemoteViews 是怎么保证它强大的跨进程更新UI的优势的。
这里需要注意两个问题:
1.RemoteViews为什么可以通过一次IPC实现对多个View的操作。
2. 其他进程怎么获取布局文件。
首先准备 AppWidget 的所有文件:MyAppWidgetProvider、要显示的xml以及向 AndroidManifest.xml 中注册 MyAppWidgetProvider 等等。
AndroidManifest.xml
MyAppWidgetProvider
接下来结合代码来分析 RemoteViews 是怎么发挥它的优势的:
当用户将 AppWidget 拖到桌面上时,MyAppWidgetProvider 继承 AppWidgetProvider 原有的 onReceive 方法,回调其 onUpdate 方法
在 onWidgetUpdate 方法中建立 RemoteViews,之后调用 appWidgetManager 的 updateAppWidget 发起 IPC
这里实例化了 RemoteViews,先看 RemoteViews 的构造函数:
这里我们关注 RemoteViews 的 mLayoutId 成员变量。
之后 RemoteViews 调用了 setOnClickPendingIntent 方法。
remoteViews.setOnClickPendingIntent(R.id.imageView,pendingIntent);
setOnClickPendingIntent 方法在内部利用 viewId, pendingIntent 生成 SetOnClickPendingIntent 对象,并将此对象作为参数传入 addAction 中,这里不难看出 SetOnClickPendingIntent和Action 存在继承或者实现的关系。先看 addAction 的具体逻辑,发现 addAction 中将传入的参数添加至 RemoteViews 的成员变量 mActions 中。
看一下 Action 类:
Action 类为一个抽象类,同时实现了 Parcelable 接口,支持 IPC。唯一的一个抽象方法 apply。
再看涉及到的 Action 的子类 SetOnClickPendingIntent:
RemoteViews 的 setOnClickPendingIntent 方法可以这么理解:将添加监听的一个 View 动作,封装成一个 Action 类,保存在 RemoteViews 的 mActions 中。其实查看 RemoteViews 的每一个 set 方法,不难发现都是把对 View 操作的动作封装成Action类,最终保存在 RemoteViews 的 mActions 中。这个过程可以理解为:
到目前为止发现 RemoteViews 更多承担的是信息的一个载体,这些信息包括:要 显示View的资源ID值、mActions 等等。
接下来来看看 appWidgetManager.updateAppWidget 内部发生了什么:
看到了RemoteException 猜测这里就开始了远程服务的调用,而这个远程服务对象 mService 的类型是 IAppWidgetService。之后由 AppWidgetService 发送消息,AppWidgetHost 监听来自 AppWidgetService 的事件。这其中的细节涉及太多知识点,毕竟要扒的是RemoteViews。这是详细分析AppWidget生成流程的一系列文章:
http://blog.csdn.net/thl789/article/details/7893292
AppWidgetHost 收到 AppWidgetService 发送的消息,创建 AppWidgetHostView,然后通过 AppWidgetService 查询 appWidgetId 对应的 RemoteViews,最后把 RemoteViews 传递给 AppWidgetHostView 去 updateAppWidget。
updateAppWidget 的实现逻辑很好理解(当然这里只是保留了主要的逻辑代码),如果没有加载过 remoteViews 的布局则调用 remoteViews.apply 方法,若加载过了则调用 remoteViews.reapply 方法。
其实这个时候所有的操作已经处于 systemServer 进程中了,所要理解的也就是 remoteViews 的 apply 和 reapply 方法了。由于 apply 比 reapply 方法中多了一道加载布局文件的程序,这里选择分析 apply 的实现过程。
apply 的实现过程如下:
1.通过 RemoteViews 的 getLayoutId 方法获取要显示的资源ID值
2.利用 LayoutInflater 加载要加载的xml文件,生成View。
3.调用 RemoteViews 的 performApply 方法。
performApply 的流程相对简单,就是将前面存入 mActions 中的 Action 遍历取出来,并调用 action 的 apply 方法。接下来再看具体的 Action 的 apply 的方法,就拿上面的 SetOnClickPendingIntent类 来分析这个过程吧!
实现的过程如下:
1.通过 View 的id值获取对应的 view(target)。
2.SetOnClickPendingIntent类 中的成员变量 pendingIntent 生成相应的 OnClickListener。
3.为 target 设置监听。
当然这里就分析了一个相对简单的 Action,其他的 Action 逻辑也是相同的,有的会使用反射技术来修改View的某些属性。
到这里关于 RemoteViews 的学习也就结束了,最后盗用别人的图来进一步解释下 RemoteView 内部机制,至于上面两个问题我想也不需要解释太多了。
完。。。。。。。。。。。。。。。。。。。。。
文章原创作者GuoLin 书籍推荐
郭林大神原创android 书籍:《第一行代码 android》
网友评论