1. 什么是App Widgets
2. App Widgets的基本要素
3. 创建App Widgets的基本步骤
3.1 声明应用小部件(App Widgets)
3.2 设置应用小部件提供器信息(AppWidgetProviderInfo)
3.3 定义应用小部件提供器(扩展AppWidgetProvider或扩展BroadcastReceiver)
3.4 创建应用小部件布局(App Widgets Layout)
App Widgets就是微型的应用程序视图,它可以被内嵌在其他的应用程序视图内,并可以接收周期性的更新以改
变其(App Widgets)外观表象。App Widgets则是由应用小部件提供器(AppWidgetProvider)进行发布。顾名
思义,那些能够持有应用小部件的应用组件(application component)则可称之为小部件宿主。
下图是一个常见的小部件,部件的宿主则是Home Screen。
要想创建一个小部件,需要具备以下要素:
1. 应用小部件提供器信息对象(AppWidgetProviderInfoobject)
它描述了一个小部件的元数据,诸如小部件的引用布局,更新频率,大小,以及部件提供器类(
AppWidgetProvider)类,此类应该以XML方式定义,并放置在res/xml/目录下。
2. 小部件提供器(AppWidgetProvider)类
通常,应该定义一个扩展了AppWidgetProvider的新类,在新类中定义一些基本的方法用来与小部件进行程序
上的基于广播事件的交互。当小部件更新,有效,禁用及被删除时,这个类便可以接收到广播。
3. 小部件视图布局(View layout)
此外,还可以实现一个部件配置活动(an App Widget configuration Activity),此配置活动在
用户将部件放置宿主视图上的瞬间被加载,同过它可以对小部件进行配置,诸如大小,内容显示等。
3.1 声明应用小部件(App Widgets)
首先,在应用程序的AndroidManifest.xml文件里声明应用小组件类(此类为扩展了AppWidgetProvider的新
类),例如:
<receiver android:name=".WordWidget" android:label="@string/widget_name"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.test.appwidget.action_updated" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_word" /> </receiver>
<receiver>元素需要指定android:name属性,它指定由小部件使用的AppWidgetProvider的名字。
<intent-filter>元素必须包含一个带有android:name属性的<action>元素。该属性指定AppWidgetProvider
接受ACTION_APPWIDGET_UPDATE广播。這是唯一的须要在此显式地声明的系统广播。应用组件管理器根据需要
给小部件发送其他的广播事件。
<meta-data>元素指定的AppWidgetProviderInfo的资源和描述符, 并需要如下属性:
android:name - 元数据名。 这里使用了android.appwidgets.provider来表示数据作为AppWidgetProviderInof的描述符。
android:resource - AppWidgetProviderInof资源的位置。
3.2 设置应用小部件提供器信息(AppWidgetProviderInfo)
AppWidgetProviderInfo
定义了小部件的基本属性,譬如它的最小布局尺寸,初始的布局资源,更新频率,及
在部件被创建时启动的配置活动(可选)。AppWidgetProviderInfo
对象的是在XML文件内使用一个单独的
<appwidget-provider>元素进行定义的,并将该XML文件保存在工程的res/xml/目录下。
下面是一个AppWidgetProviderInfo
对象的定义:
<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/widget_message" android:configure="com.example.android.ExampleAppWidgetConfigure" android:resizeMode="horizontal|vertical"> </appwidget-provider>
<appwidget-provider>元素下的可用属性可以对应到appWidgetProviderInof类下的相应成员,接下来只简单介绍上述属性:
1. android:minWidth和android:minHeight
这两个属性值指定了在默认情况下部件所占用的最小空间。Home screen能将应用小部件定位在其窗口上是基
于一组网格式的单元格,这些单元格拥有明确的高度和宽度。如果小部件的最小宽度和最小高度与单元格尺寸不匹
配,此时部件的大小会被调整到与单元格大小最为接近的尺寸。
提醒:为了使小部件可以在多个设备上拥有良好的用户体验,部件的最小尺寸应该绝不要比4x4个单元格大小。
2. android:
updatePeriodMillis
该属性指定部件多久(单位:毫秒)需要更新
一次
,更新时调用AppWidgetProvider
内的onUpdate()
回调方
法。同时,部件管理器也在时间上对多久更新部件一次做了限制,如果该属性值小于30分钟,部件将不会被更新。因
此,实际的更新动作并不能保证在
属性值所指定的时间间隔上发生。为了节省设备电池的电量消updatePeriodMillis
耗,尽可能低不要频繁地更新部件。建议部件一个小时不超过一次更新。当然,也可以根据实际需要在部件配置中设
置更新周期,或者通过设置一个Alarm来更新部件,通过这些方式设置的更新周期将打破部件管理器对更新周期的限
制。
提醒:如果在需要更新部件时设备正处于休眠状态,此时设备将被唤醒。如果想频繁地更新部件,或者在设备处于休眠状态时
不更新部件。为此,我们可以基于一个警报(Alarm)来执行部件更新,这使得部件在需要更新时,不会把处于休眠状态的设备唤
醒。为了做到这一点,可以在应用部件提供器(扩展AppWidgetProvider的新类)内重写基类的onEnabled方法内用设置一个重复
式警报(Alarm),类型设置为ELAPSED_REALTIME或者RTC,然后将setupdatePeriodMillis设置为0。在onEnabled内设置警报的
目的是为保证该警报只被设置一次。
3. android:initialLayout
该属性通过资源引用的方式指明了布局资源,在该布局资源内定义了部件的初始布局。
4. android:configure
配置属性定义了在添加应用部件时加载的活动, 通过该配置活动可以使用户对部件属性进行配置。配置活动的存在与否是可选的。
5. android:previewImage
该属性指定了一个部件的预览视图,它是在配置之后当选择这个部件时看起来像的样子。当没有为部件提供预览
视图时,看的到样子是应用的启动图标。
6. android:resizeMode
该属性是由Android 3.1引入的,它指定了调整部件大小的处理规则。在宿主窗口长按住部件时,就会看到调整
部件大小的操作把手,然后水平或者竖直地拖动把手来改变部件的大小。resizeMode的有效值包括"horizontal",
"vertical", and "none",或是它们联合方式,譬
如
。 "horizontal|vertical"
关于<appwidget-provider>元素更多的可用属性可以参考appWidgetProviderInfo类。除了上述提到的属
性,其它未提到的属性会在接下来的文章内逐一进行说明,演示。
3.3 定义应用小部件提供器(扩展AppWidgetProvider或扩展BroadcastReceiver)
3.3.1 扩展AppWidgetProvider
扩展AppWidgetProvider的目的是为了把它作为一个便利类使用,进而减少了用户代码内的工作。同时,可以
根据需要对基类( AppWidgetProvider)内的事件方法进行重写。 AppWidgetProvider类扩展了
BroadcastReceiver类,因而可以处理广播事件。不过,它只接受那些和部件有关联的广播,例如当部件被更新,删
除。当此类广播事件发生时,AppWidgetProvider便会接收它们,并调用以下方法:
onDeleted(Context , int[])
每次部件从宿主内被删除时将会调用此方法,如果有n个此部件从宿主内被删除,该方法则被调用n次。
onEnabled(Context)
当部件的实例第一次被创建时会调用此方法。例如,如果添加了两个此部件到宿主内,则该方法只在第一个此部
件被添加时调用。因此,如果需要为此部件的所有实例执行打开某个数据库或者执行某些设置的操作,且此类操作对
所有实例仅发生一次,则该方法是执行这些操作的最好地方。
onDisabled(Context)
和onEnabled(Context)方法相反,该方法只有在部件的最后一个实例从宿主内移除时调用。因此,这里也是执
行清理工作的地方,例如关闭数据库,断开网络连接等。
onReceive(Context, Intent)
该方法在广播事件发生时,且在上述方法被调用之前被调用。通常不必实现此方法,默认情况下,在基类
(appWidgetProvider)内对部件事件进行过滤,并相应地调用了上述方法。
提醒:在Android 1.5中有个众所周知的问题,即在onDeleted()方法本该被调用时,它并不会被调用。为了解决这个问题,可
以在appWidgetProvider扩展类内实现onReceive()方法,并在此方法内对ACTION_APPWIDGET_DELETED方法进行过滤,然后手
动调用onDeleted()方法。但别忘记依然需要调用基类(appWidgetProvider)的onReceive()方法.
onUpdate():
当部件被添加到宿主窗口内时将会调用该方法,接下来就是每逢部件需要更新时也会调用此方法。由于部件被添
加到宿主窗口内时将会调用该方法,因此在该方法内应该执行必要的设置,譬如为视图定义事件处理方式,开启一个
临时的服务。然而,如果我们定义了部件配置活动,当添加该部件时,此方法不会被调用。但是在后续的更新事件发
生时,仍然会调用该方法。因为存在配置活动时,它来负载部件的第一次更新。
提醒:onUpdate()方法是
AppWidgetProvider内最重要的回调方法,因为每当把部件加入到宿主内时都会调用该方法(除非
使用了部件配置活动)。如果部件需要接受用户的交互事件,则需要在该方法内注册事件处理函数。此外,如果部件不需要创建临
时文件或数据库,或者执行其他不需要清理的工作,那么在AppWidgetProvider 扩展类内只需要定义该方法。
上述回调对应的广播事件可参考:http://developer.android.com/reference/android/appwidget
/AppWidgetManager.html
下面是只实现了onUpdate()方法
的扩展类实例:
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); } } }
通过该扩展类可知:
1. 如果部件不需要临时文件,或打开数据库等操作,只实现了onUpdate()方法就可以了;
2. 在onUpdate()方法内对由该部件提供器创建的且已添加到宿主内的所有部件实例ID进行迭代;
3. 对每个迭代到的部件实例获取其远程视图;
4. 对部件内的按钮(R.id.button)注册事件处理程序,当在宿主内点击该按钮时,ExampleActivity活动启动;
5. 同理, 可以对部件内其他视图或按钮进行设置;
6. 使用指定的部件实例ID对部件进行更新;
7. 一次onUpdate()方法调用可以对所有该部件实例进行更新;
8. 由(7)可知,所有的此部件实例使用一个updatePeriodMillis进行更新;
9. 由(8)可知,最后一次此部件实例的创建
不会重置此前的updatePeriodMillis
(如果不使用部件配置活
动)。例如:更新周期updatePeriodMillis
为每两个小时一次,如果部件的第二个实例在部件的第一个实例创建一个
小时候才被创建。在部件的第一个实例被创建两个小时后,这两个部件实例都会被更新,因此第二实例的
updatePeriodMillis
被忽略了。
提醒:因为AppWidgetProvider
扩展了BroadcastReceiver
, 所以受BroadcastReceiver生命周期的影响,
在此回调在返回之
前不能保证程序可以保持运行。例如在部件创建的过程中需要执行网络数据请求,并且要求程序等待请求完成后继续运行。鉴于
此类情况时,可以考虑在onUpdate方法内启动一个服务来执行网络数据的请求工作,然后onUpdate方法随后返回。因而不用担心
由于ANR错误的发生而导致组件提供器被关闭。
3.3.2 扩展BroadcastReceiver
由于appWidgetProvider只是扩展了BroadcastRecevier, 并在其内部对事件进行过滤后然后再调用相应的方
法。如果我们的扩展类需要接收部件广播事件,只要直接扩展BroadcastRecevier便可,同时重写onReceive方法,
并根据需要定义事件处理方法。
下面是自定义应用小部件提供器的简单实现:
public class WidgetUpdatedReciever extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { if(intent.getAction().equals(AppWidgetManager.ACTION_APPWIDGET_UPDATE)) { //handle the event here } else if(intent.getAction().equals(AppWidgetManager.ACTION_APPWIDGET_ENABLED)) { //handle the event here } else if(intent.getAction().equals(AppWidgetManager.ACTION_APPWIDGET_DISABLED)) { //handle the event here } else if(intent.getAction().equals(AppWidgetManager.ACTION_APPWIDGET_DELETED)) { //handle the event here } } }
应用部件布局是基于远程视图的(RemoteView
), 因此,部件布局并不支持每种布局或控件视图。部件布局可以
使用以下视图对象:
布局类:
FrameLayout
LinearLayout
RelativeLayout
控件类(不包括以下类的派生类):
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
下面是一个小部件布局, 它使用了RelativeLayout布局,并包含有TextView和ImageView控件视图:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/widget" android:layout_width="match_parent" android:layout_height="wrap_content" android:focusable="true" style="@style/WidgetBackground"> <ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:src="@drawable/star_logo" /> <TextView android:id="@+id/word_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="14dip" android:layout_marginBottom="1dip" android:includeFontPadding="false" android:singleLine="true" android:ellipsize="end" style="@style/Text.WordTitle" /> <TextView android:id="@+id/word_type" android:layout_width="0dip" android:layout_height="wrap_content" android:layout_toRightOf="@id/word_title" android:layout_toLeftOf="@id/icon" android:layout_alignBaseline="@id/word_title" android:paddingLeft="4dip" android:includeFontPadding="false" android:singleLine="true" android:ellipsize="end" style="@style/Text.WordType" /> <TextView android:id="@+id/bullet" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/word_title" android:paddingRight="4dip" android:includeFontPadding="false" android:singleLine="true" style="@style/BulletPoint" /> <TextView android:id="@+id/definition" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/word_title" android:layout_toRightOf="@id/bullet" android:paddingRight="5dip" android:paddingBottom="4dip" android:includeFontPadding="false" android:lineSpacingMultiplier="0.9" android:maxLines="4" android:fadingEdge="vertical" style="@style/Text.Definition" /> </RelativeLayout>
最后,通过上述介绍后,我们应该可以创建一个自己应用小部件。在文章《应用小部件(App Widget)---- 基础篇(2)》中,主要通过分析一个实例代码来巩固上述内容。
4. 参考资料
1. http://developer.android.com/guide/topics/appwidgets/index.html
2. http://developer.android.com/resources/samples/WiktionarySimple/index.html
2012年4月26日, 晚毕