Android AppWidget分析

莘睿
2023-12-01

简述

Android widget 也称为桌面插件,其是android系统应用开发层面的一部分,是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。 Android系统增加了AppWidget 框架,用以支持widget类型应用的开发。

appwidget-provider标签

这个东西是用来定义桌面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的子类。

AppWidgetProvider是BroadcastReceiver的子类,这个类处理App Widget广播。AppWidgetProvider只接收于App Widget有关系的广播,比如App Widget在updated, deleted, enabled, and disabled。当这些广播发生的时候,AppWidgetProvider会调用一下回调方法:

  1. onUpdate(Context, AppWidgetManager, int[])间隔调用此方法去更新App Widget,间隔时间的设置是在AppWidgetProviderInfo下的updatePeriodMillis属性,同样当用户添加App Widget的时候也被调用。如果你已经声明了一个configuration Activity,用户添加App Widget的时候就不会调用onUpdate,但是在随后的更新中依然会被调用。

  2. onDeleted(Context, int[])当App Widget 从App Widget host 中删除的时候调用。

  3. onDeleted(Context, int[]) 当App Widget 从App Widget host 中删除的时候调用。

  4. onEnabled(Context) 当App Widget第一次创建的时候调用。比如,当用户增加两个同样的App Widget时候,这个方法只在第一次去调用。如果你需要打开一个新的数据库或者其他的设置,而这在所有的App Widgets只需要设置一次的情况下,这个是最好的地方去实现它们。

  5. onDisabled(Context) 当App Widget的最后一个实例从App Widget host中被删除的时候调用。这里可以做一些在onEnabled(Context)中相反的操作,比如删除临时数据库。

  6. 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里面进行处理。

注册SimpleAppWidgetProvider

<!-- 声明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添加到桌面啦.

AppWidget原理

AppWidgetService运行于system_process进程,当包含AppWidgetProvider的apk被安装到系统中的时候,AppWidgetService会监听广播,并处理相应的AppWidgetProvider:

  • 监听到有包被加入(Intent.ACTION_PACKAGE_ADDED或Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE)时,执行addProvidersForPackageLocked()/ updateProvidersForPackageLocked() 添加或更新其中的AppWidgetProvider;
  • 监听到有包被移除(Intent.ACTION_PACKAGE_REMOVED或Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)时,执行removeProvidersForPackageLocked()移除其中的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>

 类似资料: