Android 3.0正式版API Level 11中加入了一个天气预报例子,下面是manifest.xml中的关键代码,只是少了xml的编码头:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.weatherlistwidget"> <uses-sdk android:minSdkVersion="11" /> <application android:label="Weather Widget Sample"> <receiver android:name="WeatherWidgetProvider"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widgetinfo" /> </receiver> <service android:name="WeatherWidgetService" android:permission="android.permission.BIND_REMOTEVIEWS" android:exported="false" /> <provider android:name="WeatherDataProvider" android:authorities="com.example.android.weatherlistwidget.provider" /> </application> </manifest>
这里WeatherDataProvider.java的源码为主要是ContentProvider相关的处理,这里作为appWidget的receiver
class WeatherDataPoint { String city; //城市 int degrees; //度数 WeatherDataPoint(String c, int d) { city = c; degrees = d; } } public class WeatherDataProvider extends ContentProvider { public static final Uri CONTENT_URI = Uri.parse("content://com.example.android.weatherlistwidget.provider"); public static class Columns { public static final String ID = "_id"; public static final String CITY = "city"; public static final String TEMPERATURE = "temperature"; } private static final ArrayList<WeatherDataPoint> sData = new ArrayList<WeatherDataPoint>(); @Override public boolean onCreate() { sData.add(new WeatherDataPoint("San Francisco", 13)); sData.add(new WeatherDataPoint("New York", 1)); sData.add(new WeatherDataPoint("Seattle", 7)); sData.add(new WeatherDataPoint("Boston", 4)); sData.add(new WeatherDataPoint("Miami", 22)); sData.add(new WeatherDataPoint("Toronto", -10)); sData.add(new WeatherDataPoint("Calgary", -13)); sData.add(new WeatherDataPoint("Tokyo", 8)); sData.add(new WeatherDataPoint("Kyoto", 11)); sData.add(new WeatherDataPoint("London", -1)); sData.add(new WeatherDataPoint("Nomanisan", 27)); return true; } @Override public synchronized Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { assert(uri.getPathSegments().isEmpty()); final MatrixCursor c = new MatrixCursor( new String[]{ Columns.ID, Columns.CITY, Columns.TEMPERATURE }); for (int i = 0; i < sData.size(); ++i) { final WeatherDataPoint data = sData.get(i); c.addRow(new Object[]{ new Integer(i), data.city, new Integer(data.degrees) }); } return c; } @Override public String getType(Uri uri) { return "vnd.android.cursor.dir/vnd.weatherlistwidget.citytemperature"; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public synchronized int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { assert(uri.getPathSegments().size() == 1); final int index = Integer.parseInt(uri.getPathSegments().get(0)); final MatrixCursor c = new MatrixCursor( new String[]{ Columns.ID, Columns.CITY, Columns.TEMPERATURE }); assert(0 <= index && index < sData.size()); final WeatherDataPoint data = sData.get(index); data.degrees = values.getAsInteger(Columns.TEMPERATURE); getContext().getContentResolver().notifyChange(uri, null); return 1; } }
上面可以看到,对于插入和删除没有做过多的处理,对于天气更新给出了详细的解决方法。
有关 WeatherWidgetProvider.java 主要是appWidget的核心,为provider
class WeatherDataProviderObserver extends ContentObserver { //监控数据库的变化 private AppWidgetManager mAppWidgetManager; private ComponentName mComponentName; WeatherDataProviderObserver(AppWidgetManager mgr, ComponentName cn, Handler h) { super(h); mAppWidgetManager = mgr; mComponentName = cn; } @Override public void onChange(boolean selfChange) { mAppWidgetManager.notifyAppWidgetViewDataChanged( mAppWidgetManager.getAppWidgetIds(mComponentName), R.id.weather_list); } } public class WeatherWidgetProvider extends AppWidgetProvider { public static String CLICK_ACTION = "com.example.android.weatherlistwidget.CLICK"; public static String REFRESH_ACTION = "com.example.android.weatherlistwidget.REFRESH"; public static String EXTRA_CITY_ID = "com.example.android.weatherlistwidget.city"; private static HandlerThread sWorkerThread; private static Handler sWorkerQueue; private static WeatherDataProviderObserver sDataObserver; public WeatherWidgetProvider() { sWorkerThread = new HandlerThread("WeatherWidgetProvider-worker"); //开一个线程,这里用到了HandlerThread sWorkerThread.start(); sWorkerQueue = new Handler(sWorkerThread.getLooper()); //不了解Thread的Looper可以看下这个例子比较简单清晰 } @Override public void onEnabled(Context context) { //当appWidget添加到桌面上时 final ContentResolver r = context.getContentResolver(); if (sDataObserver == null) { final AppWidgetManager mgr = AppWidgetManager.getInstance(context); final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class); sDataObserver = new WeatherDataProviderObserver(mgr, cn, sWorkerQueue); r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver); //注册监控数据库变化的回调 } } @Override public void onReceive(Context ctx, Intent intent) { final String action = intent.getAction(); if (action.equals(REFRESH_ACTION)) { //接收数据库改变的回调广播 final Context context = ctx; sWorkerQueue.removeMessages(0); sWorkerQueue.post(new Runnable() { @Override public void run() { final ContentResolver r = context.getContentResolver(); final Cursor c = r.query(WeatherDataProvider.CONTENT_URI, null, null, null, null); final int count = c.getCount(); final int maxDegrees = 96; r.unregisterContentObserver(sDataObserver); //首先取消数据库监控 for (int i = 0; i < count; ++i) { final Uri uri = ContentUris.withAppendedId(WeatherDataProvider.CONTENT_URI, i); final ContentValues values = new ContentValues(); values.put(WeatherDataProvider.Columns.TEMPERATURE, new Random().nextInt(maxDegrees)); r.update(uri, values, null, null); //更新数据库记录层 } r.registerContentObserver(WeatherDataProvider.CONTENT_URI, true, sDataObserver); //重新设置监视数据库 final AppWidgetManager mgr = AppWidgetManager.getInstance(context); final ComponentName cn = new ComponentName(context, WeatherWidgetProvider.class); mgr.notifyAppWidgetViewDataChanged(mgr.getAppWidgetIds(cn), R.id.weather_list); //提示Widget有数据更新并刷新UI } }); } else if (action.equals(CLICK_ACTION)) { final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); final String city = intent.getStringExtra(EXTRA_CITY_ID); final String formatStr = ctx.getResources().getString(R.string.toast_format_string); Toast.makeText(ctx, String.format(formatStr, city), Toast.LENGTH_SHORT).show(); } super.onReceive(ctx, intent); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { for (int i = 0; i < appWidgetIds.length; ++i) { final Intent intent = new Intent(context, WeatherWidgetService.class); //当桌面上有多个这个相同的appWidget需要分别处理 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout); rv.setRemoteAdapter(appWidgetIds[i], R.id.weather_list, intent); rv.setEmptyView(R.id.weather_list, R.id.empty_view); final Intent onClickIntent = new Intent(context, WeatherWidgetProvider.class); onClickIntent.setAction(WeatherWidgetProvider.CLICK_ACTION); onClickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); onClickIntent.setData(Uri.parse(onClickIntent.toUri(Intent.URI_INTENT_SCHEME))); final PendingIntent onClickPendingIntent = PendingIntent.getBroadcast(context, 0, onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT); rv.setPendingIntentTemplate(R.id.weather_list, onClickPendingIntent); final Intent refreshIntent = new Intent(context, WeatherWidgetProvider.class); refreshIntent.setAction(WeatherWidgetProvider.REFRESH_ACTION); final PendingIntent refreshPendingIntent = PendingIntent.getBroadcast(context, 0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT); rv.setOnClickPendingIntent(R.id.refresh, refreshPendingIntent); appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } super.onUpdate(context, appWidgetManager, appWidgetIds); } }
对于WeatherWidgetService.java这个Service集成于RemoteViewsService,主要是UI上的处理
public class WeatherWidgetService extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new StackRemoteViewsFactory(this.getApplicationContext(), intent); } } class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private Context mContext; private Cursor mCursor; private int mAppWidgetId; public StackRemoteViewsFactory(Context context, Intent intent) { mContext = context; mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); } public void onCreate() { } public void onDestroy() { if (mCursor != null) { mCursor.close(); } } public int getCount() { return mCursor.getCount(); } public RemoteViews getViewAt(int position) { String city = "Unknown City"; int temp = 0; if (mCursor.moveToPosition(position)) { final int cityColIndex = mCursor.getColumnIndex(WeatherDataProvider.Columns.CITY); final int tempColIndex = mCursor.getColumnIndex( WeatherDataProvider.Columns.TEMPERATURE); city = mCursor.getString(cityColIndex); temp = mCursor.getInt(tempColIndex); } final String formatStr = mContext.getResources().getString(R.string.item_format_string); final int itemId = (position % 2 == 0 ? R.layout.light_widget_item : R.layout.dark_widget_item); RemoteViews rv = new RemoteViews(mContext.getPackageName(), itemId); rv.setTextViewText(R.id.widget_item, String.format(formatStr, temp, city)); final Intent fillInIntent = new Intent(); final Bundle extras = new Bundle(); extras.putString(WeatherWidgetProvider.EXTRA_CITY_ID, city); fillInIntent.putExtras(extras); rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent); return rv; } public RemoteViews getLoadingView() { return null; } public int getViewTypeCount() { return 2; } public long getItemId(int position) { return position; } public boolean hasStableIds() { return true; } public void onDataSetChanged() { if (mCursor != null) { mCursor.close(); } mCursor = mContext.getContentResolver().query(WeatherDataProvider.CONTENT_URI, null, null, null, null); } }