Android 之窗口小部件详解(三)  部分转载

时间:2022-12-10 11:17:08

原文地址:http://blog.csdn.net/iefreer/article/details/4626274

(一)

应用程序窗口小部件App Widgets

应用程序窗口小部件(Widget)是微小的应用程序视图,可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个AppWidgetProvider来发布一个Widget。可以容纳其它App Widget的应用程序组件被称为App Widget宿主。下面的截屏显示了一个音乐App Widget。

Android 之窗口小部件详解(三)  部分转载

这篇文章描述了如何使用App Widget Provider发布一个App Widget。

基础知识The Basics

为了创建一个App Widget,你需要下面这些:

AppWidgetProviderInfo对象

描述一个App Widget元数据,比如App Widget的布局,更新频率,以及AppWidgetProvider类。这应该在XML里定义。

AppWidgetProvider类的实现

定义基本方法以允许你编程来和App Widget连接,这基于广播事件。通过它,当这个App Widget被更新,启用,禁用和删除的时候,你都将接收到广播通知。

视图布局

为这个App Widget定义初始布局,在XML中。

另外,你可以实现一个App Widget配置活动。这是一个可选的活动Activity,当用户添加App Widget时加载并允许他在创建时来修改App Widget的设置。

下面的章节描述了如何建立这些组件:

在清单中声明一个应用小部件

首先,在应用程序AndroidManifest.xml文件中声明AppWidgetProvider类,比如:

  1. <receiver android:name="ExampleAppWidgetProvider" >
  2. <intent-filter>
  3. <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
  4. </intent-filter>
  5. <meta-data android:name="android.appwidget.provider"
  6. android:resource="@xml/example_appwidget_info " />
  7. </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/目录下。

比如:

  1. <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:minWidth="294dp" <!-- density-independent pixels -->
  3. android:minHeight="72dp"
  4. android:updatePeriodMillis="86400000" <!-- once per day -->
  5. android:initialLayout="@layout/example_appwidget"
  6. android:configure="com.example.android.ExampleAppWidgetConfigure" >
  7. </appwidget-provider>
下面是
<appwidget-provider>属性的总结:

·         minWidth和minHeight属性的值指定了这个App Widget布局需要的最小区域。

缺省的App Widgets所在窗口的桌面位置基于有确切高度和宽度的单元网格。如果App Widget的最小长宽和这些网格单元的尺寸不匹配,那么这个App Widget将收缩到最接近的单元尺寸。(参见App Widget Design Guidelines以获取更多关于桌面单元尺寸的信息)

因为桌面布局方向(由此,单元的尺寸)可以变化,按照拇指规则,你应该假设最坏情况单元尺寸是像素高和宽。不过,你必须从最后的尺寸中减去以把像素计算过程中产生的任何的整数舍入误差考虑在内。要找到像素密度无关的最小宽度和高度,使用这个公式:
(number of cells * 74) - 2
遵循这个公式,你应该使用72dp为每一个单元高度,294dp为四个单元宽度。

·         updatePerdiodMillis属性定义了App Widget框架调用onUpdate()方法来从AppWidgetProvider请求一次更新的频度。实际更新时间并不那么精确,而且我们建议更新频率越低越好-也许每小时不超过一次以节省电源。你也许还会允许用户在配置中调整这个频率-一些人可能想每分钟一次股票报价,或者一天只要四次。

·         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)可以支持下面这个布局类:

·        FrameLayout

·         LinearLayout

·         RelativeLayout

以及下面的小部件类:

·         AnalogClock

·         Button

·         Chronometer

·         ImageButton

·         ImageView

·         ProgressBar

·         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。)

onDeleted(Context, int[])

当App Widget从宿主中删除时被调用。

onEnabled(Context)

当一个App Widget实例第一次创建时被调用。比如,如果用户添加两个你的App Widget实例,只在第一次被调用。如果你需要打开一个新的数据库或者执行其他对于所有的App Widget实例只需要发生一次的设置,那么这里是完成这个工作的好地方。

onDisabled(Context)

当你的App Widget的最后一个实例被从宿主中删除时被调用。你应该在onEnabled(Context)中做一些清理工作,比如删除一个临时的数据库。

onReceive(Context, Intent)

这个接收到每个广播时都会被调用,而且在上面的回调函数之前。你通常不需要实现这个方法,因为缺省的AppWidgetProvider实现过滤所有App Widget广播并恰当的调用上述方法。

注意:在Android 1.5中,有一个已知问题,onDeleted()方法在该调用时不被调用。为了规避这个问题,你可以像Group post中描述的那样实现onReceive()来接收这个onDeleted()回调。

