一、前言



所谓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>一些属性的介绍:


minWidthminHeight 属性用于说明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及其子类:


FrameLayoutLinearLayoutRelativeLayoutAnalogClockButtonChronometerImageButtonImageViewProgressBarTextViewViewFlipper

六、如何使用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,重写它的

onReceive(Context, Intent)

方法,在里面处理以下的几个Intent:

ACTION_APPWIDGET_UPDATEACTION_APPWIDGET_DELETEDACTION_APPWIDGET_ENABLEDACTION_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将不被添加.