Chinaunix首页 | 论坛 | 博客
  • 博客访问: 539050
  • 博文数量: 150
  • 博客积分: 5000
  • 博客等级: 大校
  • 技术积分: 1705
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-11 23:29
文章分类

全部博文(150)

文章存档

2011年(9)

2010年(25)

2009年(94)

2008年(22)

我的朋友

分类: LINUX

2009-06-16 17:11:48

前边有几篇针对Android 1.5 SDK “cupcake” 的讨论 (, )。 Android开发小组前些天发布了”early-look” android 1.5 SDK,其目的是让大家预先了解即将在新版本中出现的新功能。而且前边也提到过,在今后会陆续针对这个版本的新功能做一些介绍,帮助大家能更好的利用Android小组成员智慧的“结晶”。

这次将与大家一同了解 AppWidget framework,这个令人期待的新平台内嵌架构可以允许开发者编写”widgets”。用户可以将它直接拖放到主窗口作为互动的接口,或者可以通过主 窗口的Widget辅助某些APPs和后台服务程序的应用,例如:显示calendar中即将开始任务、查看后台正在播放的音乐信息等。

当有新的Widgets被拖放到主窗口的同时,它们也会被分配(或者预留)一个显示更多信息内容的区域,其信息可以是由它所服务的Apps提供,用 户就可以方便快捷的通过Widget对相应的app做出一些快速的操作或回应。可以有两种方式对当前Widget提供更新,其一是通过后台的服务,根据日 程设定来对某些内容作出更新,另外一种方法是直接应用AppWidget framework预置的自动更新框架来满足基本需要(Automatic update mechanism)。(注:这里所提到的更新,并不是指程序自身的更新,而是其所显示的内容或者某些应用级别的更新)。

从较高的实现层级来分析Widget的工作原理。可以发现它其实是BroadcastReceiver,并借助相应xml与其匹配来描述更多 widget自身细节信息,从而可以使AppWidget framework借助broadcast intents与某些Widget通讯。其通讯的信息内容,是借助于RemoteViews将Layout和内容捆绑为独立的对象,Widget作为一个载体将其所包含的内容显示在主窗口。