最重要的AppWidgetProvider回调函数是onUpdated(),因为它是在每个App Widget添加进宿主时被调用的(除非你使用一个配置活动)。如果你的App Widget要接受任何用户交互事件,那么你需要在这个回调函数中注册事件处理器。如果你的App Widget不创建临时文件或数据库,或者执行其它需要清理的工作,那么onUpdated()可能是你需要定义的唯一的回调函数。比如,如果你想要一个带一个按钮的App Widget,当点击时启动一个活动,你可以使用下面的AppWidgetProvider实现:

  1. public class ExampleAppWidgetProvider extends AppWidgetProvider {
  2. public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
  3. final int N = appWidgetIds.length;
  4. // Perform this loop procedure for each App Widget that belongs to this provider
  5. for (int i=0; i<N; i++) {
  6. int appWidgetId = appWidgetIds[i];
  7. // Create an Intent to launch ExampleActivity
  8. Intent intent = new Intent(context, ExampleActivity.class);
  9. PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
  10. // Get the layout for the App Widget and attach an on-click listener to the button
  11. RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
  12. views.setOnClickPendingIntent(R.id.button, pendingIntent);
  13. // Tell the AppWidgetManager to perform an update on the current App Widget
  14. appWidgetManager.updateAppWidget(appWidgetId, views);
  15. }
  16. }
  17. }
这个
AppWidgetProvider
仅定义了
onUpdated()
方法,为了定义一个
PendingIntent

来启动一个活动并使用
setOnClickPendingIntent(int, PendingIntent)
方法把它附着到这个
App Widget
的按钮上。注意它包含了一个遍历
appWidgetIds
中所有项的循环,这是一个
IDs
数组,每个
ID
用来标识由这个
Provider
创建的一个
App Widget
。这样,如果用户创建多于一个这个
App Widget
的实例,那么它们将被同步更新。不过,对于所有的
App Widget
实例,只有一个
updatePeriodMillis
时间表被管理。比如,如果这个更新时间表被定义为每隔两个小时,而且
App Widget
的第二个实例是在第一个后面一小时添加的,那么它们将按照第一个所定义的周期来更新而第二个被忽略(它们将都是每

个小时进行更新,而不是每小时)。

注意:因为这个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)回调函数。你需要注意的个意图如下:

·         ACTION_APPWIDGET_UPDATE

·         ACTION_APPWIDGET_DELETED

·         ACTION_APPWIDGET_ENABLED

·         ACTION_APPWIDGET_DISABLED

创建一个App Widget 配置活动

如果你想让用户在添加一个新的App Widget时调整设置,你可以创建一个App Widget配置活动。这个活动将被App Widget宿主自动启动并允许用户在创建时配置可用的设置,比如App Widget颜色,尺寸,更新周期或者其它功能设置。

这个配置活动应该在Android清单文件中声明为一个通用活动。不过,它将被通过ACTION_APPWIDGET_CONFIGURE活动而被App Widget宿主启动,因此这个活动需要接受这个意图。比如:

  1. <activity android:name=".ExampleAppWidgetConfigure">
  2. <intent-filter>
  3. <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
  4. </intent-filter>
  5. </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>

注意这个活动是用全名声明的,因为它将从你的程序包外被引用。

这就是所有关于配置活动你一开始需要了解的。现在你需要一个真实的活动。这儿就有,不过,当你实现这个活动时记住两件重要的事情:

•  App Widget 宿主调用配置活动而且配置活动应该总是返回一个结果.这个结果应该包含这个通过启动该活动的意图传递的App Widget ID(以EXTRA_APPWIDGET_ID保存在意图的附加段Intent extras中)

•  当这个 App Widget 被创建时将不会调用onUpdate()方法(当一个配置活动启动时,系统将不会发送ACTION_APPWIDGET_UPDATE广播).配置活动应该在 App Widget 第一次被创建时负责从AppWidgetManager请求一个更新.不过,onUpdate()将在后续更新中被调用-只忽略第一次.

参见下面章节的代码片断,该示例说明了如何从配置中返回一个结果并更新这个App Widget.

从配置活动中更新一个App Widget

当一个App Widget使用一个配置活动,那么当配置结束时,就应该由这个活动来更新这个App Widget.你可以直接AppWidgetManager里请求一个更新来这么做.

下面是恰当的更新App Widget 以及关闭配置活动这个过程的一个概要描述:

  1. 首先,从启动这个活动的意图中获取App Widget ID:
    1. Intent intent = getIntent();
    2. Bundle extras = intent.getExtras();
    3. if (extras != null) {
    4. mAppWidgetId = extras.getInt( ApWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);}
  2. 实施你的App Widget 配置。
  3. 当配置完成后,通过调用getInstance(Context)获取一个AppWidgetManager实例:
    1. AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
  4. 以一个RemoteViews布局调用updateAppWidget(int, RemoteViews)更新App Widget:
    1. RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget);appWidgetManager.updateAppWidget(mAppWidgetId, views);
  5. 最后,创建返回意图,设置活动结果,并结束这个活动:
    1. Intent resultValue = new Intent();resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);setResult(RESULT_OK, resultValue);finish();
    2. <span style="font-family:Arial;BACKGROUND-COLOR: #ffffff"></span>

