編輯:關於Android編程
最近項目中接觸到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的核心服務,在之後的文章會介紹,這篇就不介紹了。下面詳細介紹每個類的功能以及實現原理。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMyBpZD0="appwidgetprovider">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 getInstalledProviders()
關於launcher可以參考aosp的源碼:http://arnab.ch/blog/2013/08/how-to-write-custom-launcher-app-in-android/。我們也可以自己開發launcher應用。
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也是類似的。
RemoteView裡面有一個mActions變量,是Action的列表。通過Parcel傳遞RemoteView的時候,RemoteView會將mActions裡面的內容都寫入到Parcel中。在readParcel的時候,使用Action的帶Parcel參數的構造函數從Parcel裡面讀取Action,進行設置。Action其實就是一種模板方法模式。RemoteView很多設置是跟普通的View不一樣的,RemoteView是一個能夠跨進程設置相關內容的,如果需要設置監聽函數之類的,只能設置PendingIntent。可以看看RemoteView裡面的CREATOR和RemoteView對應的構造方法:
<code><code><code><code><code><code><code><code><code> /** * 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;="" setdrawableparameters.tag:="" setdrawableparameters(parcel));="" ...="" default:="" throw="" new="" actionexception("tag="" "="" +="" not="" found");="" }="" else="" mode_has_landscape_and_portrait="" mlandscape="new" remoteviews(parcel,="" mbitmapcache);="" mportrait="new" mapplication="mPortrait.mApplication;" mlayoutid="mPortrait.getLayoutId();" setup="" the="" memory="" usage="" statistics="" mmemoryusagecounter="new" memoryusagecounter();="" recalculatememoryusage();="" 最後調用RemoteView的apply方法就在遠程進程設置了相關內容了。我們AppWidget的遠程進程是Launcher應用所在的進程。由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相關的一些類,分析裡面的源碼功能,以及實現方式。通過深入了解它的實現方式才能夠更好地使用它,分析遇到的問題。下一篇將從源碼角度上介紹一些方法的處理流程。
在Android程序中很多客戶端軟件和浏覽器軟件都喜歡用Tab分頁標簽來搭建界面框架。讀者也許會馬上想到使用TabHost 與 TabActivity的組合,其實最常用的
沒睡著覺,起來更篇文章吧哈哈!首先祝賀李宗偉擊敗我丹,雖然我是支持我丹的,但是他也不容易哈哈,值得尊敬的人!切入正題:這一篇來介紹個自定義廣播接收者。通常我們在外撥電話的
文章伊始,讓我們先靜心回憶三秒:在我們寫過的Android應用當中,是不是有很多地方都離不開數據加載的需求呢?如果是,那麼我們首先來看下圖:好的,從這裡開始我們暫時忘記自
android-pdf-viewer在android studio應用問題說明小白一枚,之前一直是做.NET開發的,最近需要弄一個新聞app,能力有限,只能借助HTML5