《Android 开发艺术探索》笔记7--RemoteViews的内部机制和意义

2023-11-13

RemoteViews的内部机制和意义思维导图

RemoteViews的内部机制和意义.png

RemoteViews的内部机制

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

最常用的构造函数就是public RemoteViews(String packageName, int layoutId), 注意RemoteViews目前并不能支持所有的View类型, 目前支持如下(不包括其子类):

Layout

FrameLayout, LinearLayout, RelativeLayout, GridLayout

View

TextView, ImageView, ImageButton, Button, AnalogClock, Chronometer, ProgressBar, ViewFlipper, ListView, GridView, StackView, AdapterViewFlipper, ViewStub

RemoteViews没有提供findviewById()方法, 只有一系列的set()方法.

方法名 作用
setTextViewText() 设置TextView的文本
setTextViewSize() 设置TextView的字体大小
setTextColor() 设置TextView的字体颜色
setImageViewResource() 设置imageView的图片资源
setImageViewBitmap() 设置imageView的图片
setInt() 反射调用View对象的参数类型为int的方法
setLong() 反射调用View对象的参数类型为long的方法
setBoolean() 反射调用View对象的参数类型为boolean的方法
setOnClickPendingIntent() 为View添加单击事件, 事件类型只能PendingIntent

RemoteViews的工作流程

通知栏和桌面小部件分别由NotificationManagerAppWidgetManager管理, 而这两个管理者都是通过Binder分别和SystemServer进程中的NotificationManagerService以及AppWidgetService进行通信. 由此可见,通知栏和桌面小部件中的布局文件实际上是在NotificationManagerService以及AppWidgetService中被加载的, 而他们运行在系统的SystemServer中, 这就和我们的进程构成了进程间通信.

最开始RemoteViews会通过Binder传递到SystemServer进程, RemoteViews实现了Parcelable接口. 系统根据RemoteViews中的包名等信息去得到该应用的资源, 然后通过LayoutInflate去加载RemoteViews中的布局文件. 在SystemServer进程中加载后的布局文件是一个普通的View, 只不过相对于我们的进程他是一个RemoteViews而已. 接着系统会对View执行一系列界面更新任务, 这些任务就是之前的设置的set(). set方法对View所做的更新不是立即执行, 在RemoteViews内部会记录所有的更新操作, 具体的执行时机要等到RemoteViews被加载以后才能执行, 这样RemoteViews就可以在SystemServer进程中显示, 这就是我们看到的通知栏或者桌面小部件. 当需要更新RemoteViews时, 我们需要调用set方法并通过NotificationManagerAppWidgetManager来提交更新任务, 具体的更新操作也是在SystemServer进程中完成的.

为什么不支持所有的View和其操作? 因为代价太大, View的方法太多, 另外就是大量的IPC操作会影响效率. 为了解决这个问题, 系统并没有通过Binder直接支持View的跨进程访问, 而是提供了一个Action的概念, Action代表一个View操作, Action同样实现了Parcelable接口. 系统首先将View操作封装到Action对象并将这些对象跨进程传输到远程进程, 接着在远程进程中执行Action对象中的具体操作. 在我们的应用中每调用一次set(), RemoteViews中就会添加一个对应的Action对象, 当我们通过NotificationManagerAppWidgetManager来提交我们的更新时, 这些Action对象就会传输到远程进程并在远程进程中一次执行. 如图

[图片上传失败…(image-f43de0-1599738952325)]

远程进程通过RemoteViews的apply方法来进行View的更新操作, RemoteViews的apply方法内部则会去遍历所有的Action对象并调用他们的apply方法, 具体的View更新操作是由Action对象的apply方法来完成的. 上述做法的好处是显而易见的, 首先不需要定义大量的Binder接口, 其次通过远程进程中批量执行RemoteViews的修改操作从而避免了大量的IPC操作, 这就提高了程序的性能.

接下来从源码角度分析.

首先最长用到的setTextViewText(),源码如下


public void setTextViewText(int viewId, CharSequence text) {

   setCharSequence(viewId, "setText", text);

}

接收的参数比较简单,继续跟进setCharSequence()方法.