提示: 当你的配置活动第一次打开时,设置活动结果为RESULT_CANCELED。这样,如果用户在结束之前从活动外返回,这个App Widget 宿主会接收到配置取消通知而不会添加这个App Widget。

参见ApiDemos里面的ExampleAppWidgetConfigure.java 例子。

(二)Android开发指南-窗口小部件(App Widgets)在这里有转载

应用程序窗口小部件App Widgets

应用程序窗口小部件(Widget)是微小的应用程序视图,可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个App Widget provider来发布一个Widget。可以容纳其它App Widget的应用程序组件被称为App Widget宿主。下面的截屏显示了一个音乐App Widget。

Android 之窗口小部件详解(三)  部分转载

这篇文章描述了如何使用App Widget Provider发布一个App Widget。

基础知识The Basics

为了创建一个App Widget,你需要下面这些:

AppWidgetProviderInfo 对象

描述一个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以获取更多关于桌面单元尺寸的信息)

因为桌面布局方向(由此,单元的尺寸)可以变化,按照拇指规则,你应该假设最坏情况单元尺寸是像素高和宽。不过,你必须从最后的尺寸中减去以把像素计算过程中产生的任何的整数舍入误差考虑在内。要找到像素密度无关的最小宽度和高度,使用这个公式:
(number of cells * 74) - 2
遵循这个公式,你应该使用72dp为每一个单元高度,294dp为四个单元宽度。

·         updatePerdiodMillis属性定义了App Widget框架调用onUpdate()方法来从AppWidgetProvider请求一次更新的频度。实际更新时间并不那么精确,而且我们建议更新频率越低越好-也许每小时不超过一次以节省电源。你也许还会允许用户在配置中调整这个频率-一些人可能想每分钟一次股票报价,或者一天只要四次。

·         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)可以支持下面这个布局类:

·         FrameLayout

·         LinearLayout

·         RelativeLayout

以及下面的小部件类:

·         AnalogClock

·         Button

·         Chronometer

·         ImageButton

·         ImageView

·         ProgressBar

·         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。)

onDeleted(Context, int[])

当App Widget从宿主中删除时被调用。

onEnabled(Context)

当一个App Widget实例第一次创建时被调用。比如,如果用户添加两个你的App Widget实例,只在第一次被调用。如果你需要打开一个新的数据库或者执行其他对于所有的App Widget实例只需要发生一次的设置,那么这里是完成这个工作的好地方。

onDisabled(Context)

当你的App Widget的最后一个实例被从宿主中删除时被调用。你应该在onEnabled(Context)中做一些清理工作,比如删除一个临时的数据库。

onReceive(Context, Intent)

这个接收到每个广播时都会被调用,而且在上面的回调函数之前。你通常不需要实现这个方法,因为缺省的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的第二个实例是在第一个后面一小时添加的,那么它们将按照第一个所定义的周期来更新而第二个被忽略(它们将都是每个小时进行更新,而不是每小时)。

注意:因为这个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) 回调函数。你需要注意的个意图如下:

·         ACTION_APPWIDGET_UPDATE

·         ACTION_APPWIDGET_DELETED

·         ACTION_APPWIDGET_ENABLED

·         ACTION_APPWIDGET_DISABLED

创建一个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>

注意这个活动是用全名声明的,因为它将从你的程序包外被引用。

这就是所有关于配置活动你一开始需要了解的。现在你需要一个真实的活动。这儿就有,不过,当你实现这个活动时记住两件重要的事情:

•  App Widget 宿主调用配置活动而且配置活动应该总是返回一个结果.这个结果应该包含这个通过启动该活动的意图传递的App Widget ID(以EXTRA_APPWIDGET_ID保存在意图的附加段Intent extras中)

•  当这个 App Widget 被创建时将不会调用onUpdate() 方法(当一个配置活动启动时,系统将不会发送ACTION_APPWIDGET_UPDATE广播).配置活动应该在 App Widget 第一次被创建时负责从AppWidgetManager请求一个更新.不过, onUpdate() 将在后续更新中被调用-只忽略第一次.

参见下面章节的代码片断,该示例说明了如何从配置中返回一个结果并更新这个App Widget.

从配置活动中更新一个App Widget

当一个App Widget使用一个配置活动,那么当配置结束时,就应该由这个活动来更新这个App Widget.你可以直接AppWidgetManager里请求一个更新来这么做.

下面是恰当的更新App Widget 以及关闭配置活动这个过程的一个概要描述:

  1. 首先,从启动这个活动的意图中获取App Widget ID:
    Intent intent = getIntent();Bundle extras = intent.getExtras();if (extras != null) {    mAppWidgetId = extras.getInt(            AppWidgetManager.EXTRA_APPWIDGET_ID,             AppWidgetManager.INVALID_APPWIDGET_ID);}
  2. 实施你的App Widget 配置。
  3. 当配置完成后,通过调用getInstance(Context)获取一个AppWidgetManager实例:
    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
  4. 以一个RemoteViews布局调用updateAppWidget(int, RemoteViews)更新App Widget:
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget);appWidgetManager.updateAppWidget(mAppWidgetId, views);
  5. 最后,创建返回意图,设置活动结果,并结束这个活动:
    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 例子。

Android 之窗口小部件详解(三)  部分转载

 版本号 说明 作者 日期 
1.0  添加App Widge介绍和示例  Sky Wang 2013/06/27 
     

1 App Widget简介

App Widget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个App Widget Provider来发布一个Widget。

本文参考Android官方文本,先介绍App Widget的主要组件,然后再以示例来详细说明。


2 App Widget主要的相关类介绍

2.1 AppWidgetProvider

AppWidgetProvider 继承自 BroadcastReceiver,它能接收 widget 相关的广播,例如 widget 的更新、删除、开启和禁用等。

AppWidgetProvider中的广播处理函数如下:

onUpdate()
  当 widget 更新时被执行。
  同样,当用户首次添加 widget 时,onUpdate() 也会被调用,这样 widget 就能进行必要的设置工作(如果需要的话) 。但是,如果定义了 widget 的 configure属性(即android:config,后面会介绍),那么当用户首次添加 widget 时,onUpdate()不会被调用;之后更新 widget 时,onUpdate才会被调用。

onAppWidgetOptionsChanged()
  当 widget 被初次添加 或者 当 widget 的大小被改变时,执行onAppWidgetOptionsChanged()。你可以在该函数中,根据 widget 的大小来显示/隐藏某些内容。可以通过 getAppWidgetOptions() 来返回 Bundle 对象以读取 widget 的大小信息,Bundle中包括以下信息:
  OPTION_APPWIDGET_MIN_WIDTH -- 包含 widget 当前宽度的下限,以dp为单位。
  OPTION_APPWIDGET_MIN_HEIGHT -- 包含 widget 当前高度的下限,以dp为单位。
  OPTION_APPWIDGET_MAX_WIDTH -- 包含 widget 当前宽度的上限,以dp为单位。
  OPTION_APPWIDGET_MAX_HEIGHT -- 包含 widget 当前高度的上限,以dp为单位。

onAppWidgetOptionsChanged() 是 Android 4.1 引入的。

onDeleted(Context, int[])
  当 widget 被删除时被触发。

onEnabled(Context)
  当第1个 widget 的实例被创建时触发。也就是说,如果用户对同一个 widget 增加了两次(两个实例),那么onEnabled()只会在第一次增加widget时触发。

onDisabled(Context)
  当最后1个 widget 的实例被删除时触发。

onReceive(Context, Intent)
  接收到任意广播时触发,并且会在上述的方法之前被调用。

总结,AppWidgetProvider 继承于 BroadcastReceiver。实际上,App Widge中的onUpdate()、onEnabled()、onDisabled()等方法都是在 onReceive()中调用的;是onReceive()对特定事情的响应函数。参考android源码frameworks/base/core/java/android/appwidget/AppWidgetProvider.java中onReceive()的定义:

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);
}
}

2.2 AppWidgetProviderInfo

AppWidgetProviderInfo描述一个App Widget元数据,比如App Widget的布局,更新频率,以及AppWidgetProvider 类。这个应该在XML里定义。下面以XML示例来对AppWidgetProviderInfo中常用的类型进行说明。

示例XML
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/preview"
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen|keyguard"
android:initialKeyguardLayout="@layout/example_keyguard">
</appwidget-provider>

示例说明
minWidth 和minHeight
  它们指定了App Widget布局需要的最小区域。
  缺省的App Widgets所在窗口的桌面位置基于有确定高度和宽度的单元网格中。如果App Widget的最小长度或宽度和这些网格单元的尺寸不匹配,那么这个App Widget将上舍入(上舍入即取比该值大的最接近的整数——译者注)到最接近的单元尺寸。
  注意:app widget的最小尺寸,不建议比 “4x4” 个单元格要大。关于app widget的尺寸,后面在详细说明。

minResizeWidth 和 minResizeHeight
  它们属性指定了 widget 的最小绝对尺寸。也就是说,如果 widget 小于该尺寸,便会因为变得模糊、看不清或不可用。 使用这两个属性,可以允许用户重新调整 widget 的大小,使 widget 的大小可以小于 minWidth 和 minHeight。
  注意:(01) 当 minResizeWidth 的值比 minWidth 大时,minResizeWidth 无效;当 resizeMode 的取值不包括 horizontal 时,minResizeWidth 无效。
           (02) 当 minResizeHeight 的值比 minHeight 大时,minResizeHeight 无效;当 resizeMode 的取值不包括 vertical 时,minResizeHeight 无效。

