一、前言
所谓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
首先,需要在你的应用程序的 AndroidManifest.xm 文件中声明一个 AppWidgetProvider 类, 比如示例1.
示例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
在<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在屏幕上至少要占用多大的空间 。
默认的Home screen在放置App Widgets时,是以网格为基本单位,而不是以像素,这里的网格是指拥有一定的像素的长方形区域。
因为home屏幕的布局方向可能改变(网格的大小就会随之改变),所以你应该考虑最坏的情况,此时网格拥有74个像素的长和高。
然而你必须减去2个像素,以便避免在计算网格数量时,对像素进行取整,发生错误。
你应该使用下面的公式计算 android:minWidth 和 android:minHeight :
(number of cells * 74) - 2
根据上面的公式,如果想你的App Widget占用 4个网格的宽, 1个网格的高话,那么 android:minWidth 和 android:minHeigh t应该分别为 "294dp" 和 "72dp"
注意:为了让你的App Widget能在各类平台上运行,你的App Widget不要超过4X4网格的尺寸。关于App Widget的设计的更多内容请参考 App Widget Design Guidelines
updatePeriodMillis 属性用于说明App Widget框架请求 AppWidgetProvider 的 onUpdate() 方法来更新 App 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的属性一致。关于此的更多内容请参照后文。
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.
resizeMode属性在Android 3.1中才被添加,它用于说明App Widget重新调整大小的规则。通过该属性,你可以设置在什么方向允许调整App Widget的大小,可以是垂直、水平、或同时垂直和水平两个方向。用户可以按住App Widget来显示大小调整handle,通过在水平或垂直方向拖动handle来调整
App Widget在垂直或水平方向的尺寸。resizeMode属性的值可以是"horizontal", "vertical", "none"和"horizontal|vertical"
icon属性用于说明你的AppWidget在AppWidget picker列表中显示的图标,它应该和AndroidManifest.xml中的<receiver>元素的android:icon的属性一致
label属性用于说明你的AppWidget在AppWidget picker列表中显示的名字,它应该和AndroidManifest.xml中的<receiver>元素的android:label的属性一致。
关于 <appwidget-provider>属性的更多内容请参照 AppWidgetProviderInfo
五、编写App Widget布局文件
你必须在XML文件中定义你的App Widget的布局文件,并把它保存在res/layout/目录下。你可以在 App Widget中使用如下的View,但是请你在开始你的App Widget的布局时请阅读 App Widget Design Guidelines.
如果你熟悉在XML中如何进行布局文件的编写,那么编写App Widget布局文件将非常的简单。因为App Widget的布局是基于 RemoteViews对象, 所以它并不能支持所有的View.
RemoteViews对象支持如下的一些View及其子类:
FrameLayout
LinearLayout
RelativeLayout
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
六、如何使用AppWidgetProvider
AppWidgetProvider 继承于BroadcastReceiver,它对App Widget的广播进行了简单分类,并封装了处理的统一接口,以方便使用。
AppWidgetProvider只接受和App Widget相关的广播,比如App Widget更新, 被删除, enabled, 和disabled的广播.
当收到以上广播后,将分别调用以下的函数:
onUpdate()
当系统以AppWidgetProviderInfo中的updatePeriodMillis定义的频率请求更新App Widget时,将调用该函数。
如果没有定义configuration Activity ,当用户添加该App Widget时,也会调用该函数,此时可以做些初始化工作,比如设置View的事件监听者,启动一个临时Service。 如果定义了configuration Activity的话,你需要在configuration Activity完成时,发送Intent到AppWidgetProvider来进行该函数的调用.
onDeleted(Context, int[])
当App Widget在App Widget Host(比如Home Screen)移除时,将调用该函数.
onEnabled(Context)
如果用户向App Widget Host(比如Home Screen)加入App Widget时,在App widget Host中还没有你的App Widget实例,就会调用该函数.。在该函数中可以做些初始话工作,如果你想打开一个数据库连接或其它对多个App Widget实例,只执行一次的操作。
onDisabled(Context)
如果用户把App Widget从App Widget Host(比如Home Screen)中移除时,它是App widget Host中的唯一的该App Widget实例的话,就会调用该函数.在该函数你可以清理在 onEnabled(Context)
中做的工作,比如清理临时的数据库。
onReceive(Context, Intent)
在收到任何广播时, 该函数都会被调用,而且在以上几个函数被调用前进行。一般来说你不用重载该函数。AppWidgetProvider已经提供了默认的实现,它对广播进行分类,并调用上面几个其对应的回调函数(即上面的onUpdate()等)。
注意
:在Android1.5中,有onDeleted()函数不能被调用的BUG。为解决这个BUG,你可以参照
Group 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的PendingIntent
,并通过 setOnClickPendingIntent(int, PendingIntent)把
该PendingIntent附在App Widget的一个按钮上 。注意我们是在一个循环中
对
appWidgetIds这个数组的每项依次进行操作的,该数组包括了通过该
AppWidgetProvider创建的所有App Widget实例的id.通过该方法用户可以创建App Widget的多个实例,并同时对它们进行更新。然而,只有一个App Widget实例的updatePeriodMillis的schedule来对所有的该App Widget的实例的更新进行管理。比如,一个App Widget的更新schedue是2小时一次,我首先添加了它的一个实例,然后隔了一个小时,又添加它的一个实例,这时它的更新还是通过第一个App Widget的更新schedue来处理,第二个将被忽略掉(他们将每隔两小时更新,而不是两个更新schedue叠加变成每隔一小时)
注意 :因为 AppWidgetProvider 扩展自 BroadcastReceiver , 所以,你不能保证回调函数完成调用后, AppWidgetProvider 还在继续运行。
BroadcastReceiver),如果你的App Widget的初始化需要多达几秒的时间(比如需要进行WEB请求),而且希望AppWidgetProvider的进程能够长久运行,那么你可以考虑在onUpdate() 中启动一个Service。
在这个Service,你可以更新你的App Widget,这样就不用担心AppWidgetProvider因为ANR错误而被迫关闭。
Widgets基础篇附件1(WordWidget.java)》
关于App Widget的简单使用请参考《 Widgets基础篇附件2(ExampleAppWidgetProvider.java) 》
七、接收App Widget的broadcast Intents广播
AppWidgetProvider扩展自BroadcastReceiver,它对App Widget的广播进行了简单分类,并封装了处理的统一接口,以方便使用。
你可以自己实现一个 BroadcastReceiver,重写它的
方法,在里面处理以下的几个Intent:
ACTION_APPWIDGET_UPDATE
ACTION_APPWIDGET_DELETED
ACTION_APPWIDGET_ENABLED
ACTION_APPWIDGET_DISABLED
八,如何编写App Widget Configuration Activity
如果你想让用户在添加一个新的App Widget时,能对该App Widget进行一些个性化的配置的话,你可以通过编写一个 App Widget Configuration Activity 来实现。在用户添加一个新的App Widget时, configuration Activity能够自动被App Widget host启动 ,
在该Activity中,你可以让用户对App Widget进行一些个性化的设置,比如颜色,大小,更新频率以及其他的一些设置。
configuration Activity应该像一般的Activity一样,在Android manifest文件中进行申明。App Widget host是通过 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广播。
在后面的文章,我们将讲述如何在configuration Activity中返回执行结果和更新App Widget.
九、如何在configuration Activity中更新App Widget和返回结果
configuration Activity必须负责请求AppWidgetManager对App Widget进行的首次更新。你可以通过 AppWidgetManager
直接来更新App Widget.
以下是 在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);
第四、调用 updateAppWidget(int, RemoteViews)
函数, 通过RemoteViews对象来更新App Widget.比如,示例8。
示例8:
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.example_appwidget);
appWidgetManager.updateAppWidget(mAppWidgetId, views);
第五、把执行结果放在Intent,用Intent返回结果,并结束configuration Activity。比如,示例9。
示例9:
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
注意:当 configuration Activity首次启动的时候,应该把Activity的返回结果设置为RESULT_CANCELED.
这样如果用户中途退出了configuration Activity的话,系统将通知App Widget host该configuration被取消,这样App Widget将不被添加.