public void setCharSequence(int viewId, String methodName, CharSequence value) {

   addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));

}

从这里实现看到, 内部并没有对View进程直接的操作, 而是添加一个ReflectionAction()一个看名字类似反射类型的对象. 接下看addAction()

private void addAction(Action a) {

    //省略部分代码...

    if (mActions == null) {

        mActions = new ArrayList<Action>();

    }

    mActions.add(a);

    // update the memory usage stats

    a.updateMemoryUsageEstimate(mMemoryUsageCounter);

}

这里看到, 在RemoteViews内部有一个mActions成员, 它是一个ArrayList, 外界每调用一次set(), RemoteViews就会为其创建一个Action对象并加入到这个集合中, 这里仅仅将Action对象保存了起来, 并未对View进行实际的操作, 这一点在上面的理论分析中已经提到过.

接下来再看ReflectionAction的实现之前, 先看一下RemoteViewsapply()方法以及Action类的实现.

public View apply(Context context, ViewGroup parent, OnClickHandler handler) {

        RemoteViews rvToApply = getRemoteViewsToApply(context);

        View result;

        final Context contextForResources = getContextForResources(context);

        Context inflationContext = new ContextWrapper(context) {

            @Override

            public Resources getResources() {

                return contextForResources.getResources();

            }

            @Override

            public Resources.Theme getTheme() {

                return contextForResources.getTheme();

            }

            @Override

            public String getPackageName() {

                return contextForResources.getPackageName();

            }

        };

        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;

    }

这段代码首先通过LayoutInflate去加载RemoteViews中的布局文件, RemoteViews中的布局文件可以通过getLayoutId()这个方法获得, 加载完布局文件后会通过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++) {

               Action a = mActions.get(i);

               a.apply(v, parent, handler);

           }

       }

   }

这个实现就是遍历mActions并执行每个Action对象的apply()方法, 这里猜想Action对象的apply方法就是真正操作View的地方.

RemoteViews在通知栏和桌面小部件中的工作过程和上面描述的过程是一致的. 当调用了RemoteViews的set方法时, 并不会立刻更新他们的界面, 而必须要通过NotificationManagernotify方法以及AppWidgetManagerupdateAppWidget才能更新他们的界面. 实际上在AppWidgetManagerupdateAppWidget内部实现中, 他们就是通过RemoteViewsapply以及reapply方法来加载或者更新布局的. applyreApply的区别在于:前者会加载布局并更新界面, 而后者只会更新界面. 通知栏和桌面小部件在初始化界面的时候回调用apply()方法, 而在后续的更新界面时则会调用reapply()方法.

了解了apply()以及reapply()的作用后, 接着看Action的子类具体实现, 先看ReflectionAction的具体实现.

private final class ReflectionAction extends Action {

   //省略部分代码    ...

   String methodName;

   int type;

   Object value;

   ReflectionAction(int viewId, String methodName, int type, Object value) {

       this.viewId = viewId;

       this.methodName = methodName;

       this.type = type;

       this.value = value;

   }

   @Override

   public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {

       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);

       }

   }

    // ...

}

ReflectionAction表示的是一个反射动作, 通过它对View的操作会以反射的方式来调用, 其中getMethod就是根据方法名来得到反射所需要的Method对象. 除了ReflectionAction, 还有其他的Action. 例如: TextViewSizeAction, ViewPaddingAction, SetOnClickPendingIntent等. 看一下TextViewSizeAction

private class TextViewSizeAction extends Action {

   public TextViewSizeAction(int viewId, int units, float size) {

       this.viewId = viewId;

       this.units = units;

       this.size = size;

   }

   @Override

   public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {

       final TextView target = (TextView) root.findViewById(viewId);

       if (target == null) return;

       target.setTextSize(units, size);

   }

   public String getActionName() {

       return "TextViewSizeAction";

   }

   int units;

   float size;

   public final static int TAG = 13;

}

这个类没有使用反射, 因为setTextSize的方法有两个参数,因此无法复用ReflectionAction, 因为这个反射调用只能有一个参数.

关于单击事件, RemoteViews只支持发起PendingIntent,不支持onClickListener()这种模式.

