1. 程式人生 > >AppWidget原始碼分析(1)---介面類

AppWidget原始碼分析(1)---介面類

最近專案中接觸到AppWidget,相對來說這部分比較簡單,所以趁著空餘時間詳細閱讀了AppWidget的原始碼。這篇文章主要是從原始碼上分析AppWidget中API類的相關原理,相關類的簡單功能介紹和實現原理。關於使用,建議看指導文件

簡述

AppWidget相關的API類(供我們應用開發者使用的類)主要有:

AppWidgetProvider:繼承這個類,來提供Appwidget。

AppWidgetManager:提供了AppWidget的管理介面,比如更新,繫結AppWidget id,根據Component獲取AppWidget id等等。

RemoteView:能夠跨程序更新的View。

AppWidgetHost:與AppWidgt 服務互動的類,獲取App widget,顯示出來

AppwidgetHostView:實際上顯示出來的View

他們之間的關係簡略來講如下:

這裡寫圖片描述

AppWidget Service 是一些類的集合,是AppWidget的核心服務,在之後的文章會介紹,這篇就不介紹了。下面詳細介紹每個類的功能以及實現原理。

AppWidgetProvider

這是我們在建立AppWidget的時候,需要去實現的類,它有幾個重要的方法:


public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
       int
appWidgetId, Bundle newOptions) // 當Widget的佈局到新的大小的時候會被呼叫 public void onDeleted(Context context, int[] appWidgetIds) // 當某個Widget被移除的時候回撥 public void onDisabled(Context context) // 當最後一個Widget被移除的時候回撥 public void onEnabled(Context context) // 當第一個Widget被新增的時候回撥 public void onRestored(Context context, int
[] oldWidgetIds, int[] newWidgetIds) // 當Widget從快取的Widget恢復時 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) //當Widget需要被提供RemoteView的時候,每次新增Widget的時候會呼叫 public void onReceive(Context context, Intent intent) // 後面介紹

這幾個方法是我們在繼承AppWidgetProvider的時候,可以根據自己的需要來實現的方法。註釋裡面是每個方法被回撥的時機。

實際上如果去檢視AppWidgetProvider的原始碼,你會發現AppWidgetProvider是繼承自BroadcastReceiver的,也就是說它是一種廣播。所以它也有一個onReceive方法,這個方法我前面特意沒有說明什麼時候呼叫,其實就是收到廣播的時候回撥。看看onReceive方法的實現,你會發現前面的那幾個方法都是在onReceiver中呼叫的,每一種方法都對應著一種廣播Action:


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);
           }
       }
   } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
       Bundle extras = intent.getExtras();
       if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
           final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
           this.onDeleted(context, new int[] { appWidgetId });
       }
   } else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
       Bundle extras = intent.getExtras();
       if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
               && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
           int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
           Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
           this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
                   appWidgetId, widgetExtras);
       }
   } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
       this.onEnabled(context);
   } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
       this.onDisabled(context);
   } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
       Bundle extras = intent.getExtras();
       if (extras != null) {
           int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
           int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
           if (oldIds != null && oldIds.length > 0) {
               this.onRestored(context, oldIds, newIds);
               this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
           }
       }
   }
}

AppWidgetProvider是一個BroadcastReceiver,所以它的onUpdate, onEnabled等方法每次被呼叫前,都會呼叫onReceive方法。我們繼承AppWidgetProvider,也可以過載onReceiver方法,修改onUpdate等方法被呼叫的時機,當然並不推薦這麼做,破壞介面的語義會非常危險。另外一方面它是一個BroadcastReceiver,所以它就具備BroadcastReceiver的各種特性(需要說明的是AppWidgetProvider要在manifest檔案中靜態註冊,因為系統在安裝apk時需要知道應用有哪些方法桌面Widget),它能夠註冊自定義的ACTION,每次執行的時候是會建立一個新的AppWidgetProvider例項,Context不能繫結服務,執行時間不能超過10秒等等。

AppWidgetProvider是一個BroadcastReceiver,明白這一點其實也就是理解AppWidget實現原理了。在onUpdate中提供了AppWidgetManager引數,這個AppWidgetMananger又是什麼呢?下面來介紹一下。

AppWidgetManager

