Android widget 也称为桌面插件,其是android系统应用开发层面的一部分,是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。 Android系统增加了AppWidget 框架,用以支持widget类型应用的开发。
这个东西是用来定义桌面widget的大小,初始状态等等信息的,它的位置应该放在res/xml文件夹下,具体的xml参数如下:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
android:initialLayout="@layout/example_widget"
android:minWidth="200dp"
android:minHeight="50dp"
android:updatePeriodMillis="2000"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen|keyguard"
xmlns:android="http://schemas.android.com/apk/res/android">
<!--
android:minWidth : 最小宽度
android:minHeight : 最小高度
android:updatePeriodMillis : 更新widget的时间间隔(ms)。在实际测试中,发现设置android:updatePeriodMillis的值为5秒时,不管用!跟踪android源代码,发现:
当android:updatePeriodMillis的值小于30分钟时,会被设置为30分钟。也就意味着,即使将它的值设为5秒,实际上也是30分钟之后才更新。因此,我们若向动态的更新widget的某组件,最好通过service、AlarmManager、Timer等方式;本文采用的是service
android:previewImage : 用于指定预览图片。即搜索到widget时,查看到的图片。若没有设置的话,系统为指定一张默认图片
android:initialLayout : 加载到桌面时对应的布局文件
android:resizeMode : widget可以被拉伸的方向。horizontal表示可以水平拉伸,vertical表示可以竖直拉伸
android:widgetCategory : widget可以被显示的位置。home_screen表示可以将widget添加到桌面,keyguard表示widget可以被添加到锁屏界面。
android:initialKeyguardLayout : 加载到锁屏界面时对应的布局文件
-->
</appwidget-provider>
注意:在构造Widget布局时,App Widget支持的布局和控件非常有限,有如下几个:
//App Widget支持的布局:
FrameLayout、LinearLayout、RelativeLayout、GridLayout
//App Widget支持的控件:
AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper
除此之外的所有控件(包括自定义控件)都无法显示,无法显示时,添加出来的widget会显示“加载布局出错”。
AppWidgetProvider是BroadcastReceiver的子类,这个类处理App Widget广播。AppWidgetProvider只接收于App Widget有关系的广播,比如App Widget在updated, deleted, enabled, and disabled。当这些广播发生的时候,AppWidgetProvider会调用一下回调方法:
onUpdate(Context, AppWidgetManager, int[])间隔调用此方法去更新App Widget,间隔时间的设置是在AppWidgetProviderInfo下的updatePeriodMillis属性,同样当用户添加App Widget的时候也被调用。如果你已经声明了一个configuration Activity,用户添加App Widget的时候就不会调用onUpdate,但是在随后的更新中依然会被调用。
onDeleted(Context, int[])当App Widget 从App Widget host 中删除的时候调用。
onDeleted(Context, int[]) 当App Widget 从App Widget host 中删除的时候调用。
onEnabled(Context) 当App Widget第一次创建的时候调用。比如,当用户增加两个同样的App Widget时候,这个方法只在第一次去调用。如果你需要打开一个新的数据库或者其他的设置,而这在所有的App Widgets只需要设置一次的情况下,这个是最好的地方去实现它们。
onDisabled(Context) 当App Widget的最后一个实例从App Widget host中被删除的时候调用。这里可以做一些在onEnabled(Context)中相反的操作,比如删除临时数据库。
onReceive(Context, Intent) 每一个广播的产生都会调用此方法,而且是在上面方法之前被调用。通常不需要实现此方法。
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
Log.d(TAG, "onUpdate " + appWidgetIds.length);
}
@Override
public void onEnabled(Context context) {
super.onEnabled(context);
Log.d(TAG, "onEnabled 第一个widget被创建时调用");
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
Log.d(TAG, "onDeleted 移除");
}
@Override
public void onDisabled(Context context) {
super.onDisabled(context);
Log.d(TAG, "onDisabled 最后一个widget被删除时调用");
}
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive " + intent.getAction());
super.onReceive(context, intent);
}
在AppWidgetProvider中最重要的callback就是onUpdated(),如果你的App Widget接收用户交互事件,就需要在这个callback里面进行处理。
<!-- 声明widget对应的AppWidgetProvider -->
<receiver android:name=".simple.SimpleAppWidgetProvider">
<intent-filter>
<!-- 必须要显示声明的action!因为所有的widget的广播都是通过它来发送的;要接收widget的添加、删除等广播,就必须包含它 -->
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<!-- 自定义action -->
<action android:name="com.example.appwidgetsimple.REFRESH_ALL" />
<action android:name="com.example.appwidgetsimple.action.CLICK" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/simple_app_widget_info" />
</receiver>
(1)接收的action定义为:”android.appwidget.action.APPWIDGET_UPDATE”这表明接收系统发来的有关这个app的所有widget的消息(主要是增加、删除)。
(2)<.meta-data> 指定了 AppWidgetProviderInfo 对应的资源文件:
android:name – 指定metadata名,指定为android.appwidget.provider表示这个data中的数据是AppWidgetProviderInfo 类型的
android:resource – 指定 AppWidgetProviderInfo 对应的资源路径。即,xml/simple_app_widget_info.xml。
现在可以把Widget添加到桌面啦.
AppWidgetService运行于system_process进程,当包含AppWidgetProvider的apk被安装到系统中的时候,AppWidgetService会监听广播,并处理相应的AppWidgetProvider:
private void onPackageBroadcastReceived(Intent intent, int userId) {
390 final String action = intent.getAction();
391 boolean added = false;
392 boolean changed = false;
393 boolean componentsModified = false;
394
395 final String pkgList[];
396 switch (action) {
397 case Intent.ACTION_PACKAGES_SUSPENDED:
398 case Intent.ACTION_PACKAGES_UNSUSPENDED:
399 pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
400 changed = true;
401 break;
402 case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
403 added = true;
404 // Follow through
405 case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
406 pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
407 break;
408 default: {
409 Uri uri = intent.getData();
410 if (uri == null) {
411 return;
412 }
413 String pkgName = uri.getSchemeSpecificPart();
414 if (pkgName == null) {
415 return;
416 }
417 pkgList = new String[] { pkgName };
418 added = Intent.ACTION_PACKAGE_ADDED.equals(action);
419 changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
420 }
421 }
422 if (pkgList == null || pkgList.length == 0) {
423 return;
424 }
425
426 synchronized (mLock) {
427 if (!mUserManager.isUserUnlockingOrUnlocked(userId) ||
428 isProfileWithLockedParent(userId)) {
429 return;
430 }
431 ensureGroupStateLoadedLocked(userId, /* enforceUserUnlockingOrUnlocked */ false);
432
433 Bundle extras = intent.getExtras();
434
435 if (added || changed) {
436 final boolean newPackageAdded = added && (extras == null
437 || !extras.getBoolean(Intent.EXTRA_REPLACING, false));
438
439 for (String pkgName : pkgList) {
440 // Fix up the providers - add/remove/update.
441 componentsModified |= updateProvidersForPackageLocked(pkgName, userId, null);
442
443 // ... and see if these are hosts we've been awaiting.
444 // NOTE: We are backing up and restoring only the owner.
445 // TODO: http://b/22388012
446 if (newPackageAdded && userId == UserHandle.USER_SYSTEM) {
447 final int uid = getUidForPackage(pkgName, userId);
448 if (uid >= 0 ) {
449 resolveHostUidLocked(pkgName, uid);
450 }
451 }
452 }
453 } else {
454 // If the package is being updated, we'll receive a PACKAGE_ADDED
455 // shortly, otherwise it is removed permanently.
456 final boolean packageRemovedPermanently = (extras == null
457 || !extras.getBoolean(Intent.EXTRA_REPLACING, false));
458
459 if (packageRemovedPermanently) {
460 for (String pkgName : pkgList) {
461 componentsModified |= removeHostsAndProvidersForPackageLocked(
462 pkgName, userId);
463 }
464 }
465 }
466
467 if (componentsModified) {
468 saveGroupStateAsync(userId);
469
470 // If the set of providers has been modified, notify each active AppWidgetHost
471 scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
472 }
473 }
474 }
475
当有AppWidgetProvider加入时,查看updateProvidersForPackageLocked 中的的实现,没有添加Widget的时候调用addProviderLocked方法添加到 mProviders 中,
48 private boolean addProviderLocked(ResolveInfo ri) {
2249 if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
2250 return false;
2251 }
2252
2253 if (!ri.activityInfo.isEnabled()) {
2254 return false;
2255 }
2256
2257 ComponentName componentName = new ComponentName(ri.activityInfo.packageName,
2258 ri.activityInfo.name);
2259 ProviderId providerId = new ProviderId(ri.activityInfo.applicationInfo.uid, componentName);
2260
2261 Provider provider = parseProviderInfoXml(providerId, ri, null);
2262 if (provider != null) {
2263 // we might have an inactive entry for this provider already due to
2264 // a preceding restore operation. if so, fix it up in place; otherwise
2265 // just add this new one.
2266 Provider existing = lookupProviderLocked(providerId);
2267
2268 // If the provider was not found it may be because it was restored and
2269 // we did not know its UID so let us find if there is such one.
2270 if (existing == null) {
2271 ProviderId restoredProviderId = new ProviderId(UNKNOWN_UID, componentName);
2272 existing = lookupProviderLocked(restoredProviderId);
2273 }
2274
2275 if (existing != null) {
2276 if (existing.zombie && !mSafeMode) {
2277 // it's a placeholder that was set up during an app restore
2278 existing.id = providerId;
2279 existing.zombie = false;
2280 existing.info = provider.info; // the real one filled out from the ResolveInfo
2281 if (DEBUG) {
2282 Slog.i(TAG, "Provider placeholder now reified: " + existing);
2283 }
2284 }
2285 } else {
2286 mProviders.add(provider);
2287 }
2288 return true;
2289 }
2290
2291 return false;
2292 }
之后,在maskWidgetsViewsLocked 方法中,通过createMaskedWidgetRemoteViews 创建对应的R e mo te View s对象,
private RemoteViews createMaskedWidgetRemoteViews(Bitmap icon, boolean showBadge,
590 PendingIntent onClickIntent) {
591 RemoteViews views = new RemoteViews(mContext.getPackageName(),
592 R.layout.work_widget_mask_view);
593 if (icon != null) {
594 views.setImageViewBitmap(R.id.work_widget_app_icon, icon);
595 }
596 if (!showBadge) {
597 views.setViewVisibility(R.id.work_widget_badge_icon, View.INVISIBLE);
598 }
599 if (onClickIntent != null) {
600 views.setOnClickPendingIntent(R.id.work_widget_mask_frame, onClickIntent);
601 }
602 return views;
603 }
在CallbackHandler中,通过AIDL回调到AppWidgetHost,构建Widget。
private void scheduleNotifyProviderChangedLocked(Widget widget) {
2071 long requestId = UPDATE_COUNTER.incrementAndGet();
2072 if (widget != null) {
2073 // When the provider changes, reset everything else.
2074 widget.updateSequenceNos.clear();
2075 widget.updateSequenceNos.append(ID_PROVIDER_CHANGED, requestId);
2076 }
2077 if (widget == null || widget.provider == null || widget.provider.zombie
2078 || widget.host.callbacks == null || widget.host.zombie) {
2079 return;
2080 }
2081
2082 SomeArgs args = SomeArgs.obtain();
2083 args.arg1 = widget.host;
2084 args.arg2 = widget.host.callbacks;
2085 args.arg3 = widget.provider.info;
2086 args.arg4 = requestId;
2087 args.argi1 = widget.appWidgetId;
2088
2089 mCallbackHandler.obtainMessage(
2090 CallbackHandler.MSG_NOTIFY_PROVIDER_CHANGED,
2091 args).sendToTarget();
2092 }
2093
2094 private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
2095 int appWidgetId, AppWidgetProviderInfo info, long requestId) {
2096 try {
2097 callbacks.providerChanged(appWidgetId, info);
2098 host.lastWidgetUpdateSequenceNo = requestId;
2099 } catch (RemoteException re) {
2100 synchronized (mLock){
2101 Slog.e(TAG, "Widget host dead: " + host.id, re);
2102 host.callbacks = null;
2103 }
2104 }
2105 }
AppWidgetHost通过IAppWidgetService利用Binder机制实现与系统进程中的AppWidgetService通信;
AppWidgetHost有IAppWidgetHost(通过Callbacks)的实现,并在AppWidgetHost.startListening()中注册到AppWidgetService中,实现当Remote端的数据有更新时,通过IAppWidgetHost.updateAppWidget()通知AppWidgetHost更新本地的显示;或者当Remote端的Provider改变时通知AppWidgetHost。
AppWidgetHost创建本地AppWidgetHostView时,会以AppWidgetId和AppWidgetHostView加入mViews: HashMap<Interger,AppWidgetHostView>