setOnClickPendingIntent,setPendingIntentTemplate,setOnClickFillIntent这三个的区别.

setOnClickPendingIntent: 只支持普通View设置点击事件, 不能给集合(ListView,StackView)中的View设置点击事件,如item. 因为开销比较大, 系统禁止了这种方式. 如果要给集合中的item添加点击事件,则必须使用后两种组合使用才可以.

RemoteViews的意义可以模拟一个通知栏效果并实现跨进程的UI更新

参看文章

《Android 开发艺术探索》书集
《Android 开发艺术探索》 05-理解RemoteViews

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《Android 开发艺术探索》笔记7--RemoteViews的内部机制和意义 的相关文章

  • 简单对话框中的 ViewPager

    我想在对话框中使用 ViewPager 但失败了 这是所有代码 对话框中显示两个片段 Layouts main xml
  • 有没有办法隐藏 TextView 中的文本?

    有没有办法隐藏 TextView 中的部分 但不是全部 文本 我尝试使用 AbsoluteSizeSpan 将大小设置为 0 但这没有任何我看到的视觉效果 你可以将大小设置为 1 但实际上你会得到凹凸不平的线条 而不是可读的文本 很可爱 但
  • Moshi 无法解析 nullable

    你好 希望你能帮助我 使用 kotlin Retrofit2 moshi 我从 https api spacexdata com v3 launches 获取数据并解析它 一切都很顺利 我得到的属性如下 flight number miss
  • 尝试拍摄 https://github.com/appsthatmatter/GraphView 的图表快照时出现 IllegalStateException

    我正在尝试拍摄 GraphView 的快照 但它给出了错误 GraphView 必须在硬件加速模式下使用 我正在使用以下代码来拍摄快照 Bitmap bitmap Bitmap createBitmap view getWidth view
  • Android 无法查找支持版本 27.0.0 的窗口

    更新后supportVersion to 27 0 0仅在 Android 5 0 2 上 应用程序会因以下堆栈跟踪而崩溃 W WindowManager Failed looking up window java lang Illegal
  • 片段开始时显示用于编辑文本的键盘

    当我的片段开始时 我希望我的编辑文本成为焦点 让用户开始输入内容 我可以使用 requestFocus 将其聚焦 但无法显示键盘 我已经尝试过这两种方法 edit EditText view findViewById R id search
  • Android 设备选择器在目标列中显示红色 X

    我最近构建了一个 Android 应用程序 minSdkVersion 为 7 targetSdkVersion 为 10 我现在正在使应用程序兼容平板电脑并添加操作栏 因此 我将 targetSdkVersion 更新为 15 并在项目属
  • 如何模糊视图

    I have a view having different colors I need to blur the background of that view for example There is LinearLayout in wh
  • 如何在应用程序中创建会话对象

    在我的应用程序中 我想创建一个用于登录和注销的会话 我不知道如何使用会话 任何人都可以通过提供一些示例来帮助我 我认为会话对象应该是在应用程序开始运行时声明和初始化的静态对象 我遇到了这个问题 并决定将我的会话对象放入 utils 类中 该
  • Android 生命周期哪个事件在生命周期中只触发一次?

    我读过一些博客并访问了一些网站 我想知道哪个事件在生命周期中只触发了一次 阅读博客后我意识到onCreate 方法在生命周期内仅触发一次 我不知道我是对还是错 现在我的问题是 我想触发任何仅在我更改横向或纵向方向时触发一次的事件 而不是在启
  • 如何从MediaCodec获取解码格式?

    我正在与MediaCodec 我用它来解码 mp4 video MediaCodec 将视频解码为YUV格式 但我需要得到RGBA 一切都很好 但我发现有几种可能的格式 例如YUV420 YUV422等等 因此 据我所知 要进行转换 我需要
  • Android wifi的信号强度[重复]

    这个问题在这里已经有答案了 可能的重复 Android 如何监控WiFi信号强度 https stackoverflow com questions 1206891 android how to monitor wifi signal st
  • 如何修补更新 Android Studio (0.80 -> 0.81)?

    我安装了 Android Studio Beta v0 8 0 并下载了 v0 8 1 因为 IDE 不会自动下载 v0 80 并使用新版本修补 Android Studio 的预览系列自动做到了这一点 从他们的网页 http tools
  • 短信管理器在少于 160 个字符时发送多部分消息

    我编写了一个使用短信管理器的应用程序 我用的方法sendTextMessage 但这行不通 现在我正在使用sendMutlipartTextMessage 这是工作 但当它大约 60 个字符时 它会发送多部分消息 这个是正常的 我读过的所有
  • 图标和导航视图之间的左边距

    我必须在图标和图标之间添加左边距NavigationView 如下图中箭头所示 我知道根据谷歌规范 这个边距必须有16dp但我需要改变它 我努力了
  • Android - 按下后退按钮时停止 AsyncTask 并返回到上一个 Activity

    我有一个 AsyncTask 我希望它在按下后退按钮时停止执行 我还希望应用程序返回到之前显示的 Activity 看来我已经成功停止了任务 但应用程序没有返回到之前的活动 有任何想法吗 这是我的代码的摘录 private class My
  • 新的 Android 项目未创建布局或 Java 文件

    这两天我一直在尝试简单地阅读 Big Nerd Ranch Android 编程书 第一章的前几页 我的问题的要点是 当我创建新的 Android 应用程序时 不会创建布局或 java 文件 我已经从 Android 开发站点安装了 ADT
  • 加载 highchart 时 Android 错误膨胀类

    我正在尝试加载highcharts via Dialog 下面是我的代码 Gradle implementation com highsoft highcharts highcharts 9 0 1 XML
  • 在应用程序的所有活动中重用操作栏

    我创建了一个 MenuActivity 它有一个操作栏和一个拆分操作栏 我想将此操作栏和 splitactionbar 视图用于我的应用程序中的所有活动 我是 android 的新手 所以有人可以逐步指导我 另外 我试图将搜索图标放在操作栏
  • 安装 APK 时出现会话“应用程序”错误

    我在将 Android Studio 1 1 编写的项目导入 Android Studio 2 1 2 时遇到困难 每当在平板电脑上测试应用程序之前构建 gradle 时 我都会收到此错误 下面是错误的屏幕截图 有谁知道是什么问题 我尝试过

