widget主要功能就是显示一些信息。我今天编写一个很简单的作为widget,显示时间、日期、星期几等信息。需要显示时间信息,那就需要实时更新,实时更新就需要定时任务,1秒钟更新一次。
这个时间Widget我是参考(Android应用开发揭秘)书里面的一个demo例子做的,效果如下图:
1.AppWidget框架
AppWidget框架主要包括以下类:
- AppWidgetProvider:继承自BroadcastReceiver,在AppWidget应用update、enable、disable和deleted时接收通知。其中,onUpdate、onReceive是最常用到的方法,他们接收更新通知。
- AppWidgetProviderInfo:描述Appwidget的大小,更新频率和初始界面等信息,已XML文件存在于应用的res/xml/目录下。
- AppWidgetManager:负责管理AppWidget,向AppWidgetProvider发送通知。
- RemoteViews:一个可以再其他应用进程中运行的类,是构造AppWidget的核心。
2.Widget开发流程
- appWidget配置
代码清单:widget.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/date_layout"
android:initialLayout="@layout/date_layout"
android:minHeight="72dp"
android:minWidth="72dp"
android:previewImage="@drawable/preview"
android:updatePeriodMillis="0" >
</appwidget-provider>
•android:initialLayout 指定界面布局的Layout文件,和activity的Layout一样
•android:minWidth 你的widget的最小宽度。根据Layout的单元格计算(72*格子数-2)
•android:minHeigh 你的widget的最小高度。计算方式和minwidth一样。(对这个不了解可以看我Launcher分析文章)
•android:updatePerioMillis 使用系统定时更新服务,单位毫秒。
这里需要说明android:updatePerioMillis的问题,系统为了省电,默认是30分钟更新一次,如果你设置的值比30分钟小,系统也是30分钟才会更新一次。对于我们做时间Widget来说,显然不靠谱。所以只能自己编写一个Alarm定时服务更新。
- 继承appwidgetProvider的widget
代码清单:TodayDate.java
package com.hutest.android;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.text.format.Time;
import android.widget.RemoteViews;
import android.widget.Toast;
import com.example.widget.R;
public class TodayDate extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
RemoteViews updateView = buildUpdate(context);
appWidgetManager.updateAppWidget(appWidgetIds, updateView);
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
private String[] months = { "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月",
"九月", "十月", "十一月", "十二月" };
private String[] dayofWeeks = { "星期一 ", "星期二", "星期三", "星期四", "星期五", "星期六",
"星期日" };
private RemoteViews buildUpdate(Context context) {
RemoteViews updateView = null;
Time time = new Time();
time.setToNow();
//使用Service更新时间
Intent launchIntent = new Intent(context,UpdateService.class);
PendingIntent intent = PendingIntent.getService(context, 0,
launchIntent, 0);
//使用Alarm定时更新界面数据
AlarmManager alarm = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
alarm.setRepeating(AlarmManager.RTC, time.toMillis(true), 1*1000, intent);
return updateView;
}
}
package com.hutest.android;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.text.format.Time;
import android.widget.RemoteViews;
import com.example.widget.R;
public class UpdateService extends Service {
private String[] months = { "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月",
"九月", "十月", "十一月", "十二月" };
private String[] dayofWeeks = { "星期一 ", "星期二", "星期三", "星期四", "星期五", "星期六",
"星期日" };
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
UpdateWidget(this);
return super.onStartCommand(intent, flags, startId);
}
private void UpdateWidget(Context context) {
RemoteViews updateView = null;
Time time = new Time();
time.setToNow();
String month = months[time.month];
String year = String.valueOf(time.year);
String monthday = String.valueOf(time.monthDay);
String weekday = dayofWeeks[time.weekDay - 1];
String currenttime = String.format("%02d:%02d:%02d", time.hour,
time.minute, time.second);
updateView = new RemoteViews(context.getPackageName(),
R.layout.date_layout);
updateView.setTextViewText(R.id.textmonth, month);
updateView.setTextViewText(R.id.textyear, year);
updateView.setTextViewText(R.id.textday, monthday);
updateView.setTextViewText(R.id.textweek, weekday);
updateView.setTextViewText(R.id.texttime, currenttime);
//点击widget,启动日历
Intent launchIntent = new Intent();
launchIntent.setComponent(new ComponentName("com.android.calendar",
"com.android.calendar.LaunchActivity"));
launchIntent.setAction(Intent.ACTION_MAIN);
launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
PendingIntent intentAction = PendingIntent.getActivity(context, 0,
launchIntent, 0);
updateView.setOnClickPendingIntent(R.id.base, intentAction);
AppWidgetManager appWidgetManager = AppWidgetManager
.getInstance(context);
appWidgetManager.updateAppWidget(new ComponentName(context,
TodayDate.class), updateView);
}
}
- AndroidManifest.xml配置
<manifest package="com.example.widget"
android:versionCode="1"
android:versionName="1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="19" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<receiver android:label="日历" android:name="com.hutest.android.TodayDate">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/widget"/>
</receiver>
<service android:name="com.hutest.android.UpdateService"></service>
</application>
</manifest>
有一点需要说明的是RemoteViews
updateView = new RemoteViews(context.getPackageName(),R.layout.date_layout);
从我们的界面配置文件生成一个远程Views更新的对象,这个可以在不同进程中操作别的进程的View。因为Widget是运行在Launcher的进程里面的,而不是一个独立的进程。这也是一种远程访问机制。最后就是加了一个点击桌面Widget启动一个程序的功能,也是使用了PendingIntent的方法。