updatePeriodMillis
  它定义了 widget 的更新频率。实际的更新时机不一定是精确的按照这个时间发生的。建议更新尽量不要太频繁,最好是低于1小时一次。 或者可以在配置 Activity 里面供用户对更新频率进行配置。 实际上,当updatePeriodMillis的值小于30分钟时,系统会自动将更新频率设为30分钟!关于这部分,后面会详细介绍。
  注意: 当更新时机到达时,如果设备正在休眠,那么设备将会被唤醒以执行更新。如果更新频率不超过1小时一次,那么对电池寿命应该不会造成多大的影响。 如果你需要比较频繁的更新,或者你不希望在设备休眠的时候执行更新,那么可以使用基于 alarm 的更新来替代 widget 自身的刷新机制。将 alarm 类型设置为 ELAPSED_REALTIME 或 RTC,将不会唤醒休眠的设备,同时请将 updatePeriodMillis 设为 0。

initialLayout
  指向 widget 的布局资源文件

configure
  可选属性,定义了 widget 的配置 Activity。如果定义了该项,那么当 widget 创建时,会自动启动该 Activity。

previewImage
  指定预览图,该预览图在用户选择 widget 时出现,如果没有提供,则会显示应用的图标。该字段对应在 AndroidManifest.xml 中 receiver 的 android:previewImage 字段。由 Android 3.0 引入。

autoAdvanceViewId
  指定一个子view ID,表明该子 view 会自动更新。在 Android 3.0 中引入。

resizeMode
  指定了 widget 的调整尺寸的规则。可取的值有: "horizontal", "vertical", "none"。"horizontal"意味着widget可以水平拉伸,“vertical”意味着widget可以竖值拉伸,“none”意味着widget不能拉伸;默认值是"none"。Android 3.1 引入。

widgetCategory
  指定了 widget 能显示的地方:能否显示在 home Screen 或 lock screen 或 两者都可以。它的取值包括:"home_screen" 和 "keyguard"。Android 4.2 引入。

initialKeyguardLayout
  指向 widget 位于 lockscreen 中的布局资源文件。Android 4.2 引入。


3 App Widget布局说明

3.1 添加 widget 到lock screen中

默认情况下(即不设置android:widgetCategory属性),Android是将widget添加到 home screen 中。
  但在Android 4.2中,若用户希望 widget 可以被添加到lock screen中,可以通过设置 widget 的 android:widgetCategory 属性包含keyguard来完成。

当你把 widget 添加到lock screen中时,你可能对它进行定制化操作,以区别于添加到home screen中的情况。 你能够通过 getAppWidgetOptions() 来进行判断 widget 是被添加到lock screen中,还是home screen中。通过 getApplicationOptions() 获取 Bundle对象,然后读取 Bundle 的OPTION_APPWIDGET_HOST_CATEGORY值:若值为 WIDGET_CATEGORY_HOME_SCREEN, 则表示该 widget 被添加到home screen中; 若值为 WIDGET_CATEGORY_KEYGUARD,则表示该 widget 被添加到lock screen中。

另外,你应该为添加到lock screen中的 widget 单独使用一个layout,可以通过 android:initialKeyguardLayout 来指定。而 widget 添加到home screen中的layout则可以通过 android:initialLayout 来指定。

3.2 布局

一 Widget窗口组件

Android 之窗口小部件详解(三)  部分转载

如上图所示,典型的App Widget有三个主要组件:一个边界框(A bounding box),一个框架(a Frame),和控件的图形控件(Widget Controls)和其他元素。App Widget并不支持全部的视图窗口,它只是支持视图窗口的一个子集,后面会详细说明支持哪些视图窗口。
  要设计美观的App Widget,建议在“边界框和框架之间(Widget Margins)”以及“框架和控件(Widget Padding)”之间填留有空隙。在Android4.0以上的版本,系统为自动的保留这些空隙。

二 Widget窗口大小

在AppWidgetProviderInfo中已经介绍了,minWidth 和minHeight 用来指定了App Widget布局需要的最小区域。缺省的App Widgets所在窗口的桌面位置基于有确定高度和宽度的单元网格中。如果App Widget的最小长度或宽度和这些网格单元的尺寸不匹配,那么这个App Widget将上舍入(上舍入即取比该值大的最接近的整数——译者注)到最接近的单元尺寸。
  例如,很多手机提供4x4网格,平板电脑能提供8x7网格。当widget被添加到时,在满足minWidth和minHeight约束的前提下,它将被占领的最小数目的细胞。

粗略计算minWidth和minHeight,可以参考下面表格:

