1.概要
本文主要介绍了OPhone 1.5平台上的AppWidget框架结构,并用一个“按需更新”的例子详细讲解了AppWidget的开发过程及其特点。 (作者:吴博)
2.AppWidget框架
AppWidget是OPhone 1.5平台推出的一种崭新的应用程序框架。基于该框架,开发者可以在OPhone及模拟器上开发“外形”类似传统Widget的小应用程序,并将其嵌入到其他应用中。一个最典型的应用场景就是在主屏上灵活的添加,拖动和删除AppWidget应用。
和传统的Widget,如Yahoo的Dashboard和中移动的BAE Widget等相比,AppWidget和它们“形似而神不似”,有着完全不同的技术路线。前者的核心基于Web相关技术,有专门的Widget引擎运行环境,而AppWidget则完全基于OPhone平台的上层应用框架,根据特定的UI控件来展示内容。
AppWidget应用框架中,常用的几个类如下:
AppWidgetProvider: 继承自BroadcastReceiver,在AppWidget应用update, enable, disable和deleted时接受通知。其中,onUpdate,onReceive是最常用到的方法,它们接受更新通知。
AppWidgetProviderInfo: 描述AppWidget的大小,更新频率和初始界面邓信息。以XML文件形式存在于应用的res/xml/目录下。
AppWidgetManager: 负责管理AppWidget, 向AppWidgetProvider发送通知
RemoteViews: 一个可以在其他应用进程中运行的类,是构造AppWidget的核心。目前,OPhone平台上的RemoteViews支持的布局(Layout)类暂时只有FrameLayout, LinearLayout和RelativeLayout,并且不支持自定义类。
3.一个按需更新的AppWidget案例
接下来,我们创建一个监控系统CPU使用率的小应用来具体讲解AppWidget的使用方法。在OPhone平台上,如果使用定时更新策略来更新AppWidget,则会增加电量和CPU资源的开销,因此,在本例中,我们提供了一种按需更新的策略,并且确保该应用不会因时延而造成ANR故障。
3.1 AppWidgetProviderInfo
首先,建立MyAppWidget工程,并在res/xml/目录下添加mywidget_info.xml文件来反应 AppWidgetProviderInfo信息。AppWidget的像素大小取决于它所占的方块多少,其计算公式是(块数 * 74) – 2,因此,我们取高度是72像素,宽度220像素。android:initialLayout设置了AppWidget的布局文件是 mywidget_frame.xml。因为本例采用按需更新的策略,所以没有标注android:updatePeriodMillis属性。
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="220dip" android:minHeight="72dip"
android:initialLayout="@layout/mywidget_frame"
/>
3.2 RemoteView
第二步,建立RemoteViews对应的布局文件。目前, RemoteViews支持的控件暂时有TextView, ImageView, Button,Progressbar等。我们在一个LinearLayout类中添加一个ImageView和一个TextView,并将布局文件命名为mywidget_frame.xml
3.3 AppWidgetProvider
第三步,创建自定义的MyWidgetProvider类,它继承自AppWidgetProvider。在添加AppWidget应用或自动定时更新时,AppWidgetManager会广播动作名字是“android.appwidget.action.APPWIDGET_UPDATE”的 Intent,当onReceive()方法没有被重载时,onUpdate方法会接受到这些广播的Intent。类似普通 BroadcastReceiver类,我们可以重载MyWidgetProvider的onReceive方法,并在其中指定我们想要接受的 Intent。在本例子中,我们自定义的更新动作名为"com.OPhone.update"。在OPhone平台中,对于 BroadcastReceiver类,如果其运行时间过长,有可能会出现ANR(Application Not Response)的故障。因此,我们在onReceive方法中,添加了一个Service来保持应用一直存在。这个Service和 AppWidget应用运行在同一个进程中。通过重载onReceive()方法,我们开辟了直接对AppWidget的更新途径,从而不依赖于 AppWidgetManager发出的更新消息。onReceiver()方法的示例代码如下:
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if("android.appwidget.action.APPWIDGET_UPDATE".equals(action)
||"com.OPhone.update".equals(action))
context.startService(new Intent(context,UpdateService.class));
Log.d(TAG,"Action: "+ action);
Log.d(TAG,"onReceive Process Id: "+ Process.myPid());
}
AppWidget依赖RemoteViews来完成内容的更新,我们在Service的onStart()方法中对新的RemoteViews对象赋值。和普通的控件(如Botton, TextView等)不同的是,RemoteView没有OnClickListener方法,去而代之的是 setOnClickPendingIntent方法---在点击RemoteViews对象时,用PendingIntent的方式来启动一个新的 Activity,新的广播或新Service。AppWidgetManager负责把新生成的Remote更新到AppWidget上。在本例中,我们采用PendingIntent的getBroadcast方法来发送广播,其携带的Intent有自定义的接收动作。onStart()方法的示例代码如下:
public void onStart(Intent intent, int startId) {
String info=null;
//创建RemoteViews
RemoteViews views = new RemoteViews(getPackageName(),
R.layout.mywidget_frame);
try{
info = String.valueOf(getCpuInfo()); //获取CPU消耗信息
}catch(Exception e){
e.printStackTrace();
}
//设置控件
views.setTextViewText(R.id.text_1, info);
views.setImageViewResource(R.id.image_1, R.drawable.icon);
Intent intent_b = new Intent();
intent_b.setAction("com.OPhone.update");
pendingIntent = PendingIntent.getBroadcast(this, 0, intent_b, 0);
//给图片设置响应事件
views.setOnClickPendingIntent(R.id.image_1, pendingIntent);
//获得组建的完整名字
ComponentName thisWidget = new ComponentName(this, MyWidgetProvider.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
//执行内容更新
manager.updateAppWidget(thisWidget, views);
}
在以上代码中,AppWidgetManager类的updateAppWidget方法完成了将RemoteViews对象推送到显示区的作用。如果没有这句操作,则主屏上会显示小应用加载错误的提示。OPhone平台基于Linux,因此getCpuInfo()方法采用读取平台"/proc /stat/"目录下信息的方法来计算CPU耗用信息。
3.4 AndroidManifest.xml
第四步,在AndroidManifest.xml文件中注册名为MyWidgetProvider 的AppWidgetProvider类对象和自定义的Service:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.MyAppWidget"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<receiver android:name="MyWidgetProvider">
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/mywidget_info" />
<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action
android:name="com.OPhone.update"/>
</intent-filter>
</receiver>
<service android:name=".MyWidgetProvider$UpdateService" >
</service>
</application>
</manifest>
其中,android:name属性值“MyWidgetProvider”是AppWidgetProvider的名字,meta-data标签的android:name属性指定了数据类型是“android.appwidget.provider”,而android:resource属性指定了AppWidgetProviderInfo信息的存储资源是mywidget_info.xml.在intent-fliter标签中,我们指定该 AppWidgetProvider接受的通知的Action类型。
3.5运行
运行该工程后,我们通过主屏上的长按响应,选择“外部工具”,添加“AppWidget示例”。在主屏上得到的AppWidget如下图所示:
图下方的蓝色图标及旁边显示的数字及为我们在主屏添加的AppWidget。用户可以随意的拖动,并可以添加多个该类对象。在用户添加多个对象时,每个对象的显示内容都一致,并且只有在用户点击蓝色图标时,才会更新显示的CPU耗用值。
观察程序的log,我们可以发现AppWidget虽然在主屏上显示,但它和主屏却运行在两个不同的进程中。
4.结束语
AppWidget为开发者提供了一种让一个应用显示在另一个应用中的方法。因为主屏使用了AppWidgetHost这个类,因而,在默认情况下,我们编写的AppWidget都会添加到主屏上。如果开发者想在自己编写的应用中添加AppWidget,则还需要实现自定义的 AppWidgetHostView对象,有兴趣的读者可以参考主屏源代码中的实现过程。