本文从开发AppWidgetProvider角度出发,看一个AppWidgetPrvodier在整个AppWidget体系中所扮演的角色。分析了AppWidgetProvider如何被AppWidget系统所识别;AppWidgetProvider何时/如何通过RemoteViews提供并更新数据;如何响应通过RemoteViews提供的PendingIntent的按钮点击操作。
因为一般应用开发者并不关注AppWidget其他部分(比如,AppWidgetHost,或AppWidget内部组件)的开发,所以一般就直接把AppWidgetProvider开发称为“AppWidget开发”。
一、实现一个AppWidgetProvider
要实现一个AppWidgetProvider,需要:
- 实现AppWidgetProvider的子类,并至少override onUpdate()方法[非必须,但是如果不这样做,该AppWidgetProvider就没有提供任何内容,也就不是AppWidgetProvider了];
- 在AndroidManifest.xml中,声明上述的AppWidgetProvider的子类是一个Receiver,并且:
- 该Receiver的intent-filter的Action必须包含“android.appwidget.action.APPWIDGET_UPDATE”;
- 该Receiver的meta-data为“android.appwidget.provider”,并用一个xml文件来描述布局属性。
- 在2.2的xml文件中描述布局属性的节点名称必须为“appwidget-provider”。
以上几点皆是AppWidget系统判断是否是AppWidgetProvider的标志。后面本文的3.2中详述是如何被检索并加入到系统中的。
二、AppWidgetProvider类分析
AppWidgetProvider是一个BroadcastReceiver,必须在AndroidManifest.xml中声明该Receiver,并接收“android.appwidget.action.APPWIDGET_UPDATE”。
类AppWidgetProvider的实现是一个模板模式:
图一、AppWidgetProvider
在AppWidgetProvider的onReceiver()实现中已经对接收到的ActionAppWidgetManager.ACTION_APPWIDGET_UPDATE / AppWidgetManager.ACTION_APPWIDGET_DELETED/ AppWidgetManager.ACTION_APPWIDGET_ENABLED以及AppWidgetManager.ACTION_APPWIDGET_DISABLED做了处理,分别执行onUpdate()/ onDeleted() / onEnabled() / onDisabled()。
所以,AppWidgetProvider的实现类,要overrideonReceive(),以及onXXX()[注:至少要实现onUpdate(),在这里AppWidgetProvider通过RemoteViews提供内容给AppWidgetHost,否则,所谓的AppWidgetProvider什么也没提供]。
而在onReceive()的开始处就要执行super.onReceive()让AppWidgetProvider来分发AppWidgetProvider所要处理的上述广播消息。
AppWidgetProvider处理AppWidget中的广播:
- onUpdate() 处理AppWidgetManager.ACTION_APPWIDGET_UPDATE广播。该广播在需要AppWidgetProvider提供RemoteViews数据时,由AppWidgetService.sendUpdateIntentLocked()发出。
- onDeleted() 处理AppWidgetManager.ACTION_APPWIDGET_DELETED广播。该广播在有该AppWidgetProvider的实例被删除时,由AppWidgetService.deleteAppWidgetLocked()发出。
- onEnabled() 处理AppWidgetManager.ACTION_APPWIDGET_ENABLED广播。该广播在该AppWidgetProvider被实例化时,由AppWidgetService.sendEnableIntentLocked()发出。
- onDisabled() 处理AppWidgetManager.ACTION_APPWIDGET_DISABLED广播。该广播在该AppWidgetProvider的所有实例中的最后一个实例被删除时,由AppWidgetService.deleteAppWidgetLocked()发出。
一般地,AppWidgetProvider必须处理onUpdate();onEnabled()和onDisabled()最好也要处理;onDeleted()可以不处理。
三、AppWidgetProvider的如何被系统识别
3.1 AppWidgetProvider中配置AndroidManifest.xml
Android中“电量控制”这个AppWidget是由Settings中的SettingsAppWidgetProvider实现的,先来看它的AndroidManifest.xml。
下面是Settings中关于SettingsAppWidgetProvider这个AppWidgetProvider的描述信息:
- <receiver android:name=".widget.SettingsAppWidgetProvider"
- android:label="@string/gadget_title"android:exported="true">
- <intent-filter>
- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
- <action android:name="android.net.wifi.WIFI_STATE_CHANGED"/>
- <action android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" />
- <action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
- <action android:name="android.location.PROVIDERS_CHANGED"/>
- <action android:name="com.android.sync.SYNC_CONN_STATUS_CHANGED" />
- </intent-filter>
- <meta-data android:name="android.appwidget.provider"android:resource="@xml/appwidget_info" />
- </receiver>
<receiver android:name=".widget.SettingsAppWidgetProvider"
android:label="@string/gadget_title"android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.net.wifi.WIFI_STATE_CHANGED"/>
<action android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" />
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
<action android:name="android.location.PROVIDERS_CHANGED"/>
<action android:name="com.android.sync.SYNC_CONN_STATUS_CHANGED" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"android:resource="@xml/appwidget_info" />
</receiver>
这其中满足一中的1&2的要求,另外,这个AppWidget要处理设置Wifi、Bluetooth、GPS、数据同步和亮度,所以要处理这些相应的设置项变化时的广播通知。
对3这点,还要要看res/xml/appwidget_info.xml文件
- <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
- android:minWidth="294dip"
- android:minHeight="72dip"
- android:updatePeriodMillis="0"
- android:initialLayout="@layout/widget"
- >
- </appwidget-provider>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dip"
android:minHeight="72dip"
android:updatePeriodMillis="0"
android:initialLayout="@layout/widget"
>
</appwidget-provider>
这里定义了最小宽度minWidth、最小高度minHeight和初始layoutinitialLayout,用来在AppWidgetProvider还未通过RemoteViews提供数据之前,AppWidgetHost就能够获知需要为该AppWidget预留大概的位置;
updatePeriodMillis指示是否需要周期性的更新AppWidget,0是不需要周期更新。
3.2 AppWidgetProvider的信息被系统所识别
这部分是由AppWidgetService实现。
当包含AppWidgetProvider的apk被安装到系统中的时候,AppWidgetService会监听广播,并处理相应的AppWidgetProvider:
- 监听到有包被加入(Intent.ACTION_PACKAGE_ADDED或Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE)时,执行addProvidersForPackageLocked()/ updateProvidersForPackageLocked() 添加或更新其中的AppWidgetProvider;
- 监听到有包被移除(Intent.ACTION_PACKAGE_REMOVED或Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)时,执行removeProvidersForPackageLocked()移除其中的AppWidgetProvider。
下面重点关注如何加入AppWidgetProvider,看addProvidersForPackageLocked()的实现:
- void addProvidersForPackageLocked(String pkgName) {
- Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
- intent.setPackage(pkgName);
- List<ResolveInfo> broadcastReceivers =mPackageManager.queryBroadcastReceivers(intent,
- PackageManager.GET_META_DATA);
- final int N = broadcastReceivers == null ? 0 :broadcastReceivers.size();
- for (int i=0; i<N; i++) {
- ResolveInfo ri = broadcastReceivers.get(i);
- ActivityInfo ai = ri.activityInfo;
- if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE)!= 0) {
- continue;
- }
- if (pkgName.equals(ai.packageName)) {
- addProviderLocked(ri);
- }
- }
- }
void addProvidersForPackageLocked(String pkgName) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.setPackage(pkgName);
List<ResolveInfo> broadcastReceivers =mPackageManager.queryBroadcastReceivers(intent,
PackageManager.GET_META_DATA);
final int N = broadcastReceivers == null ? 0 :broadcastReceivers.size();
for (int i=0; i<N; i++) {
ResolveInfo ri = broadcastReceivers.get(i);
ActivityInfo ai = ri.activityInfo;
if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE)!= 0) {
continue;
}
if (pkgName.equals(ai.packageName)) {
addProviderLocked(ri);
}
}
}
- 检索所加入包中的所有AppWidgetManager.ACTION_APPWIDGET_UPDATE的Receiver,并放入broadcastReceivers:List<ResolveInfo>;[Line#2~ 5]
- 对每一个这样的Broadcast,满足下列要求的加入已安装AppWidgetProvider列表:
- 不是安装在外存储器上;
- Receiver的包名与安装的包名相同;
- Meta-data的节点名必须是"appwidget-provider"[解析meta-data中android:resource指向的xml内容]
解析meta-data中android:resource指向的xml内容时,所要解析哪些内容是由frameworks/base/core/res/res/values/attrs.xml中的AppWidgetProviderInfo配置:
- <declare-styleable name="AppWidgetProviderInfo">
- <!-- Minimum width of the AppWidget. -->
- <attr name="minWidth"/>
- <!-- Minimum height of the AppWidget. -->
- <attr name="minHeight"/>
- <!-- Update period in milliseconds, or 0 if the AppWidget will updateitself. -->
- <attr name="updatePeriodMillis" format="integer"/>
- <!-- A resource id of a layout. -->
- <attr name="initialLayout" format="reference"/>
- <!-- A class name in the AppWidget's package to be launched toconfigure.
- If not supplied, then no activity will be launched. -->
- <attr name="configure" format="string" />
- </declare-styleable>
<declare-styleable name="AppWidgetProviderInfo">
<!-- Minimum width of the AppWidget. -->
<attr name="minWidth"/>
<!-- Minimum height of the AppWidget. -->
<attr name="minHeight"/>
<!-- Update period in milliseconds, or 0 if the AppWidget will updateitself. -->
<attr name="updatePeriodMillis" format="integer"/>
<!-- A resource id of a layout. -->
<attr name="initialLayout" format="reference"/>
<!-- A class name in the AppWidget's package to be launched toconfigure.
If not supplied, then no activity will be launched. -->
<attr name="configure" format="string" />
</declare-styleable>
这些值被解析出来之后,连同label、icon以及由ComponentName(packageName,className)构造的provider被赋值到AppWidgetProviderInfo中,并被记录在AppWidgetService的mInstalledProviders:ArrayList<Provider>中。
图二、AppWidgetProviderInfo
四、AppWidgetProvider的Enable与Disable
因为AppWidgetProvider只是提供显示内容,具体显示是显示在AppWidgetHost中的。因为Android机制的关系,后台的AppWidgetProvider很容易被系统杀掉。所以AppWidgetProvider在收到AppWidgetManager.ACTION_APPWIDGET_ENABLED和AppWidgetManager.ACTION_APPWIDGET_DISABLED而执行的onEnbaled()和onDisabled()中是恰当的设置实现AppWidgetProvider的包能不能被移除设置的恰当点。
在onEnbaled()中,该AppWidgetProvider正在被使用,不让被杀掉:
- PackageManager pm = context.getPackageManager();
- pm.setComponentEnabledSetting(
- new ComponentName("com.android.settings",".widget.SettingsAppWidgetProvider"),
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
- PackageManager.DONT_KILL_APP);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(
new ComponentName("com.android.settings",".widget.SettingsAppWidgetProvider"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
在onDisabled()中,该AppWidgetProvider不再被使用,可以被杀掉了:
- Class clazz = com.android.settings.widget.SettingsAppWidgetProvider.class;
- PackageManager pm = context.getPackageManager();
- pm.setComponentEnabledSetting(
- new ComponentName("com.android.settings",".widget.SettingsAppWidgetProvider"),
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP);
Class clazz = com.android.settings.widget.SettingsAppWidgetProvider.class;
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(
new ComponentName("com.android.settings",".widget.SettingsAppWidgetProvider"),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
五、AppWidgetProvider通过RemoteViews提供内容
在需要AppWIdgetProvider提供RemoteViews时,AppWidget系统会发出AppWidgetManager.ACTION_APPWIDGET_UPDATE广播,进而onUpdate()会被执行。
图三、AppWidgetProvider提供RemoteViews
- 在onUpdate()中,创建RemoteViews的实例,传入AppWidgteProvider所在的包名和该AppWidget所要用的Layout;[Seq#5]
- 如果要响应layoutId中某个viewId被点击操作,要创建本地的PendingIntent,并通过setOnClickPendingIntetn设置到RemoteViews中;[Seq#6~ #9]
- 为layoutId中要显示的控件加上显示元素,比如某个ImageView的ImageResource。[Seq#10]
- 用AppWidgetManager.updateAppWidget()更新RemoteViews到系统中,AppWidget系统会更新与之绑定的AppWidgetHost。[Seq#11]
RemoteViews设置与显示详细实现可参考《Android中RemoteViews的实现》;AppWidgetHost如何更新RemoteView可参看《Android中Launcher对于AppWidget处理的分析:AppWidgetHost角色》。
六、通过PendingIntent设置按钮响应
上面讲到,可以通过给RemoteViews设置PendingIntent获知感兴趣的View被点击时的响应:
- Intent launchIntent = new Intent();
- launchIntent.setClass(context, SettingsAppWidgetProvider.class);
- launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE);
- launchIntent.setData(Uri.parse("custom:" + buttonId));
- PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* norequestCode */,
- launchIntent, 0 /* no flags */);
Intent launchIntent = new Intent();
launchIntent.setClass(context, SettingsAppWidgetProvider.class);
launchIntent.addCategory(Intent.CATEGORY_ALTERNATIVE);
launchIntent.setData(Uri.parse("custom:" + buttonId));
PendingIntent pi = PendingIntent.getBroadcast(context, 0 /* norequestCode */,
launchIntent, 0 /* no flags */);
buttonId是layout中的各个Button对应的自定义的Id,该Id只要在本程序中用来能够区分出是哪个Button就可以,被指进”custom:”参数。
AppWidgetProvider本身就是个BroadcastReceiver,在其onReceive()中,就可以判断出是哪个Button被点击了:
- if(intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
- Uri data = intent.getData();
- int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
- if (buttonId == BUTTON_WIFI) {
- // 切换Wifi状态
- } else if (buttonId == BUTTON_BRIGHTNESS) {
- // 切换亮度
- } else if (buttonId == BUTTON_SYNC) {
- // 切换数据同步设置
- } else if (buttonId == BUTTON_GPS) {
- // 切换GPS打开开关
- } else if (buttonId == BUTTON_BLUETOOTH) {
- // 切换蓝牙打开状态
- }
- }
if(intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
Uri data = intent.getData();
int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
if (buttonId == BUTTON_WIFI) {
// 切换Wifi状态
} else if (buttonId == BUTTON_BRIGHTNESS) {
// 切换亮度
} else if (buttonId == BUTTON_SYNC) {
// 切换数据同步设置
} else if (buttonId == BUTTON_GPS) {
// 切换GPS打开开关
} else if (buttonId == BUTTON_BLUETOOTH) {
// 切换蓝牙打开状态
}
}
总结
本文讲述了:
- 实现一个AppWidgetProvider所需要的配置和实现;
- AppWidgetProvider如何被AppWidget识别和加入到已安装列表;
- AppWidgetProvider如何生成RemoteViews对象,并更新到AppWidgetHost;
- AppWidgetProvider响应按钮操作。
可进一步参考的文章
AppWidget系统框架。
看如何调用本文描述的已经获取的AppWidgetProvider列表的。
Android中AppWidget的分析与应用:AppWidgetProvider
本文。
Android中Launcher对于AppWidget处理的分析:AppWidgetHost角色
可以看选取并绑定AppWidgetProvider之后,Launcher作为AppWidgetHost如何创建显示RemoteViews里AppWidgetProvider所提供的图形元素。还可以看到数据模型的加载。
RemoteViews的内部如何实现设置进去的OnClickPendingIntent和ViewImageResource的。