Android 小组件 AppWidgetProvider

2023-09-22 13:53:52

一、相关文档
二、小组件是什么?
三、AppWidget 核心类 AppWidgetProvider 源码解读和原理分析
     1、先看 AppWidgetProvider 源码
     2、AppWidgetProvider 回调方法分析
          onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
          onReceive(Context context, Intent intent)
     3、开发自己的小组件时,写 AppWidgetProvider 实现类,有没有必要重写     onReceive(Context context, Intent intent) 方法?
四、开发自己的小组件时,必须的配合和常用方法
     1、编写实现类,继承 AppWidgetProvider
     2、定义基本配置文件
     3、在清单文件中注册 AppWidgetProvider
     4、创建布局文件
     5、更复杂的配置,创建配置 Activity,创建集合布局等,暂不介绍了,用得少。

一、相关文档

如何开发自己的 AppWidget 小组件程序:构建应用微件

如何开发「托管 AppWidget 小组件」的宿主应用(类似于手机桌面 Launcher 应用):构建应用微件托管应用

如何设置 AppWidget 小组件的最小尺寸:应用微件设计指南

二、小组件是什么?

就是可以添加到手机桌面的窗口小程序。比如热榜卡片:

三、AppWidget 核心类 AppWidgetProvider 源码解读和原理分析

1、先看 AppWidgetProvider 源码

package android.appwidget;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

public class AppWidgetProvider extends BroadcastReceiver {
     
    public AppWidgetProvider() {

    }

    public void onReceive(Context context, Intent intent) {
        // Protect against rogue update broadcasts (not really a security issue,
        // just filter bad broacasts out so subclasses are less likely to crash).
        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), appWidgetIds);
                }
            }
        } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
                final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
                this.onDeleted(context, new int[] { appWidgetId });
            }
        } else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
                    && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
                int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
                Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
                this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
                        appWidgetId, widgetExtras);
            }
        } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
            this.onEnabled(context);
        } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
            this.onDisabled(context);
        } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null) {
                int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
                int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
                if (oldIds != null && oldIds.length > 0) {
                    this.onRestored(context, oldIds, newIds);
                    this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
                }
            }
        }
    }

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // updatePeriodMillis 手机系统定期更新时会调用此方法
        // 当用户添加应用微件时也(可能)会调用此方法,所以它应执行基本设置,如定义视图的事件处理脚本以及根据需要启动临时的 Service
    }

    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
            int appWidgetId, Bundle newOptions) {
        // 当首次放置微件时以及每当调整微件的大小时,会调用此方法。您可以使用此回调来根据微件的大小范围显示或隐藏内容。您可以通过调用 getAppWidgetOptions() 来获取大小范围,该方法会返回包含以下各项的 Bundle:
        // OPTION_APPWIDGET_MIN_WIDTH - 包含微件实例的当前宽度的下限(以 dp 为单位)。
        // OPTION_APPWIDGET_MIN_HEIGHT - 包含微件实例的当前高度的下限(以 dp 为单位)。
        // OPTION_APPWIDGET_MAX_WIDTH - 包含微件实例的当前宽度的上限(以 dp 为单位)。
        // OPTION_APPWIDGET_MAX_HEIGHT - 包含微件实例的当前高度的上限(以 dp 为单位)。
    }

    public void onDeleted(Context context, int[] appWidgetIds) {
        // 每次从应用微件托管应用中删除应用微件时,都会调用此方法。
    }

    public void onEnabled(Context context) {
        // 首次创建应用微件的实例时,会调用此方法。
        // 例如,如果用户添加应用微件的两个实例,只有首次添加时会调用此方法。
        // 如果您需要打开一个新的数据库或执行只需要对所有应用微件实例执行一次的其他设置,则此方法非常合适。
    }

    public void onDisabled(Context context) {
        // 从应用微件托管应用中删除了应用微件的最后一个实例时,会调用此方法。
        // 您应使用此方法来清理在 onEnabled(Context) 中完成的所有工作,如删除临时数据库。
    }

    public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
        // 如果你的 AppWidgetProvider 需要从 backup 中恢复时,需要重新此方法,做一些数据恢复/状态更新的操作。
    }

}