单元格个数
(行 / 列)
对应的设置大小 (dp)
(minWidth / minHeight)
1 40dp
2 110dp
3 180dp
4 250dp
n 70 × n − 30

详细计算minWidth和minHeight,要计算各个区域的大小。以下图为例:

Android 之窗口小部件详解(三)  部分转载

计算结果
minWidth = 144dp + (2 × 8dp) + (2 × 56dp) = 272dp
minHeight = 48dp + (2 × 4dp) = 56dp

3.3 App Widget支持的布局和控件

Widget并不支持所有的布局和控件,而仅仅只是支持Android布局和控件的一个子集。
(01) App Widget支持的布局:
FrameLayout
LinearLayout
RelativeLayout
GridLayout
(02) App Widget支持的控件:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper


4 App Widget应用实例

应用实例需求:建立一个Widget示例,要求Widget能被添加到主屏中,widget包含3个成分:文本、按钮和图片。文本要求:显示提示信息;按钮要求:点击按钮,弹出一个Toast提示框;图片要求:每个5秒随机更新一张图片。

第1步 新建Android工程
新建空白的Android工程,即不需要在建立Activity子类。

第2步 编辑manifest
修改AndroidManifest.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.skywang.widget"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="17" /> <application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" > <!-- 声明widget对应的AppWidgetProvider -->
<receiver android:name=".ExampleAppWidgetProvider" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.skywang.widget.UPDATE_ALL"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />
</receiver> <service android:name=".ExampleAppWidgetService" >
<intent-filter>
<action android:name="android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE" />
</intent-filter>
</service> </application> </manifest>

说明
(01) ExampleAppWidgetProvider是继承于的AppWidgetProvider类,用来响应widget的添加、删除、更新等操作。
(02) android.appwidget.action.APPWIDGET_UPDATE,必须要显示声明的action!因为所有的widget的广播都是通过它来发送的;要接收widget的添加、删除等广播,就必须包含它。
(03) action android:name="com.skywang.widget.UPDATE_ALL,这是我自己添加了,是为了接收服务所发送的更新图片的广播。
(04) <meta-data> 指定了 AppWidgetProviderInfo 对应的资源文件
    android:name -- 指定metadata名,通过android.appwidget.provider来辨别data。
    android:resource -- 指定 AppWidgetProviderInfo 对应的资源路径。即,xml/example_appwidget_info.xml。
(05) ExampleAppWidgetService 是用于更新widget中的图片的服务。
(06) android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE 用于启动服务的action。

第3步 编辑AppWidgetProviderInfo对应的资源文件
在当前工程下新建xml目录(若xml目录不存在的话);并在xml目录下新建example_appwidget_info.xml文件。xml文件内容如下:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="180dp"
android:previewImage="@drawable/preview"
android:initialLayout="@layout/example_appwidget"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen|keyguard"> <!--
android:minWidth : 最小宽度
android:minHeight : 最小高度
android:updatePeriodMillis : 更新widget的时间间隔(ms),"86400000"为1个小时
android:previewImage : 预览图片
android:initialLayout : 加载到桌面时对应的布局文件
android:resizeMode : widget可以被拉伸的方向。horizontal表示可以水平拉伸,vertical表示可以竖直拉伸
android:widgetCategory : widget可以被显示的位置。home_screen表示可以将widget添加到桌面,keyguard表示widget可以被添加到锁屏界面。
android:initialKeyguardLayout : 加载到锁屏界面时对应的布局文件
--> </appwidget-provider>

说明:
(01) android:previewImage,用于指定预览图片。即搜索到widget时,查看到的图片。若没有设置的话,系统为指定一张默认图片。
(02) android:updatePeriodMillis 更新widget的时间间隔(ms)。在实际测试中,发现设置android:updatePeriodMillis的值为5秒时,不管用!跟踪android源代码,发现:
当android:updatePeriodMillis的值小于30分钟时,会被设置为30分钟。也就意味着,即使将它的值设为5秒,实际上也是30分钟之后才更新。因此,我们若向动态的更新widget的某组件,最好通过service、AlarmManager、Timer等方式;本文采用的是service。

android源码中widget服务文件:frameworks/base/services/java/com/android/server/AppWidgetService.java
与 android:updatePeriodMillis相关代码如下:

    ...
private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes
... void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) { ...
// 获取updatePeriodMillis的值
long period = p.info.updatePeriodMillis;
// 当updatePeriodMillis小于30分钟时,设为它为30分钟
if (period < MIN_UPDATE_PERIOD) {
period = MIN_UPDATE_PERIOD;
}
mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + period, period, p.broadcast);
...
}

第4步 编辑example_appwidget.xml等资源文件
新建layout/example_appwidget.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" > <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal" > <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="HomeScreen Widget" /> <Button
android:id="@+id/btn_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show" />
</LinearLayout> <ImageView
android:id="@+id/iv_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/> </LinearLayout>

