Android神奇"控件"一一RemoteViews

作者简介 原创微信公众号郭霖 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 最常用的两个场景是NotificationAppWidget小部件,因为这两者的界面都运行在其他进程进程,确切来说它们所属 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》

淘宝链接: https://s.click.taobao.com/t?e=m%3D2%26s%3DgKUfuKdAZKocQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67p2n%2BQBNMyE6Rku8%2Bpj6eJall3bs%2B3NRhNHnsKI%2BqxhyM0iVZhTFBom4YIorMPnmg8G0g2OJi%2FzmXHfenomYtn5EW9vzeG8LzfPUwktUBEmkxg5p7bh%2BFbQ%3D&pvid=10_106.6.161.154_3367_1490163222155

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,373评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,732评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,163评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,700评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,036评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,425评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,737评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,421评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,141评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,398评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,908评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,276评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,907评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,018评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,772评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,448评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,325评论 2 261

推荐阅读更多精彩内容