从源码了解到,AppWidgetProvider 是一个广播实现类,那么 AppWidgetProvider 在清单文件中注册是不是要使用 <receiver> 呢?是的,后面会讲到。

public class AppWidgetProvider extends BroadcastReceiver {...}

2、AppWidgetProvider 回调方法分析

其他的不介绍了,上面已经注释说明。重点说两个:

onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)

这个回调方法是 AppWidgetProvider 的核心方法

在小组件手机添加到桌面等宿主程序时(这个时机也可能不会回调的),以及 xml/... 配置中设定的 updatePeriodMillis 更新周期到达时,都会调用此方法。

你需要在此方法中:

→ 请求需要的数据;

→ 创建 RemoteViews 布局包装类,设置点击事件和更新数据;

→ 最后记得更新布局:appWidgetManager?.updateAppWidget(appWidgetIds, remoteViews)

onReceive(Context context, Intent intent)

这个回调方法原本是广播 BroadcastReceiver 的回调方法。

小组件 AppWidgetProvider 在此回调方法中实现了很多逻辑,小组件所有的事件都是系统发送的广播,然后在 onReceive() 方法中接收处理,判断 action 的类型,根据不同类型再回调 AppWidgetProvider 中的其他回调方法。

那 onReceive() 和其他回调方法的执行顺序是怎样的呢?拿 onUpdate() 来举例。

→ 先执行 onReceive(),在其中判断 action,发现 action 等于

AppWidgetManager.ACTION_APPWIDGET_UPDATE
AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE
AppWidgetManager.ACTION_APPWIDGET_RESTORED

三者之一

→ 然后再执行了 onUpdate()。

3、开发自己的小组件时,写 AppWidgetProvider 实现类,有没有必要重写 onReceive(Context context, Intent intent) 方法?

→ 如果只关注 AppWidgetProvider 的生命周期方法,那就没必要再重写 onReceive() 方法了;

→ 如果在 AppWidgetProvider 中,因为进程切换等原因,还需要把 AppWidgetProvider 当成广播来使用,那就要重写 onReceive() 方法了。

重写时要注意,不要把 super.onReceive(context, intent) 删除了:

override fun onReceive(context: Context?, intent: Intent?) {
    // 不要删除父类方法调用
    super.onReceive(context, intent)
    // 针对每个广播调用此方法,并且是在各个回调方法之前调用。
    // 您通常不需要实现此方法,因为默认的 AppWidgetProvider 实现会过滤所有应用微件广播并视情况调用回调方法。
 
    val action = intent?.action?:return
    if (action == xxx) {
        ...
    }
}

四、开发自己的小组件时,必须的配合和常用方法

1、编写实现类,继承 AppWidgetProvider

Demo 如:

class DemoAppWidgetProvider : AppWidgetProvider() {
 
    override fun onUpdate(
        context: Context?,
        appWidgetManager: AppWidgetManager?,
        appWidgetIds: IntArray?
    ) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
 
        // 构建布局的 RemoteViews 对象
        // 定义布局文件 R.layout.widget_layout
        val remoteViews = RemoteViews(context?.packageName ?: "", R.layout.widget_layout)
         
        // 更新内容
        remoteViews.setTextViewText(R.id.tv_time, getTextV())
         
        // 更新小组件 UI
        appWidgetManager?.updateAppWidget(appWidgetIds, remoteViews)
    }
 
    private fun getTextV(): String {
        val time = System.currentTimeMillis()
        val minute = time / 1000 / 60 % 60
        val second = time / 1000 % 60
        return "${minute}:${second}"
    }
 
}

需要说明的是:

→ appWidgetManager: AppWidgetManager 参数可以通过 onUpdate() 方法的参数获取,也可以通过 AppWidgetManager.getInstance(context) 静态方法获取。

其实 onReceive() 中就是通过 AppWidgetManager.getInstance(context) 静态方法获取并传递给 onUpdate() 方法的。

所以没有必要再自己调用 AppWidgetManager.getInstance(context) 静态方法获取,接收 onUpdate() 的参数保存即可。