说明:

(01) example_appwidget布局中,包含了“1个文本,1个按钮和1个图片控件”。

点击下载:本工程中需要用到的图片
将所有的图片放到drawable目录中。这里的drawable广义的,指工程实际用到的图片所在目录;例如,我自己的是放在drawabld-hdmi中。

第5步 编辑ExampleAppWidgetProvider.java
在当前工程下,新建ExampleAppWidgetProvider.java,代码如下:

package com.skywang.widget;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.net.Uri;
import android.widget.RemoteViews;
import android.widget.Toast;
import android.util.Log; import java.util.Set;
import java.util.HashSet;
import java.util.Iterator; /*
* @author : skywang <wangkuiwu@gmail.com>
* description : 提供App Widget
*/ public class ExampleAppWidgetProvider extends AppWidgetProvider {
private static final String TAG = "ExampleAppWidgetProvider"; private boolean DEBUG = false;
// 启动ExampleAppWidgetService服务对应的action
private final Intent EXAMPLE_SERVICE_INTENT =
new Intent("android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE");
// 更新 widget 的广播对应的action
private final String ACTION_UPDATE_ALL = "com.skywang.widget.UPDATE_ALL";
// 保存 widget 的id的HashSet,每新建一个 widget 都会为该 widget 分配一个 id。
private static Set idsSet = new HashSet();
// 按钮信息
private static final int BUTTON_SHOW = 1;
// 图片数组
private static final int[] ARR_IMAGES = {
R.drawable.sample_0,
R.drawable.sample_1,
R.drawable.sample_2,
R.drawable.sample_3,
R.drawable.sample_4,
R.drawable.sample_5,
R.drawable.sample_6,
R.drawable.sample_7,
}; // onUpdate() 在更新 widget 时,被执行,
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
Log.d(TAG, "onUpdate(): appWidgetIds.length="+appWidgetIds.length); // 每次 widget 被创建时,对应的将widget的id添加到set中
for (int appWidgetId : appWidgetIds) {
idsSet.add(Integer.valueOf(appWidgetId));
}
prtSet();
} // 当 widget 被初次添加 或者 当 widget 的大小被改变时,被调用
@Override
public void onAppWidgetOptionsChanged(Context context,
AppWidgetManager appWidgetManager, int appWidgetId,
Bundle newOptions) {
Log.d(TAG, "onAppWidgetOptionsChanged");
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId,
newOptions);
} // widget被删除时调用
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
Log.d(TAG, "onDeleted(): appWidgetIds.length="+appWidgetIds.length); // 当 widget 被删除时,对应的删除set中保存的widget的id
for (int appWidgetId : appWidgetIds) {
idsSet.remove(Integer.valueOf(appWidgetId));
}
prtSet(); super.onDeleted(context, appWidgetIds);
} // 第一个widget被创建时调用
@Override
public void onEnabled(Context context) {
Log.d(TAG, "onEnabled");
// 在第一个 widget 被创建时,开启服务
context.startService(EXAMPLE_SERVICE_INTENT); super.onEnabled(context);
} // 最后一个widget被删除时调用
@Override
public void onDisabled(Context context) {
Log.d(TAG, "onDisabled"); // 在最后一个 widget 被删除时,终止服务
context.stopService(EXAMPLE_SERVICE_INTENT); super.onDisabled(context);
} // 接收广播的回调函数
@Override
public void onReceive(Context context, Intent intent) { final String action = intent.getAction();
Log.d(TAG, "OnReceive:Action: " + action);
if (ACTION_UPDATE_ALL.equals(action)) {
// “更新”广播
updateAllAppWidgets(context, AppWidgetManager.getInstance(context), idsSet);
} else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
// “按钮点击”广播
Uri data = intent.getData();
int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
if (buttonId == BUTTON_SHOW) {
Log.d(TAG, "Button wifi clicked");
Toast.makeText(context, "Button Clicked", Toast.LENGTH_SHORT).show();
}
} super.onReceive(context, intent);
} // 更新所有的 widget
private void updateAllAppWidgets(Context context, AppWidgetManager appWidgetManager, Set set) { Log.d(TAG, "updateAllAppWidgets(): size="+set.size()); // widget 的id
int appID;
// 迭代器,用于遍历所有保存的widget的id
Iterator it = set.iterator(); while (it.hasNext()) {
appID = ((Integer)it.next()).intValue();
// 随机获取一张图片
int index = (new java.util.Random().nextInt(ARR_IMAGES.length)); if (DEBUG) Log.d(TAG, "onUpdate(): index="+index);
// 获取 example_appwidget.xml 对应的RemoteViews
RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.example_appwidget); // 设置显示图片
remoteView.setImageViewResource(R.id.iv_show, ARR_IMAGES[index]); // 设置点击按钮对应的PendingIntent:即点击按钮时,发送广播。
remoteView.setOnClickPendingIntent(R.id.btn_show, getPendingIntent(context,
BUTTON_SHOW)); // 更新 widget
appWidgetManager.updateAppWidget(appID, remoteView);
}
} private PendingIntent getPendingIntent(Context context, int buttonId) {
Intent intent = new Intent();
intent.setClass(context, ExampleAppWidgetProvider.class);
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
intent.setData(Uri.parse("custom:" + buttonId));
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0 );
return pi;
} // 调试用:遍历set
private void prtSet() {
if (DEBUG) {
int index = 0;
int size = idsSet.size();
Iterator it = idsSet.iterator();
Log.d(TAG, "total:"+size);
while (it.hasNext()) {
Log.d(TAG, index + " -- " + ((Integer)it.next()).intValue());
}
}
}
}

