昨日有消息称,百度移动医疗事业部被整体裁撤。百度移动医疗事业部成立于2015年1月8日,主要业务为联手国内知名医院推出挂号预约等服务。事业部主要产品有百度医生。对此消息,百度方面目前表示不予置评,但是腾讯科技表示此事件为真。
本篇是 王月半子 的第三篇投稿,各方面地讲解了RemoteViews,所以文章篇幅不短,感兴趣的朋友需要仔细读一读了。
王月半子 的博客地址:
http://blog.csdn.net/wrg_20100512
先从表层意思理解 RemoteViews 感觉它是一个view的集合,而且和远程有关系。那事实上它是什么呢?请看官方对它的说明:
从说明可以看出,RemoteViews 是用来描述一个视图的,它描述的这个视图将显示在另外一个进程中,这也就符合了 RemoteViews 中 Remote 这层含义。同时说明里也说了 RemoteViews 提供了一些基本的操作方法来修改它描述的那个视图的内容。听起来它还真像是个“控件”,那它真的是吗?
看一下 RemoteViews 的类继承关系:
从图中发现,RemoteViews 与 View 没有半毛钱的关系,它仅仅就是 Object 的一个子类,实现了 Parcelable 接口(这就为 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 的时候已经介绍了如何使用,但是并不是开发中常用的方式,仅仅是为了说明它相对于传统的跨进程更新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 内部机制,至于上面两个问题我想也不需要解释太多了。
每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。
如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。
欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号: