Android常用的工具“小插件”——Widget机制

Widget俗称“小插件”,是Android系统中一个很常用的工具。比如我们可以在Launcher中添加一个音乐播放器的Widget。

在Launcher上可以添加插件,那么是不是说只有Launcher才具备这个功能呢?

Android系统并没有具体规定谁才能充当“Widget容器”这个角色。它定义了一套完整的Widget添加/移除和显示机制,使得人人都能当“Widget提供者”,人人也都有资格做“Widget容器”。

上面我们提到了“Widget提供者”和“Widget容器”这样的概念,前者如一个天气插件,后者则如Launcher。在Widget机制中,它们都有各自的专有名词(同时也是类名),分别是AppWidgetProvider和AppWidgetHost。除此之外,我们能猜想到系统中还需要一个全局的Widget管理器。类似于WindowManagerService、WallpaperManagerService的命名方式,它叫作AppWidgetService。

在这里插入图片描述

“功能的提供者”——AppWidgetProvider

既然叫作Provider,言下之意就是“功能的提供者”。从Host的角度来说,它没有办法预先知晓用户会添加多少个Widget,也没有办法知晓这些添加的Widget都实现了哪些功能。所以在Host的“世界”里,一个Widget只是一个View——它只需要按照要求进行正确显示即可,具体的功能实现则由AppWidgetProvider来完成。

Host把Widget看成View的一个“变种”。

一个有效的Provider要提供至少以下几方面的内容。

  • AppWidgetProviderInfo

也就是用于描述这个Widget的各种信息,包括它的layout布局、刷新频率以及下面要提到的AppWidgetProvider等。这些信息以XML格式的文件表示,Tag标志为。

  • AppWidgetProvider

既然Widget最终是要被显示在Host中的,那么它的功能实现和普通应用程序就一定会有差异。AppWidgetProvider主要借助于Broadcast事件来对Widget进行“远程更新”。

  • View布局

AppWidgetProviderInfo用于描述这个Widget的整体信息,而这里的Layout则是专门用于描述Widget的“显示部分”(确切地说,是初始化时的显示)。

Provider就是一个BroadcastReceiver。比如我们可以在AndroidManifest.xml中声明以下内容来定义一个AppWidgetProvider:

<receiver android:name="ExampleAppWidgetProvider" ><intent-filter><action android:name="android.appwidget.action.APPWIDGET_UPDATE" /></intent-filter><meta-data android:name="android.appwidget.provider"android:resource="@xml/example_appwidget_info" />
</receiver>

这个receiver要接收的唯一消息,就是APPWIDGET_UPDATE;并且它还需要带有信息明确指明自己是一个"android.appwidget.provider",最后的android:resource即前面说到的AppWidgetProviderInfo。比如:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"android:minWidth="294dp"android:minHeight="72dp"android:updatePeriodMillis="86400000"android:previewImage="@drawable/preview"android:initialLayout="@layout/example_appwidget"    
</appwidget-provider>

这个XML文件的最后一项属性(android:initialLayout)指定了初始的View布局为example_ appwidget,它和我们编写普通应用程序的布局语法一样。

当我们编写一个自己的Widget Provider时,首先要继承自AppWidgetProvider。后者的内部实现并不复杂,它继承自BroadcastReceiver,并在onReceive中将具体事件通过重载函数通知我们的AppWidgetProvider实例:

/*frameworks/base/core/java/android/appwidget/AppWidgetProvider.java*/
public class AppWidgetProvider extends BroadcastReceiver {…public void onReceive(Context context, Intent intent) {String action = intent.getAction();if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {Bundle extras = intent.getExtras();if (extras != null) {int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET _IDS);if (appWidgetIds != null && appWidgetIds.length > 0) {this.onUpdate(context, AppWidgetManager.getInstance(context), appWid getIds);}}}…}…

也就是说,编写一个Widget应该根据需求来重载onReceiver(如果有需要的话),onUpdate,onAppWidgetOptionsChanged,onDeleted,onEnabled以及onDisabled。它们分别会在此Widget被更新、Option改变、被删除等情况下被调用。

不过要特别注意的是,一个Widget是可以有多个具体实例的。比如我们写了一个“天气”插件供用户使用,那么理论上并不限制用户会在Launcher中添加多少个“天气”实例。因而需要有相应的WidgetId来唯一标识每一个实例。

在这里插入图片描述

AppWidgetHost

上一小节我们了解了AppWidgetProvider所要做的工作,接下来再看看Host又是如何配合Provider的。简而言之,Host这个“东道主”需要提供相应的空间供Widget来展现自己的UI界面。打个比方,AppWidgetHost就好比一个展厅,而至于陈列的汽车是大众还是奔驰品牌都是没问题的——取决于Widget本身的意愿。

成为一个AppWidgetHost,它需要解决以下问题。

  • 如何显示Widget的UI界面
  • 如何与AppWidgetProvider通信

一个AppWidgetProvider与外界的接口就是onReceive,然后再细化为onUpdate,onEnable等事件处理。而产生这些事件的根源,除了AppWidgetService这一系统元素外,就是AppWidgetHost了。只不过后者也是要通过前者来发送事件的。
在这里插入图片描述
简图中的Host_Application是指扮演Host角色的应用程序,如Launcher。它在整个Widget机制中只会与AppWidgetManager进行交互而不会直接调用AppWidgetService的接口(这有点类似于ServiceManager.java的作用)。可想而知,AppWidgetManager内部还是要通过间接调用AppWidgetService来实现的。另外每个Host_Application还要持有一个AppWidgetHost,我们可以认为它是Host的代理。

当一个Host_Application创建后,它需要向AppWidgetService注册监听Widget事件,并提供一个callback实现。这个callback实际上继承自IAppWidgetHost.Stub,即一个基于AIDL的BinderServer,这就保证了AppWidgetService在事件发生时可以回调到Host。需要接收的回调事件包括:

updateAppWidget updateAppWidgetView@AppWidgetHostproviderChangedonProviderChanged@AppWidgetHostviewDataChangedviewDataChanged @AppWidgetHost

我们以updateAppWidget为例来分析其内部实现:

/*frameworks/base/core/java/android/appwidget/AppWidgetHost.java*/void updateAppWidgetView(int appWidgetId, RemoteViews views, int userId) {AppWidgetHostView v;synchronized (mViews) {v = mViews.get(appWidgetId);}if (v != null) {v.updateAppWidget(views);}}

当WidgetProvider希望更新Host中的View显示时(比如天气插件更新气温),它会通过AppWidgetManager.updateAppWidget(int appWidgetId,RemoteViews views)来指定新的View样式(RemoteViews)。这个请求最终由AppWidgetService发送给相应的Host来实现,即updateApp WidgetView。

上面代码中的mViews定义如下:

HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>();

它是一个AppWidgetHostView的集合。换句话说,是当前这个Host所包含的所有Widget的View对象。比如在Launcher中用户每添加一个Widget(或者设备刚开机时Launcher自己从保存的配置中读取需要加载显示的Widgets),就会用AppWidgetHost.createView把它加入这个集合中。另外因为Widget数量众多,必须为它们分配一个全局唯一的WidgetId。

Launcher中添加widget的操作过程

首先Host通过AppWidgetManager.getAppWidgetInfo来得到相应WidgetId的Info信息,即我们前一小节中讲到的AppWidgetProviderInfo。接着Host会通过AppWidgetHost.createView产生一个AppWidgetHostView——这个View对应的布局是由前面的initialLayout指定的。后续AppWidget Provider根据实际情况还会通过RemoteViews来实时更新它的Widget显示。

那么,createView都做了哪些工作呢?

/*frameworks/base/core/java/android/appwidget/AppWidgetHost.java*/public final AppWidgetHostView createView(Context context, int appWidgetId,AppWidgetProviderInfo appWidget) {final int userId = mContext.getUserId();AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget);//本地的View对象view.setUserId(userId);view.setOnClickHandler(mOnClickHandler);view.setAppWidget(appWidgetId, appWidget);synchronized (mViews) {mViews.put(appWidgetId, view);}RemoteViews views;try {views = sService.getAppWidgetViews(appWidgetId, userId);//得到该Widget的RemoteViewsif (views != null) {views.setUser(new UserHandle(mContext.getUserId()));}} catch (RemoteException e) {throw new RuntimeException("system server dead?", e);}view.updateAppWidget(views);//通过RemoteViews“搭建”本地的Viewreturn view;}

第一步是要产生一个AppWidgetHostView,默认情况下onCreateView内部只是new了一个AppWidgetHostView对象然后就直接返回。如果读者有特殊需求,可以重载这个函数:

/*frameworks/base/core/java/android/appwidget/AppWidgetHostView.java*/
public class AppWidgetHostView extends FrameLayout {
…

可见,AppWidgetHostView实际上是FrameLayout的扩展子类。而setAppWidget一方面将widgetId与此AppWidgetHostView联系起来,另一方面设置了将要显示的widget的padding值,我们同样可以重载这一实现。

接下来就是widget显示的重点,即我们如何把Widget Provider定义的界面显示到Host_ Application中。

在分析源码前,我们先来打个比方。张三在北京建了一栋别墅,李四看了后很喜欢,于是也想自己在上海建一栋一模一样的。怎么办?显然不可能将张三的别墅直接挪到上海,因为它们是异地的,属于两个不同的“进程空间”。一个可行的办法就是将张三的建筑图纸完完本本地递交给李四,然后李四就可以在他自己的“进程空间”中兴建一栋一模一样的别墅了。虽然砖瓦、水泥可能用的不是一个品牌,但这丝毫不会影响大家认为“这两栋别墅的样式风格是完全一样的”。

Widget的显示也类似。我们需要在另一个进程空间(即Host_Application)中显示自己的View,那么也完全可以把View的“图纸”交给对方——这样对方只要“依葫芦画瓢”,也就不难“还原”出Widget的“真实面目”了。而这张“图纸”,就是RemoteViews:

/*RemoteViews.java*/
public class RemoteViews implements Parcelable, Filter {…

虽然它的名称中也带有“Views”,但实际上没有任何View的影子。它继承自可以跨进程传递的Parcelable类以及对数据进行约束的Filter类。

有了这些基础,我们再回头接着看前面的createView:

views = sService.getAppWidgetViews(appWidgetId);

上面这句代码根据WidgetId来得到一个RemoteViews,它借助于sService即AppWidgetService提供的接口来实现。实际上它只是简单填写了Widget的LayoutId和PackageName等,后面真正构造Widget的UI界面时才会去取“图纸”。

最后调用的updateAppWidget是真正构建widget界面的地方(分段阅读):

public void updateAppWidget(RemoteViews remoteViews) {…boolean recycled = false;View content = null;Exception exception = null;…if (remoteViews == null) {…} else {mRemoteContext = getRemoteContext(remoteViews);int layoutId = remoteViews.getLayoutId();/*Step1. 描述Widget的LayoutId*/…if (content == null) {try {content = remoteViews.apply(mContext, this, mOnClickHandler);/*Step2.创建Widget的View*/} catch (RuntimeException e) {exception = e;}}mLayoutId = layoutId;mViewMode = VIEW_MODE_CONTENT;}…if (!recycled) {prepareView(content);addView(content);/*添加Widget的View到全局管理中*/}…}

小结一下这个函数,简单来讲它做了两件事。

  • 生成一个View(content变量)
    这个View根据推测就是由Widget的“图纸”生成的,因而代表了Widget的UI界面。

  • 将上述View加到AppWidgetHostView中
    AppWidgetHostView是一个FrameLayout,它将content作为子View添加进来。这样当整个View重绘时,Widget的界面自然也就呈现出来了。

来看看上面代码段中apply函数的实现:

  /*frameworks/base/core/java/android/widget/RemoteViews.java*/public View apply(Context context, ViewGroup parent, OnClickHandler handler) {RemoteViews rvToApply = getRemoteViewsToApply(context);View result;Context c = prepareContext(context);LayoutInflater inflater = (LayoutInflater)c.getSystemService(Context.LAYOUT_ INFLATER_ SERVICE);…result=inflater.inflate(rvToApply.getLayoutId(), parent, false);…return result;}

这样程序就按照Widget提供的“图纸”成功地在host进程中构造出本地的View对象了——它会和Host_Application中其他View一起,经过SurfaceFlinger的处理后最终显示到屏幕上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/74481.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023年“羊城杯”网络安全大赛 Web方向题解wp 全

团队名称&#xff1a;ZhangSan 序号&#xff1a;11 不得不说今年本科组打的是真激烈&#xff0c;初出茅庐的小后生没见过这场面QAQ~ D0n’t pl4y g4m3!!! 简单记录一下&#xff0c;实际做题踩坑很多&#xff0c;尝试很多。 先扫了个目录&#xff0c;扫出start.sh 内容如下…

Linux CentOS7 系统中添加用户

在linux centOS7系统中&#xff0c;添加用户是管理员的基本操作。作为学习linux系统的基本操作&#xff0c;对添加用户应该多方面了解。 添加用户的命令useradd&#xff0c;跟上用户名&#xff0c;就可以快速创建一个用户。添加一些选项&#xff0c;可以设置更人性化的用户信息…

【论文阅读】Pay Attention to MLPs

作者&#xff1a;Google Research, Brain Team 泛读&#xff1a;只关注其中cv的论述 提出了一个简单的网络架构&#xff0c;gMLP&#xff0c;基于门控的MLPs&#xff0c;并表明它可以像Transformers一样在关键语言和视觉应用中发挥作用 提出了一个基于MLP的没有self-attentio…

docker 笔记11: Docker容器监控之CAdvisor+InfluxDB+Granfana

1.原生命令 docker stats命令的结果 是什么 2.是什么 容器监控3剑客 CAdvisor监控收集InfluxDB存储数据Granfana展示图表 3.CAdvisor 4.InfluxDB 5.Granfana 6.总结 7.compose容器编排&#xff0c;一套带走 新建目录 7.1新建3件套组合的 docker-compose.yml version: 3.1vo…

网络原理

网络原理 传输层 UDP 特点 特点&#xff1a;无连接&#xff0c;不可靠&#xff0c;面向数据报&#xff0c;全双工 格式 怎么进行校验呢&#xff1f; 把UDP数据报中的源端口&#xff0c;目的端口&#xff0c;UDP报文长度的每个字节&#xff0c;都依次进行累加 把累加结果&a…

人脸识别技术,如何解决学校门禁安全?

在当今社会&#xff0c;学校安全已经成为一个备受关注的议题&#xff0c;而门禁监控系统已经成为学校管理和保障学生安全的重要工具之一。随着社会的不断发展和技术的不断进步&#xff0c;学校不再只是知识传授的场所&#xff0c;它们也成为了数百、数千甚至数万学生和教职员工…

Elasticsearch——Docker单机部署安装

文章目录 1 简介2 Docker安装与配置2.1 安装Docker2.2 配置Docker镜像加速器2.3 调整Docker资源限制 3 准备Elasticsearch Docker镜像3.1 下载Elasticsearch镜像3.2 自定义镜像配置3.3执行Docker Compose 4 运行Elasticsearch容器4.1 创建Elasticsearch容器4.2 修改配置文件4.3…

入门人工智能 —— 使用 Python 进行文件读写,并完成日志记录功能(4)

入门人工智能 —— 使用 Python 进行文件读写&#xff08;4&#xff09; 入门人工智能 —— 使用 Python 进行文件读写打开文件读取文件内容读取整个文件逐行读取文件内容读取所有行并存储为列表 写入文件内容关闭文件 日志记录功能核心代码&#xff1a;完整代码&#xff1a;运…

UE5、CesiumForUnreal实现瓦片坐标信息图层效果

文章目录 1.实现目标2.实现过程2.1 原理简介2.2 cesium-native改造2.3 CesiumForUnreal改造2.4 运行测试3.参考资料1.实现目标 参考CesiumJs的TileCoordinatesImageryProvider,在CesiumForUnreal中也实现瓦片坐标信息图层的效果,便于后面在调试地形和影像瓦片的加载调度等过…

超详细最新PyCharm+Python环境安装,多图,逐步骤

PyCharmPython环境安装 前言一、pycharm下载安装1. 安装地址2. 安装详细步骤 二、Python下载安装1. 安装地址2. 安装详细步骤3. 环境变量忘记添加4. python安装成功测试 三. PyCharm上配置Python总结推荐文章 前言 文章会详细介绍PyCharmPython详细安装步骤&#xff0c;接下来…

node.js笔记

首先&#xff1a;浏览器能执行 JS 代码&#xff0c;依靠的是内核中的 V8 引擎&#xff08;C 程序&#xff09; 其次&#xff1a;Node.js 是基于 Chrome V8 引擎进行封装&#xff08;运行环境&#xff09; 区别&#xff1a;都支持 ECMAScript 标准语法&#xff0c;Node.js 有独立…

网络安全-IP地址信息收集

本文为作者学习文章&#xff0c;按作者习惯写成&#xff0c;如有错误或需要追加内容请留言&#xff08;不喜勿喷&#xff09; 本文为追加文章&#xff0c;后期慢慢追加 IP反查域名 http://stool.chinaz.com/same https://tools.ipip.net/ipdomain.php 如果渗透目标为虚拟主机…

FPGA基本算术运算

FPGA基本算术运算 FPGA基本算术运算1 有符号数与无符号数2 浮点数及定点数I、定点数的加减法II、定点数的乘除法 3 仿真验证i、加减法验证ii、乘除法验证 FPGA基本算术运算 FPGA相对于MCU有并行计算、算法效率较高等优势&#xff0c;但同样由于没有成型的FPU等MCU内含的浮点数运…

合宙Air724UG LuatOS-Air LVGL API控件-图片(Gif)

图片&#xff08;Gif&#xff09; GIF图片显示&#xff0c;core版本号要>3211 示例代码 方法一 -- 创建GIF图片控件 glvgl.gif_create(lvgl.scr_act()) -- 设置显示的GIF图像 lvgl.gif_set_src(g,"/lua/test.gif") -- gif图片居中 lvgl.obj_align(g, nil, lvgl…

GaussDB技术解读系列:高级压缩之OLTP表压缩

8月16日&#xff0c;第14届中国数据库技术大会&#xff08;DTCC2023&#xff09;在北京国际会议中心顺利举行。在GaussDB“五高两易”核心技术&#xff0c;给世界一个更优选择的专场&#xff0c;华为云数据库GaussDB首席架构师冯柯对华为云GaussDB数据库的高级压缩技术进行了详…

Vue3-devtools开发者工具安装方法

因为最近在学习Vue3&#xff0c;但是之前找到的Vue3-Devtools失效了&#xff0c;那就来下载安装下 下载安装 Github下载地址&#xff1a;Vue3-Devtools 这个链接快点:Vue3-Devtools 点击链接后页面如下 点击main选项&#xff0c;下拉列表往下拉&#xff0c;找到你想要的版…

JAVA设计模式第七讲:设计模式在 Spring 源码中的应用

设计模式&#xff08;design pattern&#xff09;是对软件设计中普遍存在的各种问题&#xff0c;所提出的解决方案。本文以面试题作为切入点&#xff0c;介绍了设计模式的常见问题。我们需要掌握各种设计模式的原理、实现、设计意图和应用场景&#xff0c;搞清楚能解决什么问题…

渗透测试漏洞原理之---【业务安全】

文章目录 1、业务安全概述1.1业务安全现状1.1.1、业务逻辑漏洞1.1.2、黑客攻击目标 2、业务安全测试2.1、业务安全测试流程2.1.1、测试准备2.1.2、业务调研2.1.3、业务建模2.1.4、业务流程梳理2.1.5、业务风险点识别2.1.6 开展测试2.1.7 撰写报告 3、业务安全经典场景3.1、业务…

Excel VSTO开发7 -可视化界面开发

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 7 可视化界面开发 前面的代码都是基于插件启动或者退出时&#xff0c;以及Excel Application的相关事件&#xff0c;在用户实际操作…

亚马逊云科技人工智能内容审核服务:大大降低生成不安全内容的风险

生成式人工智能技术发展日新月异&#xff0c;现在已经能够根据文本输入生成文本和图像。Stable Diffusion是一种文本转图像模型&#xff0c;可以创建栩栩如生的图像应用。通过Amazon SageMaker JumpStart&#xff0c;使用Stable Diffusion模型轻松地从文本生成图像。 尽管生成式…