名如其義,AppWidgetManager你可以理解為AppWidget的管理介面(實際上它只提供了管理AppWidget的部分介面,後面的文章會介紹真正管理AppWidget的介面,也就是AppWidget 的client)。我們可以用AppWidgetManager來更新AppWidget的內容,給AppWidget繫結id。AppWidgetManager是在實現AppWidgetProvider的時候,經常會用到的類,可以用AppWidgetManager.updateAppWidget來更新AppWidget。如果是有AdapterView的AppWidget,可以用AppWidgetManager.notifyAppWidgetViewDataChanged來通知Adapter的變化,用partiallyUpdateAppWidget部分更新Widget。這幾個方法是經常在AppWidgetProvider的onUpdate方法中使用的。這幾個方法的原型如下:




public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId)



public void partiallyUpdateAppWidget(int appWidgetId, RemoteViews views)



public void updateAppWidget(int[] appWidgetIds, RemoteViews views)

另外可以通過getAppWidgetIds獲取AppWidgetProvider元件對應的AppWidget的Id陣列,我們經常通過這個方法獲取所有的id,然後來更新所有的跟AppWidgetProvider相關的AppWidget。



public int[] getAppWidgetIds(ComponentName provider)

另外還有bindAppWidgetIdIfAllowed方法和getInstalledProviders,這兩個方法主要是在Launcher應用當中使用,分配AppWidget的id,然後用bindAppWidgetIdIfAllowed繫結AppWidgetProvider與id。用getInstalledProviders方法獲取已經插入的AppWidgetProvider,可以用來供使用者選擇新增到桌面。


public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider,
       Bundle options)

public List<AppWidgetProviderInfo> getInstalledProviders()

RemoteView

RemoteView顧名思義就是指遠端View,通過本地程序修改RemoteView,能夠使這些修改通過RemoteView為載體傳遞到遠端程序。RemoteView不僅僅在AppWidget中有使用,Notification中也是使用了RemoteView。下面簡單介紹一下RemoteView的實現原理。

RemoteView繼承自Parcelable,所以RemoteView本身就是可以跨程序傳遞的。RemoteView有個內部類叫做Action,它也是繼承自Parcelable,對於不同的操作,RemoteView內部實現了不同的Action。

比如點選事件,RemoteView內部有一個SetOnClickPendingIntent,它也是繼承自Action,設定點選事件就是將點選事件儲存在這裡面,把點選事件作為一個Action。可以看看它的原始碼:


private class SetOnClickPendingIntent extends Action {
   public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
       this.viewId = id;
       this.pendingIntent = pendingIntent;
   }

   public SetOnClickPendingIntent(Parcel parcel) {
       viewId = parcel.readInt();

       // We check a flag to determine if the parcel contains a PendingIntent.
       if (parcel.readInt() != 0) {
           pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
       }
   }

   public void writeToParcel(Parcel dest, int flags) {
       dest.writeInt(TAG);
       dest.writeInt(viewId);

       // We use a flag to indicate whether the parcel contains a valid object.
       dest.writeInt(pendingIntent != null ? 1 : 0);
       if (pendingIntent != null) {
           pendingIntent.writeToParcel(dest, 0 /* no flags */);
       }
   }

   @Override
   public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
       final View target = root.findViewById(viewId);
       if (target == null) return;

       // If the view is an AdapterView, setting a PendingIntent on click doesn't make much
       // sense, do they mean to set a PendingIntent template for the AdapterView's children?
       if (mIsWidgetCollectionChild) {
           Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " +
                   "(id: " + viewId + ")");
           ApplicationInfo appInfo = root.getContext().getApplicationInfo();

           // We let this slide for HC and ICS so as to not break compatibility. It should have
           // been disabled from the outset, but was left open by accident.
           if (appInfo != null &&
                   appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
               return;
           }
       }

       // If the pendingIntent is null, we clear the onClickListener
       OnClickListener listener = null;
       if (pendingIntent != null) {
           listener = new OnClickListener() {
               public void onClick(View v) {
                   // Find target view location in screen coordinates and
                   // fill into PendingIntent before sending.
                   final Rect rect = getSourceBounds(v);

                   final Intent intent = new Intent();
                   intent.setSourceBounds(rect);
                   handler.onClickHandler(v, pendingIntent, intent);
               }
           };
       }
       target.setOnClickListener(listener);
   }
...

}

