Posted by Jeff Sharkey on 20 April 2009 at 6:43 PM
One exciting new feature in the Android 1.5 SDK is the AppWidget framework which allows developers to write "widgets" that people can drop onto their home screen and interact with. Widgets can provide a quick glimpse into full-featured apps, such as showing upcoming calendar events, or viewing details about a song playing in the background.
一个比较激动的新功能将会在 Android1.5SDK 中提供,就是 AppWidgetk 框架,这个框架允许开发者在编写 widgets 时提供一个不在主应用程序桌面而与其交互的功能。如显示即将来临的日历事件或者显示后台播放音乐的信息。
When widgets are dropped onto the home screen, they are given a reserved space to display custom content provided by your app. Users can also interact with your app through the widget, for example pausing or switching music tracks. If you have a background service, you can push widget updates on your own schedule, or the AppWidget framework provides an automatic update mechanism.
当 widgets 退出其界面,我们将给一个保留的空间去显示应用程序的自定内容,用户也可以通过它与你的应用程序交互。例如暂停或选择音乐。如果是后台服务,你可以根据自己的计划去更新你的 widget,AppWidget 也提供了一个自动更新的机制。
At a high level, each widget is a BroadcastReceiver paired with XML metadata describing the widget details. The AppWidget framework communicates with your widget through broadcast intents, such as when it requests an update. Widget updates are built and sent using
在更高的级别,任何 widget 都是一对 BroadcastReceiver 通过 XML 元数据描述 widget 的详细信息。 AppWidget 框架通过广播与你的 widget 通讯。例如当一个请求被更新, widget 更新将建立。
RemoteViews which package up a layout and content to be shown on the home screen.
信息将会显示在桌面,如下图:
You can easily add widgets into your existing app, and in this article I'll walk through a quick example: writing a widget to show the Wiktionary "Word of the day." The full source code is available , but I'll point out the AppWidget-specific code in detail here.
你可以很容把这个功能加入你已经有的程序中,在下面的内容中,我们将通过一个例子:写一个 widget 去显示 Wiktionary “Word of he day” 源码可以点英文中的那个 full source code is available 链接,但我需要指出的这个功能的特别处将会在这里讲述。
First, you'll need some XML metadata to describe the widget , including the home screen area you'd like to reserve, an initial layout to show, and how often you'd like to be updated. The default Android home screen uses a cell-based layout, so it rounds your requested size up to the next-nearest cell size. This can be a little confusing, so here's a quick equation to help:
第一步:你需要用 XML 的元数据描述一下 widget. 包括你在桌面保留的区域,初始化一个布局去显示,还有你多久就更新一下其中的内容。 Android 桌面默认的布局是 cell-based., 你可以申请一个相邻的单元格。在申请时可能有些麻烦,可以通过下面的公式来计算。
Minimum size in dip = (Number of cells * 74dip) - 2dip
In this example, we want our widget to be 2 cells wide and 1 cell tall, which means we should request a minimum size 146dip x 72dip. We're also going to request updates once per day, which is roughly every 86,400,000 milliseconds. Here's what our widget XML metadata looks like:
在这个例子中,我们分别给 widget2 个单元格的宽度和一个单元格的高度,我们应该申请最小的大小为 146dip x 72dip, 我们也将要求每天更新一次。这大概是每八千六百四十〇点〇 〇 〇万毫秒, XML 的元数据配置如下:
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="146dip"
android:minHeight="72dip"
android:initialLayout="@layout/widget_message"
android:updatePeriodMillis="86400000"
/>
Next, let's pair this XML metadata with a BroadcastReceiver in the AndroidManifest :
接下来是下一对 BroadcastReceiver in the AndroidManifest :XML 元数据
<!-- Broadcast Receiver that will process AppWidget updates -->
<receiver android:name=".WordWidget" android:label="@string/widget_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_word" />
</receiver>
<!-- Service to perform web API queries -->
<service android:name=".WordWidget$UpdateService" />
Finally, let's write the BroadcastReceiver code to actually handle AppWidget requests. To help widgets manage all of the various broadcast events, there is a helper class called AppWidgetProvider, which we'll use here. One very important thing to notice is that we're launching a background service to perform the actual update. This is because BroadcastReceivers are subject to the Application Not Responding (ANR) timer, which may prompt users to force close our app if it's taking too long. Making a web request might take several seconds, so we use the service to avoid any ANR timeouts.
最后,我们写一下 BroadcastReceiver 代码处理 AppWidget 请求,去帮助 widget 管理所以类型的广播消息。我们需要继承的类名为 AppWidgetProvider ,一个需要注意的是我们需要一个后台程序去执行 widget 的更新,这是因为 BroadcastReceivers 取决于 ANR 时间,它可能关闭我们的程序当它运行时间比较长的时候,一个 WEB 请求可以需要几分钟,所以我们使用后台服务去避免任何的 ANR 超时。
/**
* Define a simple widget that shows the Wiktionary "Word of the day." To build
* an update we spawn a background {@link Service} to perform the API queries.
*/
public class WordWidget extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
// To prevent any ANR timeouts, we perform the update in a service
context.startService(new Intent(context, UpdateService.class));
}
public static class UpdateService extends Service {
@Override
public void onStart(Intent intent, int startId) {
// Build the widget update for today
RemoteViews updateViews = buildUpdate(this);
// Push update for this widget to the home screen
ComponentName thisWidget = new ComponentName(this, WordWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(thisWidget, updateViews);
}
/**
* Build a widget update to show the current Wiktionary
* "Word of the day." Will block until the online API returns.
*/
public RemoteViews buildUpdate(Context context) {
// Pick out month names from resources
Resources res = context.getResources();
String[] monthNames = res.getStringArray(R.array.month_names);
// Find current month and day
Time today = new Time();
today.setToNow();
// Build today's page title, like "Wiktionary:Word of the day/March 21"
String pageName = res.getString(R.string.template_wotd_title,
monthNames[today.month], today.monthDay);
RemoteViews updateViews = null;
String pageContent = "";
try {
// Try querying the Wiktionary API for today's word
SimpleWikiHelper.prepareUserAgent(context);
pageContent = SimpleWikiHelper.getPageContent(pageName, false);
} catch (ApiException e) {
Log.e("WordWidget", "Couldn't contact API", e);
} catch (ParseException e) {
Log.e("WordWidget", "Couldn't parse API response", e);
}
// Use a regular expression to parse out the word and its definition
Pattern pattern = Pattern.compile(SimpleWikiHelper.WORD_OF_DAY_REGEX);
Matcher matcher = pattern.matcher(pageContent);
if (matcher.find()) {
// Build an update that holds the updated widget contents
updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_word);
String wordTitle = matcher.group(1);
updateViews.setTextViewText(R.id.word_title, wordTitle);
updateViews.setTextViewText(R.id.word_type, matcher.group(2));
updateViews.setTextViewText(R.id.definition, matcher.group(3).trim());
// When user clicks on widget, launch to Wiktionary definition page
String definePage = res.getString(R.string.template_define_url,
Uri.encode(wordTitle));
Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(definePage));
PendingIntent pendingIntent = PendingIntent.getActivity(context,
0 /* no requestCode */, defineIntent, 0 /* no flags */);
updateViews.setOnClickPendingIntent(R.id.widget, pendingIntent);
} else {
// Didn't find word of day, so show error message
updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_message);
CharSequence errorMessage = context.getText(R.string.widget_error);
updateViews.setTextViewText(R.id.message, errorMessage);
}
return updateViews;
}
@Override
public IBinder onBind(Intent intent) {
// We don't need to bind to this service
return null;
}
}
}
And there you have it, a simple widget that will show the Wiktionary "Word of the day." When an update is requested, we read the online API and push the newest data to the surface. The AppWidget framework automatically requests updates from us as needed, such as when a new widget is inserted, and again each day to load the new "Word of the day."
到现在你已经完成了,一个简单的显示 Wiktionary”Word of the day” 的 widget ,当有一个更新请求,我们使用在线 API 推送新的数据给他。 AppWidget 框架的自动更新机制可以满足我们的需求。例如当一个新的 widget 插入,每天再次加载 ”Word of the day”
Finally, some words of wisdom. Widgets are designed for longer-term content that doesn't update very often, and updating more frequently than every hour can quickly eat up battery and bandwidth. Consider updating as infrequently as possible, or letting your users pick a custom update frequency. For example, some people might want a stock ticker to update every 15 minutes, or maybe only four times a day. I'll be talking about additional strategies for saving battery life as part of a session I'm giving at Google I/O .
One last cool thing to mention is that the AppWidget framework is abstracted in both directions, meaning alternative home screens can also contain widgets. Your widgets can be inserted into any home screen that supports the AppWidget framework.
最后一个很酷的事情提的是 AppWidget 框架在两个方向都是抽象的。意思是你可以任选其一,是桌面包含 widgets ,, 你的 widget 也可以插入一个桌面。这些都是 AppWidget 框架所支持的。
We've already written several widgets ourselves, such as the Calendar and Music widgets, but we're even more excited to see the widgets you'll write!
我已经写好了一些 widget ,例如日历,音乐,但我们更高兴看到你写的。