RemoteViews的作用是在其他的进程显示并更新View界面的

RemoteViews的内部机制

RemoteViews支持的layout和View:

layout:
FrameLayout  LinearLayout  RelativeLayout  GridLayout
View:
AnalogClock button Chronometer ImageButton ImageView ProgressBar TextView ViewFlipper ListView GridView StackView AdapterViewFlipper ViewStub

上述就是RemoteViews所支持的所有layout和View 如果使用了这些之外的View 将会抛出异常 表明不可使用该View

RemoteViews设置view内容的方法:

由于RemoteViews没有findViewById的方法  因为它是远程的View即使findViewById我们也不知道远程app的资源文件id  所以如果想要更新View的内容 就要使用RemoteViews提供的一系列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) 添加点击事件的方法

上述是常用的一些RemoteViews的方法 还有很多没有列举 但是都是差不多的调用方式 大部分是通过反射调用View的方法

这个过程大体是这个样子的 首先RemoteViews每调用一个set方法都会向自身添加一个Action Action中封装了一些处理布局的方法 也是序列化对象
之后当RemoteViews需要加载的时候会调用自身的apply方法 这个方法会遍历所有的action一个一个执行action的apply方法 在action的apply方法中会反射调用View的方法 从而实现布局的更新

首先我们要知道这是进程间通信 所以一般是基于Binder实现的 在使用RemoteViews的时候 它会通过Binder传递到SystemServer进程 因为RemoteViews实现了Parcelable接口 所以它可以跨进程传输(跨进程传输其实就是序列化和反序列化的过程。。。) 系统会根据RemoteViews中的包名等信息去获取该应用中的资源 然后通过LayoutInFlater加载RemoteViews中的布局 显示出来 然后还会调用RemoteViews中的action的apply方法 更新布局的信息
理论上Binder可以支持View的所有操作 但是那样太麻烦了 需要提供很多Ipc操作 效率势必会降低 所以没有那样实现 所以就使用了Action 在我们通过NotificationManager或者 AppWidgetManager提交更新时 就会一次执行Action
下面我们从源码的角度看看这个过程的实现:
我们分析AppWidget中 更新TextView的方法 setTextViewText的使用过程

RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.my_widght); //创建一个RemoteViews布局remoteViews.setTextViewText(R.id.txt_1, "哈哈哈哈"); //设置远程布局中的TextViewText  appWidgetManager.updateAppWidget(new ComponentName(context,AppWidgetProvider.class),remoteViews); //提交更新

在RemoteViews执行每个set方法的时候 都会向RemoteViews中添加一个Action :