验证经过打印 Log 测试,确认 onUpdate() 方法参数中的 appWidgetManager: AppWidgetManager 对象,与 AppWidgetManager.getInstance(context) 静态方法获取的对象,二者内存地址一样。

→ appWidgetIds: IntArray? 参数可以通过 onUpdate() 方法的参数获取也可以通过 AppWidgetManager.getInstance(context).getAppWidgetIds(ComponentName(context!!, XXXAppWidgetProvider::class.java)) 静态方法获取。

其实 onUpdate() 方法中的 appWidgetIds: IntArray? 参数是通过 AppWidgetManager.EXTRA_APPWIDGET_IDS 常量获取的。

全局搜索 AppWidgetManager.EXTRA_APPWIDGET_IDS 使用的地方,能找到一处 com.android.systemui.people.widget.PeopleBackupHelper.updateWidgets(Context context),

其中的 widgetIds 是通过 AppWidgetManager.getInstance(context).getAppWidgetIds(ComponentName(context!!, XXXAppWidgetProvider::class.java)) 静态方法产生的,然后 put 到了 AppWidgetManager.EXTRA_APPWIDGET_IDS 常量中

验证经过打印 Log 测试,确认 onUpdate() 方法参数中的 appWidgetIds: IntArray? 参数,

与 AppWidgetManager.getInstance(context).getAppWidgetIds(ComponentName(context!!, XXXAppWidgetProvider::class.java)) 静态方法获取的数组,二者数组长度和内容一样。

2、定义基本配置文件

在 res/xml/ 文件夹中新建配置文件 demo_appwidget_info.xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:updatePeriodMillis="3600000"
    android:previewImage="@drawable/preview"
    android:initialLayout="@layout/example_appwidget"
    android:configure="com.example.android.ExampleAppWidgetConfigure"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen">
</appwidget-provider>

具体参数说明请参考:https://developer.android.com/guide/topics/appwidgets?hl=zh-cn 添加 AppWidgetProviderInfo 元数据

要注意如何设置 minWidth 和 minHeight,参考:https://developer.android.com/guide/practices/ui_guidelines/widget_design?hl=zh-cn#anatomy_determining_size 确定微件的尺寸

3、在清单文件中注册 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/demo_appwidget_info" />
</receiver>

重点说明:

→ action 的名称 android.appwidget.action.APPWIDGET_UPDATE 必须添加,且名称一摸一样不能改变。否则在手机小组件添加界面会不显示你的小组件了。

→ meta-data 节点必须添加,其名称 android.appwidget.provider 不能改变。除非某厂商要求定制化。

→ meta-data 节点的资源引用(值)为你上面新建的配置文件 demo_appwidget_info.xml。

→ 如果有广播用途,也可以在 intent-filter 中添加自己的 action。

4、创建布局文件

比如上例中的 R.layout.widget_layout

要注意小组件支持的 View:https://developer.android.com/guide/topics/appwidgets?hl=zh-cn 创建应用微件布局

5、更复杂的配置,创建配置 Activity,创建集合布局等,暂不介绍了,用得少。

感兴趣的话,请参考文档 https://developer.android.com/guide/topics/appwidgets?hl=zh-cn

更多推荐

chk文件怎么恢复?chk文件恢复软件哪个好?

电脑中的每个文件都有其不同的后缀名,如.txt、.png等等,那么你知道.chk后缀的文件是什么吗?下面我们就来一起了解一下吧。chk文件的含义chk文件是用户在使用磁盘碎片整理程序后所产生的丢失簇的恢复文件,磁盘中的原文件并没有丢失,而是变成了chk文件,并被存放在FOUND.000文件夹中。另外,移动存储设备在使用

2023年海南省职业院校技能大赛(高职组)应用软件系统开发赛项规程

2023年海南省职业院校技能大赛(高职组)应用软件系统开发赛项规程赛项名称赛项名称:应用软件系统开发英文名称:ApplicationsoftwareDevelopment赛项组别:高职组赛项归属产业:电子信息专业类竞赛目标本赛项以应用软件开发技术为核心、考察应用软件开发人员的专业技能、职业素养和企业化全流程开发的能力。