随机推荐

  • CTFshow web5 解题思路

    开始将代码誊到visicode 进行审计 审计结果 一共分为四个if 满足四个条件就出现flag 第一个if的意思 判断两个值是否为空值 是空值就报错 第二个 顾名思义 如果v1不是纯字符就输出 v1 error 所以要让v1成为纯字符 第
  • 三十五、android adb命令详解

    cmd常用控制台1 清屏命令 cls2 列出当前目录详细信息 dir3 删除文件 del xxx txt android adb常用命令1 创建sdcardmksdcard 50M D sdcard img gt 创建一张容量为50M的SD
  • 微信小程序文字换行符

    在微信小程序开发中有一个需求是展示长文本 后端返回的数据包含了 n n let str 第一段 n n第二段 如果将这段文字直接赋值
  • Kali Linux-网络安全之-XSS 跨站脚本攻击原理及 DVWA 靶机的搭建

    XSS 跨站脚本攻击 使用 JavaScript 创建 Cookie JavaScript 可以使用 document cookie 属性来创建 读叏 及删除 cookie 例 1 JavaScript 中 创建 cookie 如下所示 d
  • Jupyter Lab入门到精通

    Jupyter Lab Jupyter Notebook Jupyter Lab可以理解成Jupyter Notebook的升级版本 升级增加了很多功能 其支持python R java等多种编程语言及markdown letex等写作语言
  • MATLAB学习笔记:

    MATLAB学习笔记 MATLAB 变量命名规则 变量名区分大小写 变量名长度不超过63位 变量名以字母开头 可以由字母 数字和下划线组成 但不能使用标点 变量名应简洁明了 通过变量名可以只管看出所表示的物理意义 ch5 C textsca
  • Pytorch基础学习(第一章-PyTorch基础概念)

    课程一览表 目录 一 pytorch简介 二 环境配置 1 pycharm 2 annaconda 3 安装pytorch
  • altium designer芯片引脚间距规则过小

    AD中芯片的引脚间距过小 例如stm32这种MCU 引脚又细又密 违反了默认间距规则 如上图所示的16mil 而触发绿色的报错 但是我们又不能因噎废食 而把整个PCB规则间距改大 因此最好的解决方案是 只修改这一个芯片的间距规则 依次点击
  • 云汉芯城js逆向分析-v,t,s参数

    介绍 查看搜索的接口 很明显需要这几个参数 keyword 2N7002 搜索输入的关键词 font ident 945a41f33fc9693c 如下图 第一次访问的页面 返回的html里找 v 1655799627669 时间戳 t 1
  • Android平台GB28181设备接入模块相关博客概览

    Android平台GB28181设备接入模块 可实现不具备国标音视频能力的 Android终端 通过平台注册接入到现有的GB T28181 2016服务 可用于如智能监控 智慧零售 智慧教育 远程办公 生产运输 智慧交通 车载或执法记录仪等
  • 从零开始 verilog 以太网交换机(六)帧处理单元设计与实现

    从零开始 verilog 以太网交换机 六 帧处理单元设计与实现 声明 博主主页 王 嘻嘻的CSDN主页 从零开始 verilog 以太网交换机系列专栏 点击这里 未经作者允许 禁止转载 侵权必删 关注本专题的朋友们可以收获一个经典交换机设
  • 数据结构——栈(stack)

    一 顺序栈 栈 stack 是一种运算受限的线性表 其限制是仅允许在表的一端进行插入和删除运算 这一端被称为栈顶 相对地 把另一端称为栈底 向一个栈插入新元素又称作进栈 入栈或压栈 它是把新元素放到栈顶元素的上面 使之成为新的栈顶元素 从一
  • java中如何从一个url的字符串中提取出ip、port等信息

    欢迎访问个人博客 德鲁大叔撸代码 项目中有一个功能是 把我生成的对账单推送到商户指定的sftp服务器上 要上传文件到sftp那必须的就有以下几个数据 sftp的ip sftp所指向的port sftp的用户名 sftp的密码 指定sftp上
  • 安装HP LaserJet 1320n打印机驱动

    该打印机型号比较老了 不是网络打印机 只能通过并口或USB安装 由于笔记本不带并口 现在台式机基本都不带了 只能选择USB安装 折腾了好久 才找到正确的安装方法 特分享给大家 首先 就是找到正确的驱动 我的笔记本是win8 64bit的 所
  • Ioc容器refresh总结(4)--- Spring源码从入门到精通(三十四 )

    上偏文章介绍了 registerBeanPostProcessor 分别按优先级顺序先注册PriorityOrdered和Ordered接口 第三部注册没有实现接口的beanPostProcessor 最后注册mergedBeanDefin
  • 特征选取1-from sklearn.feature_selection import SelectKBest

    sklearn实战 乳腺癌细胞数据挖掘 博主亲自录制视频 https study 163 com course introduction htm courseId 1005269003 utm campaign commission utm
  • 从入门到入土:[SEED-Lab]-SQL注入攻击

    此博客仅用于记录个人学习进度 学识浅薄 若有错误观点欢迎评论区指出 欢迎各位前来交流 部分材料来源网络 若有侵权 立即删除 本人博客所有文章纯属学习之用 不涉及商业利益 不合适引用 自当删除 若被用于非法行为 与我本人无关 SEED Lab
  • Flex 学习资源

    Action Script 3 0 帮助 http help adobe com zh CN ActionScript 3 0 ProgrammingAS3 Flex 实例 http blog minidx com 2009 04 06 2
  • 计算机文献汇报ppt,常见的研究生文献汇报.ppt

    常见的研究生文献汇报 Example two Fig 9 shows a schematic illustration of the synthesis routes of single molecular nano particles m
  • 《Android 开发艺术探索》笔记7--RemoteViews的内部机制和意义

    RemoteViews的内部机制和意义思维导图 RemoteViews的内部机制 RemoteViews的意义可以模拟一个通知栏效果并实现跨进程的UI更新 参看文章 RemoteViews的内部机制和意义思维导图 RemoteViews的内