public void setTextViewText(int viewId, CharSequence text) {  //setTextViewText基于setCharSequence反射实现setCharSequence(viewId, "setText", text);}public void setCharSequence(int viewId, String methodName, CharSequence value) {addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));  //添加了Action}private void addAction(Action a) { //添加Action的方法  if (hasLandscapeAndPortraitLayouts()) {throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +" layouts cannot be modified. Instead, fully configure the landscape and" +" portrait layouts individually before constructing the combined layout.");}if (mActions == null) {mActions = new ArrayList<Action>();}mActions.add(a);  //添加action// update the memory usage statsa.updateMemoryUsageEstimate(mMemoryUsageCounter);}

上述代码就向RemoteViews中添加了一个ReflectionAction 它是Action的子类 是用反射调用方法的Action 其中还有TextViewSizeAction、ViewPaddingAction、SetOnClickPendingIntent等Action 我们暂且只分析 ReflectionAction
经过上面的代码分析 我们已经执行了setTextViewText方法 并在RemoteViews中添加了一个ReflectionAction 对象 下一步就是AppWidgetManager提交更新了 提交更新之后RemoteViews便会由Binder跨进程传输到SystemServer进程中
之后在这个进程 RemoteViews会执行它的apply方法或者reapply方法

  /** @hide */public View apply(Context context, ViewGroup parent, OnClickHandler handler) {RemoteViews rvToApply = getRemoteViewsToApply(context); //获得RemoteViews对象  有 横屏和竖屏两种 选择 View result;//.......此处省略一部分代码 LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  //获得inflater 布局加载器// Clone inflater so we load resources from correct context and// we don't add a filter to the static version returned by getSystemService.inflater = inflater.cloneInContext(inflationContext);inflater.setFilter(this);result = inflater.inflate(rvToApply.getLayoutId(), parent, false);  //加载布局  rvToApply.performApply(result, parent, handler);  //分发请求 方法中遍历Action   下面有此方法实现 return result;}

在RemoteViews的apply方法中 主要执行了 这几步
1.获取remoteViews对象
2.获取Inflater布局加载器 服务
3.根据remoteViews对象中的LayoutId和布局加载器 加载布局View —-result
4.执行remoteViews的performApply方法 遍历Action

其中performApply方法的参数中有个parent参数 先前一直不理解 查看注释 Parent that the resulting view hierarchy will be attached to. This method does not attach the hierarchy.
意思也就是 返回的结果的View所依附的父布局 我猜测也就是SysTemServer的那个布局 比如桌面控件 那就是桌面 通知栏 的话 就是通知栏。。 也不知道对不对。。。如果有哪位大哥知道它真正的含义 可以给我留言 小弟万分感谢

对于 performApply方法 我们继续看 :

private void performApply(View v, ViewGroup parent, OnClickHandler handler) {if (mActions != null) {handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;final int count = mActions.size();for (int i = 0; i < count; i++) { //遍历所有的ActionAction a = mActions.get(i);a.apply(v, parent, handler);}}}

这个方法 非常简单 就是把remoteViews中的Action全部取出来 然后遍历执行action的apply方法
需要看的只有action的apply方法了

 @Overridepublic void apply(View root, ViewGroup rootParent, OnClickHandler handler) { //这是ReflectionAction的apply方法final View view = root.findViewById(viewId);if (view == null) return;Class<?> param = getParameterType();if (param == null) {throw new ActionException("bad type: " + this.type);}try {getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));} catch (ActionException e) {throw e;} catch (Exception ex) {throw new ActionException(ex);}}

参数root也就是remoteViews的整体布局 viewId也就是需要更新的子控件 比如如果是setTextViewText 那就是某个Textview了
然后就是根据反射调用TextView的setText方法了 就完成了布局的更新
需要注意的是 这个ReflectionAction 只适用于一个参数的方法 比如setText(String text) 如果是有2个参数的方法就不可以了 比如 setTextSize(int units ,int size) 这时候可以使用TextViewSizeAction实现 对于其他的Action在这我就不一一分析了 大家可以查看RemoteViews的源码 Action都是它的内部类 我们也无法在外部使用

对于单击事件 RemoteViews只支持发起PendingIntent 不支持onclickListener模式 我们还要区分开setOnclickPendingIntent setPendingIntentTemplate setOnClickFillInIntent他们之间的区别和联系
setOnclickPendingIntent 不可以给集合类的控件添加单击事件 因为消耗太大 如果需要给集合类的控件添加 点击事件 需要 setPendingIntentTemplate 和setOnClickFillInIntent配合使用 具体怎么配合可以百度。。我还没用到过。。

RemoteViews的意义

上面我们分析了RemoteViews的内部机制 我们就可以更加灵活的使用它进行一些远程的控件更新了 比如我们需要实现两个进程间的控件远程信息更新 在以往 我们需要用AIDL 或者其他IPC方式 但是我们了解RemoteViews之后完全可以使用它来实现
比如说 我们想实现 一个 2个进程间模拟通知栏 其实很简单 原理如下:
1.创建两个Activity 在2个进程运行 (这里之所以在一个程序中创建2个进程是为了方便。。)
2.在一个Activity中发送一个广播 在广播中put一个remoteViews 因为它是Pracelable对象
3.在另一个Activity中接受这个广播 然后取出remoteViews对象 执行它的apply方法 之后把view添加到这个Activity的父布局中即可

但是上面其实还存在一个问题 那就是如果在两个不同的app中 对于remoteViews传过来的viewId 另一个app中可能没有对应的 id 而为什么在AppWidget中可以呢 我猜测它是在apply之前在app中找到了那个资源 然后加载就没问题了
而我们这 我们需要自行处理一下 我们需要在两个app中事先规定好资源的名字 一致 然后通过名字查找资源的id 然后调用reapply方法加载布局 具体实现如下 :

int layoutId =  getResources().getIdentifier("layout_name","layout",getPackageName());//此方法通过方法寻找资源id
View view = getLayoutInflater().inflate(layoutId,parentView,false);
remoteViews.reapply(this,view);//此处参数和apply不同
parentView.addView(view);

这样就实现了
但是我还有个疑问 reapply不是只会更新界面吗 没有加载界面会不会出错??

这个疑问在晚上。。回来再看代码的时候解开了。。。 原来很简单。。

apply和reapply的区别是什么呢 apply相对于reapply来说 它多了加载布局这一步 我们先来对比一下 reapply和apply的代码

apply:

public View apply(Context context, ViewGroup parent, OnClickHandler handler) {RemoteViews rvToApply = getRemoteViewsToApply(context);View result;//......此处省略一部分代码LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);// Clone inflater so we load resources from correct context and// we don't add a filter to the static version returned by getSystemService.inflater = inflater.cloneInContext(inflationContext);inflater.setFilter(this);result = inflater.inflate(rvToApply.getLayoutId(), parent, false);rvToApply.performApply(result, parent, handler);return result;}

reapply:

 public void reapply(Context context, View v, OnClickHandler handler) {RemoteViews rvToApply = getRemoteViewsToApply(context);// In the case that a view has this RemoteViews applied in one orientation, is persisted// across orientation change, and has the RemoteViews re-applied in the new orientation,// we throw an exception, since the layouts may be completely unrelated.//.....此处省略部分代码 rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);}

我们可以看的出来 不同的就是 apply多了下面这部分代码

 LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);// Clone inflater so we load resources from correct context and// we don't add a filter to the static version returned by getSystemService.inflater = inflater.cloneInContext(inflationContext);inflater.setFilter(this);result = inflater.inflate(rvToApply.getLayoutId(), parent, false);

然后最后的performApply的参数不一样

先看多的这一部分代码 就是通过服务获取布局加载器 然后加载RemoteViews的布局罢了 最后获取这个布局 result 而reapply没有这一步 因为reapply使用的场合通常都是布局已经加载好了 不需要重新加载了

然后看最后的performApply函数

//reapply
rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);//apply
rvToApply.performApply(result, parent, handler);

不同的是父布局的获取不同 一个是getParent方法获取,一个是设置
到这里你可能会问 reapply的父布局如果没有怎么办 那岂不是v.getParent()方法会返回空吗 ,这种情况是可以避免出现的,因为reapply在源码中调用通常会调用在apply之后 也就是布局已经加载完成了 父布局也已经指定了,而如果我们自己写代码 就比如上面我们写的那个代码 我们在reapply外加载布局的时候 也指定了布局的父容器

View view = getLayoutInflater().inflate(layoutId,parentView,false);

parentView就是父容器 所以不需要担心这一点 好了RemoteViews的使用的介绍也差不多完成了
最后它和AIDL的使用时机怎么区别我说一下
如果说两个进程间的布局更新 都是些简单的View 最好使用RemoteViews 因为它内部有Action模式比AIDL效率高 如果布局有自定义的View 那就只能使用AIDL 通信 然后更新布局了

RemoteViews之RemoteViews的内部机制相关推荐

  1. 理解RemoteViews——RemoteViews的内部机制

    RemoteViews的作用时再其他进程中显示并更新View的界面,为了更好地理解它地内部机制,我们先来看一下它的主要功能.首先看一下它的构造方法,这里只介绍一个最常用的构造方法:public Rem ...

  2. Android驱动学习-内部机制_回顾binder框架关键点

    内部机制_回顾binder框架关键点 server注册服务时, 对每个服务都提供不同的ptr/cookie, 在驱动程序里对每个服务都构造一个binder_node, 它也含有ptr/cookie c ...

  3. 【收藏】万字综述,核心开发者全面解读PyTorch内部机制

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:Edward Z.Yang,Pytorch核心开发者 斯坦福大学博 ...

  4. 综述|核心开发者全面解读Pytorch内部机制

    ↑ 点击蓝字 关注视学算法 作者丨Edward Z. Yang 来源丨机器之心 编辑丨极市平台 极市导读 Edward Z. Yang 是PyTorch开源项目的核心开发者之一.在PyTorch纽约聚 ...

  5. 全面解读PyTorch内部机制

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自|深度学习这件小事 斯坦福大学博士生与 Facebook ...

  6. 浅谈ASP.NET的内部机制(一)

    浅谈ASP.NET的内部机制(一) 前言:当一个Http请求发送给一个aspx页面时,服务器进行了哪些操作?又如何来解析这个请求?ASP.NET在接收请求后是怎么运行的,如怎么编译以及怎么样用托管的代 ...

  7. 浅谈ASP.NET内部机制(五)

    浅谈ASP.NET内部机制(五) 前言:本章要谈页面生命周期了,过程挺多的,但是一点都不难.不信可以看看.我尽量的讲的平实一些,而且理解页面的生命周期对喜欢开发自定义控件和组件的朋友是很有帮助的. 系 ...

  8. 【翻译】QEMU内部机制:顶层概览

    系列文章: [翻译]QEMU内部机制:宏观架构和线程模型 [翻译]QEMU内部机制:vhost的架构 [翻译]QEMU内部机制:顶层概览(本文) [翻译]QEMU内部机制:内存 原文地址:http:/ ...

  9. Zookeeper,etcd,consul内部机制和分布式锁和选主实现的比较

    我的另外3篇文章分别介绍了Zookeeper,etcd,consul是如何实现分布式锁和选主的.本文想比较一下Zookeeper.etcd.consul内部机制有哪些不同,他们实现锁和选主的方式相同和 ...

最新文章

  1. EfficientNetV2 笔记
  2. 前端界面的rem适配换算
  3. vtkImageData基本操作
  4. 2019年4月8日 1021. Remove Outermost Parentheses
  5. 关于memecache的使用及清楚示意
  6. 跨过山和大海的地铁外放党们有人管了,明年开始!网友:没收手机么?
  7. 实验一 MATLAB软件的使用
  8. Hulu俱乐部分享之兴趣篇
  9. 如何决定是否参加培训,一个业内人士的推荐
  10. Bing的高级搜索关键字
  11. vip地址能ping不通_ping不通地址
  12. acl审计软件_审计软件有哪些-审计软件的总结分析
  13. springboot为什么返回Whitelabel Error Page
  14. Uber提出基于Metropolis-Hastings算法的GAN改进思想
  15. Element-UI组件之表单Form
  16. linux c++编程教程,Linux下的C++编程入门教程.ppt
  17. adprw指令教程_三菱介绍FX3U新增指令ADPRW
  18. 业余人士必备上网工具- 3721 上网助手 2005
  19. 网站或者APP短信验证码是怎么实现的?
  20. oracle 匿名块 游标,在oracle中的匿名塊中引用通用遊標

热门文章

  1. 我的前端学习资料 (附视频地址)
  2. 清华操作系统笔记4——虚拟内存技术
  3. 特征选择之Relief算法与Relief-F算法
  4. c语言程序24转换12时间,C语言将24小时制转换为12小时制的方法
  5. SCI论文编辑教你如何准备SCI论文和写作 [转]
  6. 如何建立一个网站?规划、设计、目的、原则、宣传(一)
  7. SetFocus 方法
  8. Eclipse开发EJB3
  9. 交易日九点到九点半的挂单撤单以及价格的一点心得
  10. 接入飞书的 ChatGPT 对话机器人,SAM 来了