linux 数据恢复

Linux误删除及误格式化的数据恢复方案针对的文件系统:1、基于EXT2/EXT3/EXT4文件系统;2、基于Reiserfs文件系统;3、基于Xfs文件系统。Linux误删除及误格式化的数据恢复解决方案:一、故障检测:1、检测是否存在硬件故障,如有硬件故障先处理硬件问题。2、以只读方式检测故障表现是否与用户的描述相同

动态规划之子序列

子序列问题1.最长递增子序列2.摆动序列3.最长递增子序列的个数4.最长等差数列5.等差数列划分II-子序列首先说明一下子序列和子数组的概念。在数组中,子数组是由连续的元素组成的,而子序列则不一定是连续的。在字符串中,子串是由连续的字符组成的,而子序列则不一定是连续的。1.最长递增子序列1.题目链接:子序列问题2.题目

企业备份解决方案:保护您的企业虚拟机安全!

在目前这个高度数据化的信息时代中,企业对数据的依赖程度更高,以便进行高效的运营和理智的决策。然而,硬件的故障、自然的灾害以及网络的攻击等无法预料的情况,可能会带来大规模的数据丢失,进而造成经济的损失,甚至可能会威胁到企业的生存。企业备份解决方案是一种全面的策略,旨在备份关键的商务数据,以确保在灾难发生时,重要的数据安全

easycms v5.5 分析 | Bugku S3 AWD排位赛

前言这个awd打的悲,后台默认用户名密码为admin:admin,但是几乎所有人都改了而且一进去看到这个cms就有点懵逼,都不知道这个cms是干嘛的(没用过相似的cms)虽然网上找出了很多相关的漏洞,但是不知道为什么一个都没用上,或者说是用不了所以现在来审计一下这个cms根据里面的注释我得出是v5.5的版本(虽然不知道

Rust常见集合

迄今为止,我们前面遇到的数据类型基本都是栈上存储的。Rust标准库中包含一系列被称为集合(collections)的非常有用的数据结构。这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。本篇我们将了解三个在Rust程序中被广泛使用的集合:vector允许我们一个

「粉红杀手」通缉令,AI 阅读乳腺 X 光片的能力已与医生相当

据世界卫生组织统计,2020年全球新发乳腺癌病例230万例,在所有癌症中居首位,超越肺癌成为第一大癌。然而,如果能够早期发现并加以及时治疗,在肿瘤转移之前杀死癌细胞,乳腺癌的致死率就可以大大降低。目前乳腺癌初筛的常用手段是乳腺X光,随后医生通过分析复核X光片对乳腺健康情况进行判断。但复核过程会消耗大量时间,影响其他患者

Nodejs+vue体育用品商城商品购物推荐系统_t81xg

本课题基于协同过滤算法,主要采用nodejs技术和MySQL数据库技术以及vue框架进行开发。功能主要包括首页、个人中心、用户管理、商品分类管理、商品信息管理、交流论坛、留言板、系统管理、订单管理等功能,从而实现个性化智能体育商品推荐方式,提高个性化智能体育商品推荐的效率。相比于传统的体育商品推荐方式,个性化智能的管理

拉斯克奖(Lasker Award)2023

拉斯克奖(LaskerAward)2023🔈🔈🔈:deeplearning的两位科学家获得了拉斯克奖,这让人不禁对今年的诺贝奖展开大胆的预测。1.拉斯克奖(LaskerAward)简介Lasker-DeBakeyClinicalMedicalResearchAward(LaskerAward)是美国著名的医学科学

F.interpolate 在训练过程中无可学习参数

在PyTorch中,F.interpolate函数本身并没有可学习参数。它是一个用于调整输入张量尺寸的函数,通常用于图像的上采样或下采样操作。F.interpolate函数根据提供的调整方式(如插值方法、缩放因子等),对输入张量进行插值操作以得到目标尺寸的输出张量。这个过程是根据输入数据进行计算,而没有额外的可学习参数

热文推荐