天天看点

Android -窗口小部件开发(App Widgets) 3部分 1 App Widget简介2 App Widget主要的相关类介绍

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

 (一)

应用程序窗口小部件App Widgets

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

Android -窗口小部件开发(App Widgets) 3部分 1 App Widget简介2 App Widget主要的相关类介绍

这篇文章描述了如何使用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类,比如:

[html] view plain copy

  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/目录下。

比如:

[html] view plain copy

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

因为桌面布局方向(由此,单元的尺寸)可以变化,按照拇指规则,你应该假设最坏情况单元尺寸是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)可以支持下面这个布局类:

·        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实现:

[java] view plain copy

  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 的第二个实例是在第一个后面一小时添加的,那么它们将按照第一个所定义的周期来更新而第二个被忽略(它们将都是每 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个意图如下:

·         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宿主启动,因此这个活动需要接受这个意图。比如:

[html] view plain copy

  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: [java] view plain copy
    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实例: [java] view plain copy
    1. AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);  
  4. 以一个RemoteViews布局调用updateAppWidget(int, RemoteViews)更新App Widget: [java] view plain copy
    1. RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget);appWidgetManager.updateAppWidget(mAppWidgetId, views);  
  5. 最后,创建返回意图,设置活动结果,并结束这个活动: [java] view plain copy
    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 Widgets) 3部分 1 App Widget简介2 App Widget主要的相关类介绍

这篇文章描述了如何使用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以获取更多关于桌面单元尺寸的信息)

因为桌面布局方向(由此,单元的尺寸)可以变化,按照拇指规则,你应该假设最坏情况单元尺寸是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)可以支持下面这个布局类:

·         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的第二个实例是在第一个后面一小时添加的,那么它们将按照第一个所定义的周期来更新而第二个被忽略(它们将都是每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个意图如下:

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

View Code

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 Widgets) 3部分 1 App Widget简介2 App Widget主要的相关类介绍

  如上图所示,典型的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 -窗口小部件开发(App Widgets) 3部分 1 App Widget简介2 App Widget主要的相关类介绍

计算结果:

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 <[email protected]>
 * 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 <[email protected]>
 * 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 -窗口小部件开发(App Widgets) 3部分 1 App Widget简介2 App Widget主要的相关类介绍

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

Android -窗口小部件开发(App Widgets) 3部分 1 App Widget简介2 App Widget主要的相关类介绍

继续阅读