其中以Parcel為引數的建構函式相當於是從Parcel當中讀取內容,而writeToParcel是將Action寫入到Parcel。這兩個函式是用來傳遞Action用的。而apply方法則是將Action解析出來,設定監聽(這種監聽是遠端監聽,在onClick方法裡面傳送一個PendingIntent),具體實現在RemoteView的OnClickHandler中。其他的Action也是類似的。關於Parcel實現原理可以借鑑我之前寫的兩篇關於Bitmap傳輸的文章: Android4.0 Bitmap Parcel傳輸原始碼分析,Android6.0 Bitmap儲存以及Parcel傳輸原始碼分析

RemoteView裡面有一個mActions變數,是Action的列表。通過Parcel傳遞RemoteView的時候,RemoteView會將mActions裡面的內容都寫入到Parcel中。在readParcel的時候,使用Action的帶Parcel引數的建構函式從Parcel裡面讀取Action,進行設定。Action其實就是一種模板方法模式。RemoteView很多設定是跟普通的View不一樣的,RemoteView是一個能夠跨程序設定相關內容的,如果需要設定監聽函式之類的,只能設定PendingIntent。可以看看RemoteView裡面的CREATOR和RemoteView對應的構造方法:


/**
* Parcelable.Creator that instantiates RemoteViews objects
*/
public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
   public RemoteViews createFromParcel(Parcel parcel) {
       return new RemoteViews(parcel);
   }

   public RemoteViews[] newArray(int size) {
       return new RemoteViews[size];
   }
};

...



public RemoteViews(Parcel parcel) {
   this(parcel, null);
}

private RemoteViews(Parcel parcel, BitmapCache bitmapCache) {//實際上在建構函式中讀取Parcel的內容,重新建立一個mActions
   int mode = parcel.readInt();

   // We only store a bitmap cache in the root of the RemoteViews.
   if (bitmapCache == null) {
       mBitmapCache = new BitmapCache(parcel);
   } else {
       setBitmapCache(bitmapCache);
       setNotRoot();
   }

   if (mode == MODE_NORMAL) {
       mApplication = parcel.readParcelable(null);
       mLayoutId = parcel.readInt();
       mIsWidgetCollectionChild = parcel.readInt() == 1;

       int count = parcel.readInt();
       if (count > 0) {
           mActions = new ArrayList<Action>(count);
           for (int i=0; i<count; i++) {
               int tag = parcel.readInt();
               switch (tag) {
                   case SetOnClickPendingIntent.TAG:
                       mActions.add(new SetOnClickPendingIntent(parcel));
                       break;
                   case SetDrawableParameters.TAG:
                       mActions.add(new SetDrawableParameters(parcel));
                       break;
                    ...
                   default:
                       throw new ActionException("Tag " + tag + " not found");
               }
           }
       }
   } else {
       // MODE_HAS_LANDSCAPE_AND_PORTRAIT
       mLandscape = new RemoteViews(parcel, mBitmapCache);
       mPortrait = new RemoteViews(parcel, mBitmapCache);
       mApplication = mPortrait.mApplication;
       mLayoutId = mPortrait.getLayoutId();
   }

   // setup the memory usage statistics
   mMemoryUsageCounter = new MemoryUsageCounter();
   recalculateMemoryUsage();
}

最後呼叫RemoteView的apply方法就在遠端程序設定了相關內容了。我們AppWidget的遠端程序是Launcher應用所在的程序。由AppWidgetHost管理這些。

AppWidgetHost

這個類是Android提供的供應用與AppWidget service互動的類,我們的AppWidgetProvider提供了Widget,而AppWidgetHost則是讀取Widget,將它顯示出來。一般在home screen中使用,也就是我們的桌面,Launcher。與之相關的還有個AppWidgetHostView,由AppWidgetHost建立,它與AppWidgetProvider對應。可以看一下AppWidgetHost的createView方法:


public final AppWidgetHostView createView(Context context, int appWidgetId,
       AppWidgetProviderInfo appWidget) {
   AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
   view.setOnClickHandler(mOnClickHandler);
   view.setAppWidget(appWidgetId, appWidget);
   synchronized (mViews) {
       mViews.put(appWidgetId, view);
   }
   RemoteViews views;
   try {
       views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
   } catch (RemoteException e) {
       throw new RuntimeException("system server dead?", e);
   }
   view.updateAppWidget(views);

   return view;
}

這個主要是在launcher應用使用,就不詳細介紹了。

總結

這一篇主要是介紹AppWidget相關的一些類,分析裡面的原始碼功能,以及實現方式。通過深入瞭解它的實現方式才能夠更好地使用它,分析遇到的問題。下一篇將從原始碼角度上介紹一些方法的處理流程。