Android 小组件 AppWidgetProvider

一、相关文档
二、小组件是什么?
三、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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中,你可以通过动态生成小组件Widget)来实现在运行时创建和更新小组件的功能。以下是一些实现动态生成小组件的步骤: 1. 创建一个AppWidgetProvider类,继承自AppWidgetProvider。这个类将负责管理和处理小组件的生命周期和事件。 ```java public class MyWidgetProvider extends AppWidgetProvider { // 在这里处理小组件的生命周期和事件 } ``` 2. 在AndroidManifest.xml文件中声明AppWidgetProvider。在`<application>`标签内添加一个`<receiver>`标签,并设置相应的属性,例如`android:name`和`android:label`。 ```xml <receiver android:name=".MyWidgetProvider" android:label="@string/widget_name"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_info" /> </receiver> ``` 3. 在res/xml目录下创建widget_info.xml文件,定义小组件的属性。 ```xml <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="xxdp" android:minHeight="xxdp" android:updatePeriodMillis="xxx" android:initialLayout="@layout/widget_layout" android:configure="com.example.myapp.MyWidgetConfigActivity"> </appwidget-provider> ``` 4. 在res/layout目录下创建widget_layout.xml文件,定义小组件的布局。 ```xml <!-- 定义小组件的布局 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 小组件的UI元素 --> </LinearLayout> ``` 5. 在AppWidgetProvider类中,重写onUpdate()方法,用于处理小组件的更新操作。 ```java @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // 在这里更新小组件的UI内容 } ``` 通过以上步骤,你就可以动态生成小组件并更新其UI内容了。在AppWidgetProvider中,你可以根据需要处理小组件的生命周期和事件,并在onUpdate()方法中更新小组件的UI内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值