AppWidget学习总结
一.创建AppWidget.
1.在res/xml下创建一个xml文件,以设置widget占用的空间等信息.如widget_provider.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="20dp"
android:updatePeriodMillis="1000"
android:initialLayout="@layout/widget_main" >
</appwidget-provider>
属性说明
android:minWidth 指定widget占用的宽度
android:minHeight 指定widget占用的高度
android:updatePeriodMillis 定义Widget的更新频率, Android框架每隔一段时间, 会回调AppWidgetProvider类的onUpdate()事件;
以毫秒为单位, 更新时间为30~60分钟, 如果设定30分钟以内无作用.
android:initialLayout 指定Widget的布局文件, 一般将此文件放到res/layout下.
2.实现布局文件,如widget_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#4555" > <TextView android:id="@+id/mTvDate" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:singleLine="true" android:textColor="#f00" android:textSize="18sp" /> <ProgressBar android:id="@+id/mProgressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/mTvDate" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" > <TextView android:id="@+id/mBtnHour" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" android:text="@string/hour_24" android:textColor="#e00" android:textSize="18sp" /> </LinearLayout> </RelativeLayout>
3.实现业务逻辑类,该类继承自AppWidgetProvider.
package com.ahai.hellowidget; import static com.ahai.util.DebugMessage.d; import java.text.SimpleDateFormat; import java.util.Date; import android.annotation.SuppressLint; 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.os.Bundle; import android.os.Handler; import android.widget.RemoteViews; public class DateWidgetProvider extends AppWidgetProvider implements WidgetUpdateTask.Callbacks { public static final String ACTION_REMOTE_CLICK_UPDATE = "action.remote.click.update"; public static final String ACTION_REMOTE_CLICK_HOUR = "action.remote.click.hour"; public static final String DATE_AND_TIME_24HOUR_FORMAT = "yyyy-MM-dd HH:mm:ss"; public static final String DATE_AND_TIME_12HOUR_FORMAT = "yyyy-MM-dd K:mm:ss a"; public static final int RATE_UPDATE_TIME = 1000; private static Handler mHandler; private static WidgetUpdateTask mPendingUpdates; private static boolean mIs24Hour = true; private static int mProgress; @SuppressLint("NewApi") @Override public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); d("[DateWidgetProvider]onAppWidgetOptionsChanged"); } @Override public void onDeleted(Context context, int[] appWidgetIds) { super.onDeleted(context, appWidgetIds); d("[DateWidgetProvider]onDeleted"); ComponentName provider = new ComponentName(context, this.getClass()); int[] ids = AppWidgetManager.getInstance(context).getAppWidgetIds(provider); if (ids == null || ids.length == 0) { if (mPendingUpdates != null) { mPendingUpdates.stop(); mPendingUpdates = null; } } } @Override public void onDisabled(Context context) { super.onDisabled(context); d("[DateWidgetProvider]onDisabled"); } @Override public void onEnabled(Context context) { super.onEnabled(context); d("[DateWidgetProvider]onEnabled"); } @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); d("[DateWidgetProvider]onReceive"); d("intent=" + intent); final String action = intent.getAction(); d("action=" + action); if (ACTION_REMOTE_CLICK_UPDATE.equals(action)) { if (mPendingUpdates != null) { mHandler.removeCallbacks(mPendingUpdates); mHandler.post(mPendingUpdates); } } else if (ACTION_REMOTE_CLICK_HOUR.equals(action)) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_main); mIs24Hour = !mIs24Hour; if (mIs24Hour) { views.setTextViewText(R.id.mBtnHour, context.getString(R.string.hour_24)); } else { views.setTextViewText(R.id.mBtnHour, context.getString(R.string.hour_12)); } ComponentName widgetComponent = new ComponentName(context, this.getClass()); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); appWidgetManager.updateAppWidget(widgetComponent, views); if (mPendingUpdates != null) { mHandler.removeCallbacks(mPendingUpdates); mHandler.post(mPendingUpdates); } } } @Override public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) { super.onRestored(context, oldWidgetIds, newWidgetIds); d("[DateWidgetProvider]onRestored"); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); d("[DateWidgetProvider]onUpdate"); d("context:" + context); if (mHandler == null) { mHandler = new Handler(context.getMainLooper()); } if (mPendingUpdates != null) { mHandler.removeCallbacks(mPendingUpdates); } mPendingUpdates = new WidgetUpdateTask(context, this, mHandler, RATE_UPDATE_TIME); mHandler.post(mPendingUpdates); RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_main); Intent intent = new Intent(ACTION_REMOTE_CLICK_UPDATE); PendingIntent pendingIntentUpdate = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); views.setOnClickPendingIntent(R.id.mTvDate, pendingIntentUpdate); intent = new Intent(ACTION_REMOTE_CLICK_HOUR); PendingIntent pendingIntent12Hour = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); views.setOnClickPendingIntent(R.id.mBtnHour, pendingIntent12Hour); appWidgetManager.updateAppWidget(appWidgetIds, views); } @SuppressLint("SimpleDateFormat") @Override public void onUpdateWidget(Context context) { ComponentName provider = new ComponentName(context, this.getClass()); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); int[] ids = appWidgetManager.getAppWidgetIds(provider); if (ids == null || ids.length == 0) { return; } Date date = new Date(); SimpleDateFormat sdf; if (mIs24Hour) { sdf = new SimpleDateFormat(DATE_AND_TIME_24HOUR_FORMAT); } else { // DateFormatSymbols dfSymbols = new DateFormatSymbols(); sdf = new SimpleDateFormat(DATE_AND_TIME_12HOUR_FORMAT); } String dateString = sdf.format(date); mProgress++; if (mProgress > 10) mProgress = 0; for (int appWidgetId : ids) { // d("update id:" + appWidgetId); RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_main); views.setTextViewText(R.id.mTvDate, dateString); views.setProgressBar(R.id.mProgressBar, 10, mProgress, false); appWidgetManager.updateAppWidget(appWidgetId, views); } } }
AppWidgetProvider是BroadcastReciever的子类,因此实现的类也是一个reciver.
(1)查看AppWidgetProvider的onReceiver方法源码,
public void onReceive(Context context, Intent intent) {
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
}
}
可以发现,当收到AppWidgetManager.ACTION_APPWIDGET_UPDATE消息时,将会自动调用onUpdate方法.因此在写onReceiver方法时,可以不处理此action.
其它的action也会有相应的回调.
AppWidgetManager.ACTION_APPWIDGET_DELETED -- onDeleted
AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED -- onAppWidgetOptionsChanged
AppWidgetManager.ACTION_APPWIDGET_ENABLED -- onEnabled
AppWidgetManager.ACTION_APPWIDGET_DISABLED -- onDisabled
(2)public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
当每创建一个widget时会调用一次onUpdate, appWidgetIds中会传入对应的id,注意,以前创建的widget的id不会传入.
(3)onUpdate,onDeleted等方法传入时,this指针指向的是不同的对象,原因是BroadcastReciever在onReceive执行完毕之后被回收.
此后如需要更新时需要先调用接口getAppWidgetIds取得所有的id值.
ComponentName provider = new ComponentName(context, this.getClass());
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] ids = appWidgetManager.getAppWidgetIds(provider);
if (ids == null || ids.length == 0) {
return;
}
(4)实现类本身是一个BroadcastReceiver,因此可以在manifest.xml文件中注册其它的消息,以便更新界面信息.
对按钮等控件的点击等消息,也通过消息发送.因此需要别的类处理的消息,需要使用BroadcastReceiver注册并响应.
注意,如果在manifest.xml在本身的receiver中已经注册了某个消息,则别的BroadcastReceiver收不到该消息.
(5)设置回调时,对不同的控件需要定义不同的action,不能在同一个action中定义不同的buddle等信息来区分响应消息,这样会导致收到的响应消息中,所有的buddle等信息都是最后设置的那个.
4.修改manifest.xml中<application>节点下,注册实现的reciever类.其中android.appwidget.action.APPWIDGET_UPDATE是必须要添加的action,其它action如果设置了按钮响应等也可以在此添加,此后onReceiver中会收到对应的消息,但其它注册该action的receiver不能收到此消息.
<receiver android:name="com.ahai.hellowidget.DateWidgetProvider" >
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_provider" />
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="action.remote.click.update" />
<action android:name="action.remote.click.hour" />
</intent-filter>
</receiver>
二.相关的类说明
1.RemoteViews, 一个可以在其他应用进程中运行的类, xml布局文件中定义了界面组件, 通过创建RemoteViews对象, 对widget的信息进行更新.
(1) 创建RemoteViews对象
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_main);
(2) 更新RemoteViews信息, 对界面信息的修改都需要调用RemoteViews类的方法updateAppWidget.
注意,updateAppWidget有多个版本.通常用下面的两个方法:
appWidgetManager.updateAppWidget(appWidgetIds, views);
ComponentName widgetComponent = new ComponentName(context, this.getClass());
appWidgetManager.updateAppWidget(widgetComponent, views);
onUpdate中会传入对应的AppWidgetMannager对象.也可以自己取得此对象
appWidgetManager = AppWidgetManager.getInstance(context).
(3)定义回调
Intent intent = new Intent(ACTION_REMOTE_CLICK_UPDATE);
PendingIntent pendingIntentUpdate = PendingIntent.getBroadcast(context,
0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.mTvDate, pendingIntentUpdate);
(4)更新Text和进度条
views.setTextViewText(R.id.mTvDate, dateString);
views.setProgressBar(R.id.mProgressBar, 10, mProgress, false);
2.AppWidgetManger类, 负责管理 AppWidget, 向 AppwidgetProvider 发送通知
bindAppWidgetId(int appWidgetId, ComponentName provider)
通过给定的ComponentName 绑定appWidgetId
getAppWidgetIds(ComponentName provider)
通过给定的ComponentName 获取AppWidgetId
getAppWidgetInfo(int appWidgetId)
通过AppWidgetId 获取 AppWidget 信息
getInstalledProviders()
返回一个List<AppWidgetProviderInfo>的信息
getInstance(Context context)
获取 AppWidgetManger 实例使用的上下文对象
updateAppWidget(int[] appWidgetIds, RemoteViews views)
通过appWidgetId 对传进来的 RemoteView 进行修改,并重新刷新AppWidget 组件
updateAppWidget(ComponentName provider, RemoteViews views)
通过 ComponentName 对传进来的 RemoeteView 进行修改,并重新刷新AppWidget 组件
updateAppWidget(int appWidgetId, RemoteViews views)
通过appWidgetId 对传进来的 RemoteView 进行修改,并重新刷新AppWidget 组件
三.工具类代码
package com.ahai.hellowidget; import java.util.Set; import android.appwidget.AppWidgetManager; import android.content.Context; import android.os.Handler; public class WidgetUpdateTask implements Runnable { public interface Callbacks { void onUpdateWidget(Context context, AppWidgetManager appWidgetManager, Set<Integer> ids); } private Context mContext; private AppWidgetManager mAppWidgetManager; private Set<Integer> mIDs; private boolean mIsStopped; private Callbacks mCallbacks; private Handler mHandler; private int mUpdateRate; public WidgetUpdateTask(Context context, AppWidgetManager appWidgetManager, Set<Integer> ids, Callbacks callbacks, Handler handler, int rateMills) { mContext = context; mAppWidgetManager = appWidgetManager; mIDs = ids; mCallbacks = callbacks; mHandler = handler; mUpdateRate = rateMills; } public void stop() { mIsStopped = true; } @Override public void run() { if (mIsStopped) return; mCallbacks.onUpdateWidget(mContext, mAppWidgetManager, mIDs); mHandler.postDelayed(this, mUpdateRate); } }