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