应用程序窗口小部件 App Widgets
应用程序窗口小部件( Widget )是微小的应用程序视图,可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个 App Widget provider 来发布一个 Widget 。可以容纳其它 App Widget 的应用程序组件被称为 App Widget 宿主。下面的截屏显示了一个音乐 App Widget 。
这篇文章描述了如何使用 App Widget Provider 发布一个 App Widget 。
基础知识 The Basics
为了创建一个 App Widget ,你需要下面这些:
描述一个 App Widget 元数据,比如 App Widget 的布局,更新频率,以及 AppWidgetProvider 类。这应该在 XML 里定义。
AppWidgetProvider 类的实现
定义基本方法以允许你编程来和 App Widget 连接,这基于广播事件。通过它,当这个 App Widget 被更新,启用,禁用和删除的时候,你都将接收到广播通知。
视图布局
为这个 App Widget 定义初始布局,在 XML 中。
另外,你可以实现一个 App Widget 配置活动。这是一个可选的活动 Activity ,当用户添加 App Widget 时加载并允许他在创建时来修改 App Widget 的设置。
下面的章节描述了如何建立这些组件:
在清单中声明一个应用小部件
首先,在应用程序 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> 元素需要 android:name 属性,它指定了 App Widget 使用的 AppWidgetProvider 。
<intent-filter> 元素必须包括一个含有 android:name 属性的 <action> 元素。该元素指定 AppWidgetProvider 接受 ACTION_APPWIDGET_UPDATE 广播。这是唯一你必须显式声明的广播。当需要的时候, AppWidgetManager 会自动发送所有其他 App Widget 广播给 AppWidgetProvider 。
<meta-data> 元素指定了 AppWidgetProviderInfo 资源并需要以下属性:
· android:name – 指定元数据名称。
· android:resource – 指定 AppWidgetProviderInfo 资源路径。
增加 AppWidgetProviderInfo 元数据
AppWidgetProviderInfo 定义一个 App Widget 的基本特性,比如最小布局尺寸,初始布局资源,刷新频率,以及(可选的)创建时加载的一个配置活动。使用单独的一个 <appwidget-provider> 元素在 XML 资源里定义 AppWidgetProviderInfo 对象并保存到项目的 res/xml/ 目录下。
比如:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dp" <!-- density-independent pixels -->
android:minHeight="72dp"
android:updatePeriodMillis="86400000" <!-- once per day -->
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure" >
</appwidget-provider>
下面是 <appwidget-provider> 属性的总结:
· minWidth 和 minHeight 属性的值指定了这个 App Widget 布局需要的最小区域。
缺省的 App Widgets 所在窗口的桌面位置基于有确切高度和宽度的单元网格。如果 App Widget 的最小长宽和这些网格单元的尺寸不匹配,那么这个 App Widget 将收缩到最接近的单元尺寸。(参见 App Widget Design Guidelines 以获取更多关于桌面单元尺寸的信息)
因为桌面布局方向(由此,单元的尺寸)可以变化,按照拇指规则,你应该假设最坏情况单元尺寸是
74
像素高和宽。不过,你必须从最后的尺寸中减去
2
以把像素计算过程中产生的任何的整数舍入误差考虑在内。要找到像素密度无关的最小宽度和高度,使用这个公式:
(number of cells * 74) - 2
遵循这个公式,你应该使用
72dp
为每一个单元高度,
294dp
为四个单元宽度。
· updatePerdiodMillis 属性定义了 App Widget 框架调用 onUpdate() 方法来从 AppWidgetProvider 请求一次更新的频度。实际更新时间并不那么精确,而且我们建议更新频率越低越好 - 也许每小时不超过一次以节省电源。你也许还会允许用户在配置中调整这个频率 - 一些人可能想每 15 分钟一次股票报价,或者一天只要四次。
· initialLayout 属性指向定义 App Widget 布局的资源。
· configure 属性定义了 Activity ,当用户添加 App Widget 时启动,以为他或她配置 App Widget 特性。这是可选的(阅读下面的 Creating an App Widget Configuration Activity )。
参见 AppWidgetProviderInfo 类以获取更多可以被 <appwidget-provider> 元素接受的属性信息。
创建 App Widget 布局
你必须在 XML 中为你的 App Widget 定义一个初始布局并保存到项目的 res/layout/ 目录下。你可以使用如下所列的视图对象来设计你的 App Widget ,但是在此之前,请先阅读并理解 App Widget Design Guidelines .
如果你熟悉在 XML 中声明布局,那么创建这个 App Widget 布局是很简单的。但是,你必须意识到那个 App Widget 布局是基于 RemoteViews , 这并不支持所有类型的布局或视图小部件。
一个 RemoteViews 对象(以及,相应的,一个 App Widget )可以支持下面这个布局类:
以及下面的小部件类:
· Button
· TextView
不支持这些类的派生。
使用 AppWidgetProvider 类
你必须通过在清单文件中使用 <receiver> 元素来声明你的 AppWidgetProvider 类实现为一个广播接收器(参见上面的 Declaring an App Widget in the Manifest )。
AppWidgetProvider 类扩展 BroadcastReceiver 为一个简便类来处理 App Widget 广播。 AppWidgetProvider 只接收和这个 App Widget 相关的事件广播,比如这个 App Widget 被更新,删除,启用,以及禁用。当这些广播事件发生时, AppWidgetProvider 将接收到下面的方法调用:
onUpdate(Context, AppWidgetManager, int[])
这个方法调用来间隔性的更新 App Widget ,间隔时间用 AppWidgetProviderInfo 里的 updatePeriodMillis 属性定义(参见添加 AppWidgetProviderInfo 元数据)。这个方法也会在用户添加 App Widget 时被调用,因此它应该执行基础的设置,比如为视图定义事件处理器并启动一个临时的服务 Service , 如果需要的话。但是,如果你已经声明了一个配置活动,这个方法在用户添加 App Widget 时将不会被调用,而只在后续更新时被调用。配置活动应该在配置完成时负责执行第一次更新。(参见下面的创建一个 App Widget 配置活动 Creating an App Widget Configuration Activity 。 )
当 App Widget 从宿主中删除时被调用。
当一个 App Widget 实例第一次创建时被调用。比如,如果用户添加两个你的 App Widget 实例,只在第一次被调用。如果你需要打开一个新的数据库或者执行其他对于所有的 App Widget 实例只需要发生一次的设置,那么这里是完成这个工作的好地方。
当你的 App Widget 的最后一个实例被从宿主中删除时被调用。你应该在 onEnabled(Context) 中做一些清理工作,比如删除一个临时的数据库。
这个接收到每个广播时都会被调用,而且在上面的回调函数之前。你通常不需要实现这个方法,因为缺省的 AppWidgetProvider 实现过滤所有 App Widget 广播并恰当的调用上述方法。
注意 : 在 Android 1.5 中, 有一个已知问题, onDeleted() 方法在该调用时不被调用。为了规避这个问题,你可以像 Group post 中描述的那样实现 onReceive() 来接收这个 onDeleted() 回调。
最重要的 AppWidgetProvider 回调函数是 onUpdated() , 因为它是在每个 App Widget 添加进宿主时被调用的(除非你使用一个配置活动)。如果你的 App Widget 要接受任何用户交互事件,那么你需要在这个回调函数中注册事件处理器。如果你的 App Widget 不创建临时文件或数据库,或者执行其它需要清理的工作,那么 onUpdated() 可能是你需要定义的唯一的回调函数。比如,如果你想要一个带一个按钮的 App Widget ,当点击时启动一个活动,你可以使用下面的 AppWidgetProvider 实现:
public class ExampleAppWidgetProvider extends AppWidgetProvider {
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final int N = appWidgetIds.length;
// Perform this loop procedure for each App Widget that belongs to this provider
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];
// Create an Intent to launch ExampleActivity
Intent intent = new Intent(context, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
// Get the layout for the App Widget and attach an on-click listener to the button
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
views.setOnClickPendingIntent(R.id.button, pendingIntent);
// Tell the AppWidgetManager to perform an update on the current App Widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
}
这个 AppWidgetProvider 仅定义了 onUpdated() 方法,为了定义一个 PendingIntent , 来启动一个活动并使用 setOnClickPendingIntent(int, PendingIntent) 方法把它附着到这个 App Widget 的按钮上。注意它包含了一个遍历 appWidgetIds 中所有项的循环,这是一个 IDs 数组,每个 ID 用来标识由这个 Provider 创建的一个 App Widget 。这样,如果用户创建多于一个这个 App Widget 的实例,那么它们将被同步更新。不过,对于所有的 App Widget 实例,只有一个 updatePeriodMillis 时间表被管理。比如,如果这个更新时间表被定义为每隔两个小时,而且 App Widget 的第二个实例是在第一个后面一小时添加的,那么它们将按照第一个所定义的周期来更新而第二个被忽略(它们将都是每 2 个小时进行更新,而不是每小时)。
注意 : 因为这个 AppWidgetProvider 是一个广播接收器 BroadcastReceiver ,不能保证你的进程在回调函数返回后仍然继续运行(参见应用程序基础 > 广播接收器的生命周期 Application Fundamentals > Broadcast Receiver Lifecycle 以获取更多信息)。如果你的 App Widget 设置过程能持续几秒钟(也许当执行网页请求时)而且你要求你的进程继续,考虑在 onUpdated() 方法里启动一个服务 Service 。 从这个服务里,你可以执行自己的 App Widget 更新,而不必担心 AppWidgetProvider 由于一个应用程序无响应错误 Application Not Responding (ANR) 而关闭。参见 Wiktionary sample's AppWidgetProvider ,这是个 App Widget 运行一个 Service 的例子。
同样参见 ExampleAppWidgetProvider.java 例子类。
接收 App Widget 广播意图
AppWidgetProvider 只是一个简便类。如果你想直接接收 App Widget 广播,你可以实现自己的 BroadcastReceiver 或者重写 onReceive(Context, Intent) 回调函数。你需要注意的 4 个意图如下:
创建一个 App Widget 配置活动
如果你想让用户在添加一个新的 App Widget 时调整设置,你可以创建一个 App Widget 配置活动。这个活动将被 App Widget 宿主自动启动并允许用户在创建时配置可用的设置,比如 App Widget 颜色,尺寸,更新周期或者其它功能设置。
这个配置活动应该在 Android 清单文件中声明为一个通用活动。不过,它将被通过 ACTION_APPWIDGET_CONFIGURE 活动而被 App Widget 宿主启动,因此这个活动需要接受这个意图。比如:
<activity android:name=".ExampleAppWidgetConfigure">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
同样的,活动必须在 AppWidgetProviderInfo XML 文件中声明,通过 android:configure 属性(参见上面的添加 AppWidgetProviderInfo 元数据 Adding the AppWidgetProviderInfo Metadata )。比如,配置活动可以声明如下:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
...
android:configure="com.example.android.ExampleAppWidgetConfigure"
... >
</appwidget-provider>
注意这个活动是用全名声明的,因为它将从你的程序包外被引用。
这就是所有关于配置活动你一开始需要了解的。现在你需要一个真实的活动。这儿就有,不过,当你实现这个活动时记住两件重要的事情:
<!--StartFragment-->
• App Widget 宿主调用配置活动而且配置活动应该总是返回一个结果 .这个结果应该包含这个通过启动该活动的意图传递的App Widget ID(以 EXTRA_APPWIDGET_ID 保存在意图的附加段 Intent extras 中 )
• 当这个 App Widget 被创建时将不会调用 onUpdate() 方法 (当一个配置活动启动时,系统将不会发送 ACTION_APPWIDGET_UPDATE 广播 ). 配置活动应该在 App Widget 第一次被创建时负责从 AppWidgetManager 请求一个更新 .不过, onUpdate() 将在后续更新中被调用 -只忽略第一次.
参见下面章节的代码片断 ,该示例说明了如何从配置中返回一个结果并更新这个 App Widget.
<!--StartFragment-->
从配置活动中更新一个 App Widget
<!--EndFragment-->
当一个 App Widget 使用一个配置活动 ,那么当配置结束时,就应该由这个活动来更新这个 App Widget. 你可以直接 AppWidgetManager 里请求一个更新来这么做 .
下面是恰当的更新 App Widget 以及关闭配置活动这个过程的一个概要描述 :
<!--EndFragment--> <!--EndFragment-->-
首先,从启动这个活动的意图中获取App Widget ID:
Intent intent = getIntent(); Bundle extras = intent.getExtras(); if (extras != null) { mAppWidgetId = extras.getInt( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); }
- 实施你的App Widget 配置。
-
当配置完成后,通过调用
getInstance(Context)
获取一个AppWidgetManager实例:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
-
以一个
RemoteViews
布局调用
updateAppWidget(int, RemoteViews)
更新App Widget:
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget); appWidgetManager.updateAppWidget(mAppWidgetId, views);
- 最后,创建返回意图,设置活动结果,并结束这个活动:
Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); setResult(RESULT_OK, resultValue); finish();
提示: 当你的配置活动第一次打开时,设置活动结果为RESULT_CANCELED。这样,如果用户在结束之前从活动外返回,这个App Widget 宿主会接收到配置取消通知而不会添加这个App Widget。
参见ApiDemos里面的 ExampleAppWidgetConfigure.java 例子。