说明
(01) 当我们创建第一个widget到桌面时,会执行onEnabled()。在onEnabled()中通过 context.startService(EXAMPLE_SERVICE_INTENT) 启动服务ExampleAppWidgetService。服务的作用就是每隔5秒发送一个ACTION_UPDATE_ALL广播给我们,用于更新widget中的图片。
       仅仅当我们创建第一个widget时才会启动服务,因为onEnabled()只会在第一个widget被创建时才执行。
(02) 当我们删除最后一个widget到桌面时,会执行onDisabled()。在onDisabled()中通过 context.stopService(EXAMPLE_SERVICE_INTENT) 终止服务ExampleAppWidgetService。
       仅仅当我们删除最后一个widget时才会终止服务,因为onDisabled()只会在最后一个widget被删除时才执行。
(03) 本工程中,每添加一个widget都会执行onUpdate()。例外情况:在定义android:configure的前提下,第一次添加widget时不会执行onUpdate(),而是执行android:configure中定义的activity。
(04) onReceive()中,处理两个广播:更新桌面的widget 以及 响应按钮点击广播。
       当收到ACTION_UPDATE_ALL广播时,调用updateAllAppWidgets()来更新所有的widget。
       当收到的广播的categery为Intent.CATEGORY_ALTERNATIVE,并且scheme为BUTTON_SHOW时,对应是按钮点击事件。按钮的监听是在updateAllAppWidgets()中注册的。

第6步 编辑ExampleAppWidgetService.java
在当前工程下,新建ExampleAppWidgetService.java,代码如下:

package com.skywang.widget;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log; /*
* @author : skywang <wangkuiwu@gmail.com>
* description : 周期性更新AppWidget的服务
*/ public class ExampleAppWidgetService extends Service { private static final String TAG="ExampleAppWidgetService"; // 更新 widget 的广播对应的action
private final String ACTION_UPDATE_ALL = "com.skywang.widget.UPDATE_ALL";
// 周期性更新 widget 的周期
private static final int UPDATE_TIME = 5000;
// 周期性更新 widget 的线程
private UpdateThread mUpdateThread;
private Context mContext;
// 更新周期的计数
private int count=0; @Override
public void onCreate() { // 创建并开启线程UpdateThread
mUpdateThread = new UpdateThread();
mUpdateThread.start(); mContext = this.getApplicationContext(); super.onCreate();
} @Override
public void onDestroy(){
// 中断线程,即结束线程。
if (mUpdateThread != null) {
mUpdateThread.interrupt();
} super.onDestroy();
} @Override
public IBinder onBind(Intent intent) {
return null;
} /*
* 服务开始时,即调用startService()时,onStartCommand()被执行。
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
super.onStartCommand(intent, flags, startId); return START_STICKY;
} private class UpdateThread extends Thread { @Override
public void run() {
super.run(); try {
count = 0;
while (true) {
Log.d(TAG, "run ... count:"+count);
count++; Intent updateIntent=new Intent(ACTION_UPDATE_ALL);
mContext.sendBroadcast(updateIntent); Thread.sleep(UPDATE_TIME);
}
} catch (InterruptedException e) {
// 将 InterruptedException 定义在while循环之外,意味着抛出 InterruptedException 异常时,终止线程。
e.printStackTrace();
}
}
}
}

说明

(01) onCreate() 在创建服务时被执行。它的作用是创建并启动线程UpdateThread()。
(02) onDestroy() 在销毁服务时被执行。它的作用是注销线程UpdateThread()。
(03) 服务UpdateThread 每隔5秒,发送1个广播ACTION_UPDATE_ALL。广播ACTION_UPDATE_ALL在ExampleAppWidgetProvider被处理:用来更新widget中的图片。

点击下载源代码

点击查看skywang博客索引

widget在添加到桌面前的效果图

Android 之窗口小部件详解(三)  部分转载

widget在添加到桌面后的效果图

Android 之窗口小部件详解(三)  部分转载