借用现有程序,可以非常容易为其添加Widget功能。这里提供了一个用于演示其所包含的所有内容(实例所实现的功能是显示维基词典的 “Word of the day”。接下来就一些比较重要和典型的代码做一些详尽的补充和解释。

wiktionary-widget

首先需要创建一个XML Metadata文件作为描述Widget属性的载体,其内容包括这个Widget在主窗口需要被保留的区域、一个初始化的layout和定义更新频率的 属性。针对主窗口预留区域的尺寸,是基于标准尺寸而根据一定比例划分的,并不能随意定义尺寸的数值。下边这个是定义区域尺寸的计算公式(Width)。

Minimum size in dip = (Number of cells * 74dip) - 2dip

在这个例子中的尺寸是二倍的标准宽度(cell width)和一个单元高度(cell height),转化为实际的dip尺寸是:146 dip × 72 dip。同时定义Widget每天更新一次(由例子的需求而定),转化为milliseconds:86,400,000。下边是完成后的XML matedata:

? XML

xmlns:android=""
android:minWidth="146dip"
android:minHeight="72dip"
android:initialLayout="@layout/widget_message"
android:updatePeriodMillis="86400000"
/>

下一步,将前面设置好的XML Matedata与相应的BroadcastReceiver通过manifest.xml相关联:

? XML

android:name=".WordWidget" android:label="@string/widget_name">
>

android:name="android.appwidget.action.APPWIDGET_UPDATE" />
>

android:name="android.appwidget.provider" android:resource="@xml/widget_word" />
>

 

android:name=".WordWidget$UpdateService" />

最后,实现上一操作中提到的BroadcastReceiver,由它来实际的负责有关AppWidget请求,帮助Widgets来管理不同的 Broadcast事件。这里用到一个比较关键的类 APPWidgetProvider(官方暂时还没有提供详尽的文档)。另外需要着重注意的一点,当通过后台的服务来负责Widget实际的更新需求时, 其所应用到的BroadcastReceivers从属于Application Not Responding (ANR) timer,在遇到某些延迟或者通讯响应过长时,ANR会促使系统为用户提供一个选择来强行关闭当前程序。针对基于网络应用情况,通常会遇到因为网络原因 而引起的延迟问题,通过创建相应的Service来避免前边所提到的情况(ANR Timeouts)。以下是实现这个例子中BroadcastReceiver的源代码:

01./**
02. * Define a simple widget that shows the Wiktionary "Word of the day." To build
03. * an update we spawn a background {@link Service} to perform the API queries.
04. */
05.public class WordWidget extends AppWidgetProvider {
06.    @Override
07.    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
08.        int[] appWidgetIds) {
09.        // To prevent any ANR timeouts, we perform the update in a service
10.        context.startService(new Intent(context, UpdateService.class));
11.    }
12. 
13.    public static class UpdateService extends Service {
14.        @Override
15.        public void onStart(Intent intent, int startId) {
16.            // Build the widget update for today
17.            RemoteViews updateViews = buildUpdate(this);
18. 
19.            // Push update for this widget to the home screen
20.            ComponentName thisWidget = new ComponentName(this, WordWidget.class);
21.            AppWidgetManager manager = AppWidgetManager.getInstance(this);
22.            manager.updateAppWidget(thisWidget, updateViews);
23.        }
24. 
25.        /**
26.         * Build a widget update to show the current Wiktionary
27.         * "Word of the day." Will block until the online API returns.
28.         */
29.        public RemoteViews buildUpdate(Context context) {
30.            // Pick out month names from resources
31.            Resources res = context.getResources();
32.            String[] monthNames = res.getStringArray(R.array.month_names);
33. 
34.            // Find current month and day
35.            Time today = new Time();
36.            today.setToNow();
37. 
38.            // Build today's page title, like "Wiktionary:Word of the day/March 21"
39.            String pageName = res.getString(R.string.template_wotd_title,
40.                monthNames[today.month], today.monthDay);
41.            RemoteViews updateViews = null;
42.            String pageContent = "";
43. 
44.            try {
45.                // Try querying the Wiktionary API for today's word
46.                SimpleWikiHelper.prepareUserAgent(context);
47.                pageContent = SimpleWikiHelper.getPageContent(pageName, false);
48.            } catch (ApiException e) {
49.                Log.e("WordWidget", "Couldn't contact API", e);
50.            } catch (ParseException e) {
51.                Log.e("WordWidget", "Couldn't parse API response", e);
52.            }
53. 
54.            // Use a regular expression to parse out the word and its definition
55.            Pattern pattern = Pattern.compile(SimpleWikiHelper.WORD_OF_DAY_REGEX);
56.            Matcher matcher = pattern.matcher(pageContent);
57.            if (matcher.find()) {
58.                // Build an update that holds the updated widget contents
59.                updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_word);
60. 
61.                String wordTitle = matcher.group(1);
62.                updateViews.setTextViewText(R.id.word_title, wordTitle);
63.                updateViews.setTextViewText(R.id.word_type, matcher.group(2));
64.                updateViews.setTextViewText(R.id.definition, matcher.group(3).trim());
65. 
66.                // When user clicks on widget, launch to Wiktionary definition page
67.                String definePage = res.getString(R.string.template_define_url,
68.                        Uri.encode(wordTitle));
69.                Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(definePage));
70.                PendingIntent pendingIntent = PendingIntent.getActivity(context,
71.                        0 /* no requestCode */, defineIntent, 0 /* no flags */);
72.                updateViews.setOnClickPendingIntent(R.id.widget, pendingIntent);
73. 
74.            } else {
75.                // Didn't find word of day, so show error message
76.                updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_message);
77.                CharSequence errorMessage = context.getText(R.string.widget_error);
78.                updateViews.setTextViewText(R.id.message, errorMessage);
79.            }
80.            return updateViews;
81.        }
82. 
83.        @Override
84.        public IBinder onBind(Intent intent) {
85.            // We don't need to bind to this service
86.            return null;
87.        }
88.    }
89.}

回顾文章所应用的例子,如果Widget在特定的条件下需要更新内容时,将通过一个在线的API接口取得最新的数据,然后通过APPWidget framework自动根据我们的需要来请求更新。正如将这个Widget拖放到Home Screen中,每天都会自动的加载最新的”word of the day”。

有关系统资源合理应用的提醒:如果Widgets被设计的过于频繁更新其内容,将会吃掉非常多的系统电量资源,所以为你的Widget更新提供一个 优化的更新周期是非常必要的。可以为用户提供一个可以自由设定更新周期的接口,这样就可以让用户根据当前设备状况来及时作出调整,另外可以让程序根据来自动调整更新周期。

我们期待着你所创造出方便用户使用的Widget,或者提供一些惊奇的想法和大家来共同实现。

阅读(1091) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~