一、前言
所谓App Widgets就是微型应用程序的意思,它可以嵌入在其他应用程序(如主屏幕),并能定期更新其View。
这些View被当成用户界面的小部件,您可以使用App Widget provider来发布App Widgets。
一个能容纳其他的App Widgets的应用程序的组件,我们称之为App Widget host。图1就是一个音乐App Widget的截图。
图1
在下面的文章中,我们将讲述如何使用App Widget provider来发布App Widget
二、基本原理
为了创建一个App Widget,你需要以下三部分:
AppWidgetProviderInfo
AppWidgetProviderInfo用于对App Widgets的元数据(metadata)进行描述,如App Widgets的布局,更新频率,和AppWidgetProvider类。它应该是在XML中定义。
AppWidgetProvider
AppWidgetProvider定义了一些基本方法,通过这些方法你可以很方便和App Widget进行交互。
AppWidgetProvider基于广播事件。当App Widget进行更新,启用,禁用和删除时,在AppWidgetProvider中,您将收到其对应的广播。
视图布局文件
为了让App Widget能进行显示,我们还需要为App Widget的提供一个布局文件
另外,你还可以实现一个用于对App Widget进行配置的Activity.该Activity是可选的,当用户添加App Widget时,该Acitivity将被启动。通过它可以在App Widget被创建时,做一些对App Widget的设置。这里的设置是指和App Widget的事务相关的设置,不是指AppWidgetProviderInfo的内容。
三、在Manifest中声明App Widget
示例1
<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属性必须要进行设置, 该属性说明了我将使用哪个AppWidgetProvidere来提供App Widget。
<intent-filter>元素必须包含android:name属性为"android.appwidget.action.APPWIDGET_UPDATE"的Action,
即<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />。该属性说明了AppWidgetProvider可以接收ACTION_APPWIDGET_UPDATE广播。
该广播是唯一你必须要声明接受的广播。AppWidgetManager能自动的把其他所有的App
Widget广播发送到AppWidgetProvider
Widget广播发送到AppWidgetProvider
在<meta-data>元素中,必须要指定AppWidgetProviderInfo资源文件,必须要定义以下2个属性:
* android:name - 它用于定义metadata元素的名字. 必须把该属性设置为“android.appwidget.provider”以表明该<meta-data>元素是用于描述AppWidgetProviderInfo资源文件位置的.
* android:resource -该属性用于说明AppWidgetProviderInfo资源文件的位置。
四、编写AppWidgetProviderInfo配置文件
AppWidgetProviderInfo用于定义App Widget的基本属性,如显示的最小尺寸,其初始布局资源,更新频率,
和(可选)configuration Activity,该Activity将在其App Widget被创建时被启动。
AppWidgetProviderInfo的定义必须在一个只有单一的<appwidget-provider>元素的XML资源文件中进行,该文件必须放在res/xml目录下。
示例2:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dp"
android:minHeight="72dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/preview"
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure"
android:resizeMode="horizontal|vertical">
</appwidget-provider>
以下是关于<appwidget-provider>一些属性的介绍:
minWidth和minHeight属性用于说明App
Widget在屏幕上至少要占用多大的空间 。
Widget在屏幕上至少要占用多大的空间 。
默认的Home screen在放置App Widgets时,是以网格为基本单位,而不是以像素,这里的网格是指拥有一定的像素的长方形区域。
因为home屏幕的布局方向可能改变(网格的大小就会随之改变),所以你应该考虑最坏的情况,此时网格拥有74个像素的长和高。
然而你必须减去2个像素,以便避免在计算网格数量时,对像素进行取整,发生错误。
你应该使用下面的公式计算android:minWidth和android:minHeight:
(number of cells * 74) - 2
根据上面的公式,如果想你的App Widget占用4个网格的宽,1个网格的高话,那么android:minWidth和android:minHeight应该分别为"294dp"和"72dp"
注意:为了让你的App Widget能在各类平台上运行,你的App Widget不要超过4X4网格的尺寸。关于App Widget的设计的更多内容请参考App
Widget Design Guidelines
Widget Design Guidelines
updatePeriodMillis属性用于说明App Widget框架请求AppWidgetProvider的onUpdate()方法来更新App
Widget的频率。但是这个频率是无法完全保证的,我们建议尽量减少更新的频率。有时可能一小时才更新一次,以便节约电池。你也可以提供一个配置,让用户自己设置更新的频率。有些人可能想15分钟就更新股票的价格,而有些人仅想1天就更新股票4次。
Widget的频率。但是这个频率是无法完全保证的,我们建议尽量减少更新的频率。有时可能一小时才更新一次,以便节约电池。你也可以提供一个配置,让用户自己设置更新的频率。有些人可能想15分钟就更新股票的价格,而有些人仅想1天就更新股票4次。
注意:如果手机在处于休眠状态时,而对App Widget进行更新的时间有到了,这时设备将醒来以便进行App Widget的更新。如果更新频率低于一个小时一次的话,并不会引起明显的电池消耗。如果你的更新非常频繁或你不想手机在处于休眠时还进行App Widget更新的话,请通过一个alarm来进行更新。你可以用
AlarmManager
来设置一个定期发送AppWidgetProvider的Intent的Alarm,且把Alarm的类型设置为ELAPSED_REALTIME
or RTC,这两种类型的Alarm只有在系统处于awake状态才会发送。另外此时,要把
android:updatePeriodMillis设置为"0"
initialLayout属性用于设置App Widget的布局文件。
configure属性用于说明在App Widget在被添加到Home Screen时,哪个configure Activity将首先启动。这是一个可选属性,关于次的更多内容请阅读后文。
previewImage属性在Android 3.0版本中才被添加,它用于指明 App Widget的预览图片,它用于在用户选中该App Widget的图标,打算添加该App Widget时,进行显示,以便用户了解该App Widget的界面。如果没提供预览图标的话,显示的将是你的App
Widget的启动图标。该属性和AndroidManifest.xml中的<receiver>元素的android:previewImage的属性一致。关于此的更多内容请参照后文。
Widget的启动图标。该属性和AndroidManifest.xml中的<receiver>元素的android:previewImage的属性一致。关于此的更多内容请参照后文。
The autoAdvanceViewId attribute specifies the view ID of the app widget subview that should be
auto-advanced by the widget's host. Introduced in Android 3.0.
auto-advanced by the widget's host. Introduced in Android 3.0.
resizeMode属性在Android 3.1中才被添加,它用于说明App Widget重新调整大小的规则。通过该属性,你可以设置在什么方向允许调整App Widget的大小,可以是垂直、水平、或同时垂直和水平两个方向。用户可以按住App Widget来显示大小调整handle,通过在水平或垂直方向拖动handle来调整
App Widget在垂直或水平方向的尺寸。resizeMode属性的值可以是"horizontal",
"vertical", "none"和"horizontal|vertical"
"vertical", "none"和"horizontal|vertical"
icon属性用于说明你的AppWidget在AppWidget picker列表中显示的图标,它应该和AndroidManifest.xml中的<receiver>元素的android:icon的属性一致
label属性用于说明你的AppWidget在AppWidget picker列表中显示的名字,它应该和AndroidManifest.xml中的<receiver>元素的android:label的属性一致。
五、编写App Widget布局文件
你必须在XML文件中定义你的App Widget的布局文件,并把它保存在res/layout/目录下。你可以在 App Widget中使用如下的View,但是请你在开始你的App Widget的布局时请阅读App
Widget Design Guidelines.
Widget Design Guidelines.
RemoteViews对象支持如下的一些View及其子类:
六、如何使用AppWidgetProvider
AppWidgetProvider继承于BroadcastReceiver,它对App
Widget的广播进行了简单分类,并封装了处理的统一接口,以方便使用。
Widget的广播进行了简单分类,并封装了处理的统一接口,以方便使用。
AppWidgetProvider只接受和App Widget相关的广播,比如App Widget更新, 被删除, enabled, 和disabled的广播.
当收到以上广播后,将分别调用以下的函数:
当系统以AppWidgetProviderInfo中的updatePeriodMillis定义的频率请求更新App Widget时,将调用该函数。
如果没有定义configuration Activity ,当用户添加该App Widget时,也会调用该函数,此时可以做些初始化工作,比如设置View的事件监听者,启动一个临时Service。如果定义了configuration Activity的话,你需要在configuration Activity完成时,发送Intent到AppWidgetProvider来进行该函数的调用.
当App Widget在App Widget Host(比如Home Screen)移除时,将调用该函数.
如果用户向App Widget Host(比如Home Screen)加入App Widget时,在App widget Host中还没有你的App Widget实例,就会调用该函数.。在该函数中可以做些初始话工作,如果你想打开一个数据库连接或其它对多个App Widget实例,只执行一次的操作。
如果用户把App Widget从App Widget Host(比如Home Screen)中移除时,它是App widget Host中的唯一的该App Widget实例的话,就会调用该函数.在该函数你可以清理在
onEnabled(Context)
中做的工作,比如清理临时的数据库。
在收到任何广播时,该函数都会被调用,而且在以上几个函数被调用前进行。一般来说你不用重载该函数。AppWidgetProvider已经提供了默认的实现,它对广播进行分类,并调用上面几个其对应的回调函数(即上面的onUpdate()等)。
注意:在Android1.5中,有onDeleted()函数不能被调用的BUG。为解决这个BUG,你可以参照Group
post中的描述,重写
post中的描述,重写
onReceive()方法以便
onDeleted()函数能经常正常调用。其代码请参照示例2.1.
示例2.1:
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
final int appWidgetId = extras.getInt
(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
this.onDeleted(context, new int[] { appWidgetId });
}
} else {
super.onReceive(context, intent);
}
}
onUpdate()是AppWidgetProvider中最重要的回调函数。因为如果你没定义configuration Activity的话,在App Widget被加入到App Widget Host时该函数就会被调用。如果你的App Widget还要与用户进行交互的话,那么才需要设置用户事件的监听者,并处理其事件。如果你的App Widget不需要创建临时的文件或数据库的话,或其他一些需要清理的工作的话,onUpdate()函数可能是唯一的一个需要你重载的回调函数。
如果你想在App Widget中,让用户点击一个按钮就启动一个Activity的话,可以参照如下的代码:
示例3
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中我们只重载了onUpdate()一个回调函数。在该函数中,我们定了一个用于启动一个Activity的
Widget实例的id.通过该方法用户可以创建App Widget的多个实例,并同时对它们进行更新。然而,只有一个App Widget实例的updatePeriodMillis的schedule来对所有的该App Widget的实例的更新进行管理。比如,一个App Widget的更新schedue是2小时一次,我首先添加了它的一个实例,然后隔了一个小时,又添加它的一个实例,这时它的更新还是通过第一个App Widget的更新schedue来处理,第二个将被忽略掉(他们将每隔两小时更新,而不是两个更新schedue叠加变成每隔一小时)
PendingIntent
,并通过setOnClickPendingIntent(int,
PendingIntent)把
该PendingIntent附在App Widget的一个按钮上 。注意我们是在一个循环中对appWidgetIds这个数组的每项依次进行操作的,该数组包括了通过该AppWidgetProvider创建的所有AppWidget实例的id.通过该方法用户可以创建App Widget的多个实例,并同时对它们进行更新。然而,只有一个App Widget实例的updatePeriodMillis的schedule来对所有的该App Widget的实例的更新进行管理。比如,一个App Widget的更新schedue是2小时一次,我首先添加了它的一个实例,然后隔了一个小时,又添加它的一个实例,这时它的更新还是通过第一个App Widget的更新schedue来处理,第二个将被忽略掉(他们将每隔两小时更新,而不是两个更新schedue叠加变成每隔一小时)
注意:因为AppWidgetProvider扩展自BroadcastReceiver,
所以,你不能保证回调函数完成调用后,AppWidgetProvider还在继续运行。
所以,你不能保证回调函数完成调用后,AppWidgetProvider还在继续运行。
(关于BroadcastReceiver的生命周期的更多内容请参考BroadcastReceiver),如果你的App
Widget的初始化需要多达几秒的时间(比如需要进行WEB请求),而且希望AppWidgetProvider的进程能够长久运行,那么你可以考虑在onUpdate() 中启动一个Service。
Widget的初始化需要多达几秒的时间(比如需要进行WEB请求),而且希望AppWidgetProvider的进程能够长久运行,那么你可以考虑在onUpdate() 中启动一个Service。
在这个Service,你可以更新你的App Widget,这样就不用担心AppWidgetProvider因为ANR错误而被迫关闭。
关于在App Widget中使用Service的示例请参考《Widgets基础篇附件1(WordWidget.java)》
七、接收App Widget的broadcast Intents广播
AppWidgetProvider扩展自BroadcastReceiver,它对App
Widget的广播进行了简单分类,并封装了处理的统一接口,以方便使用。
Widget的广播进行了简单分类,并封装了处理的统一接口,以方便使用。
八,如何编写App Widget Configuration Activity
如果你想让用户在添加一个新的App Widget时,能对该App Widget进行一些个性化的配置的话,你可以通过编写一个App Widget Configuration Activity来实现。在用户添加一个新的App Widget时,configuration
Activity能够自动被App Widget host启动,
Activity能够自动被App Widget host启动,
在该Activity中,你可以让用户对App Widget进行一些个性化的设置,比如颜色,大小,更新频率以及其他的一些设置。
configuration Activity应该像一般的Activity一样,在Android manifest文件中进行申明。App Widget host是通过
来启动configuration Activity,所以configuration Activity必须要能接收该Action.
ACTION_APPWIDGET_CONFIGURE
action来启动configuration Activity,所以configuration Activity必须要能接收该Action.
比如,示例4
示例4:
<activity android:name=".ExampleAppWidgetConfigure"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/> </intent-filter> </activity>
同时在AppWidgetProviderInfo XML文件中,你也必须使用 android:configure属性中指明:当用户在添加一个新的App Widget时,哪个Configuration Activity将被启动。具体可以参照 示例5
示例5:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" ... android:configure="com.example.android.ExampleAppWidgetConfigure" ... > </appwidget-provider>
注意:这里的Activity用的是完整的名字,因为它将在你的APK包外被引用。
以上就是启动一个Configuration Activity,所有需要你做的。现在你需要关心的是Activity本身,在实现一个Configuration Activity时,你需要记住两件非常重要的事情。
首先,App Widget host调用configuration Activity,configuration Activity应该总是能返回一个执行结果。返回结果应该包含同过Intent传给configuration Activity的要添加的App Widget的ID(该ID通过EXTRA_APPWIDGET_ID保存在Intent的extras中)
其次,如果我们App Widget的有configuration Activity,那么当App Widget被创建时,AppWidgetProvider的onUpdate()方法将不会被调用,(当configuration Activity被创建启动的时候,系统将不再发送ACTION_APPWIDGET_UPDATE广播)。当App
Widget被创建的时候,configuration Activity必须负责请求AppWidgetManager对App Widget进行首次更新。然而以后只要更新时间到了,系统还是会发送ACTION_APPWIDGET_UPDATE广播,因此App Widget的onUpdate()方法还是会被调用,以进行App
Widget更新。系统只是在App Widget被创建的时候,不发送ACTION_APPWIDGET_UPDATE广播。
Widget被创建的时候,configuration Activity必须负责请求AppWidgetManager对App Widget进行首次更新。然而以后只要更新时间到了,系统还是会发送ACTION_APPWIDGET_UPDATE广播,因此App Widget的onUpdate()方法还是会被调用,以进行App
Widget更新。系统只是在App Widget被创建的时候,不发送ACTION_APPWIDGET_UPDATE广播。
在后面的文章,我们将讲述如何在configuration Activity中返回执行结果和更新App Widget.
九、如何在configuration Activity中更新App Widget和返回结果
configuration Activity必须负责请求AppWidgetManager对App Widget进行的首次更新。你可以通过
Widget.
AppWidgetManager
直接来更新AppWidget.
以下是在configuration Activity中更新App Widget和退出configuration Activity的主要步骤:
第一、在启动configuration Activity的Intent中得到App Widget的ID。比如,示例6.
示例6:
Intent intent = getIntent(); Bundle extras = intent.getExtras(); if (extras != null) { mAppWidgetId = extras.getInt( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); }
第二、进行App Widget配置的处理。
第三、当App Widget的配置事务被处理完后,调用来AppWidgetManager.getInstance(context)得到AppWidgetManager的一个实例。
比如,示例7.
示例7:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
示例8:
RemoteViews views = new