田海立@CSDN
2012-8-22
本文結合AppWidget的應用情境,分析Android中RemoteViews的內部具體實現。
從前文《Android中AppWidget的分析與應用:AppWidgetProvider》和《Android中Launcher對於AppWidget的處理的分析:AppWidgetHost角色》中得知,Android中AppWidget的圖形資源是由AppWidgetProvider通過RemoteViews提供的;而顯示是由AppWidgetHost通過AppWidgetHostView把RemoteView提供的內容顯示在本地View上的。AppWidgetProvider和AppWidgetHostView運行在不同的程式中,而它們溝通的圖形元素和點擊回饋的橋樑就是RemoteViews。
下面為了行文方便和一致,把RemoteViews的內容提供方AppWidgetProvoder稱作Remote端;而顯示RemoteViews內容的一方AppWidgetHost稱作Local端。
一、給RemoteViews提供內容——SettingsAppWidgetProvider
是SettingsAppWidgetProvider(位於Settings中的com.android.settings.widget包中)作為AppWidgetProvider得到update通知之後,建立RemoteViews,並把Remote端的響應Intent以及圖形元素放進RemoteViews中的順序圖。
圖一、為RemoteViews提供內容和偵聽
圖中,
1. Settings建立RemoteViews時,把packageName和layoutId傳進去並儲存起來。packageName相當重要,因為這裡的layoutId和各種其他資源都是相對這個程式來說的,只有通過packageName獲得相應的Context,才能進而獲得資源,否則其他程式是無法獲得這些資源的[Seq#1]。
2. Settings把layoutId中的viewId指示的View被點擊之後獲得響應的PendingIntent設定到RemoteViews中[Seq#2~ #5]。
- RemoteViews建立SetOnClickPendingIntent並把id和intent傳入,SetOnClickPendingIntent儲存這些值;
- SetOnClickPendingIntent是RemoteViews.Action的子類,通過addAction()把SetOnClickPendingIntent加入到mActions:ArrayList<RemoteViews.Action>儲存下來。
3. Settings把layoutId中的viewId指示的View的ImageSourceID設定到RemoteViews中[Seq#6~ #10]。
- RemoteViews中有很多setXYZ()的方法,用來根據不同的要設定值的類型來設定;
- setXYZ()建立ReflectionAction並把viewId和value,以及“setImageResource”作為methodName傳入,ReflectionAction儲存這些值;
- ReflectionAction是RemoteViews.Action的子類,通過addAction()把ReflectionAction加入到mActions:ArrayList<RemoteViews.Action>儲存下來。
這裡描述的是一個子過程,後續會通過AppWidgetManager把這個建立好的RemoteViews放進AppWidget系統中,從而使得AppWidget的AppWidgetHost端更新顯示RemoteViews裡承載的內容。
Remote端設定內容的過程,只是設定這些參數,而RemoteViews也只是用不同的RemoteViews.Action儲存了這些參數。下文描述內部結構。
注意:這裡的參數都是在Remote端的,在RemoteContext有效。
二、RemoteViews的內部結構
是RemoteViews相關的類圖。
圖二、RemoteViews類圖
RemoteViews中儲存Remote端的mPackage和mLayoutId;並用mActions:ArrayList<RemoteViews.Action>儲存各種Action。
mPackage和mLayoutId是在構造RemoteViews時傳進去的[上文圖中的seq#1];
mActions是設定各種Remote端的響應Intent以及圖形元素的時候,儲存到相應的Action中,然後把Action加入到這裡儲存的;
mLayoutId裡的各種控制項通過setTextViewText()/ setImageViewResource() / setProgressBar(),等函數在remote端設定的。這些方法再調用setType() [Type可為Boolean / Byte / Short / Int/ Long / Char / String / Uri / Bitmap/ Bundle, etc]儲存到ReflectionAction中。
SetOnClickPendingIntent是用來在local端使用者點擊viewId時,發出pendingIntent通知的。在SetOnClickPendingIntent的構造方法中儲存viewId和pendingIntent。
ReflectionAction用來在local端顯示時,通過Reflect機制執行獲得Remote端資源的。在ReflectionAction的構造方法中儲存viewId,methodName,type以及value。
ViewGroupAction和SetDrawableParameters也是RemoteViews.Action的子類,在這個情境中並未用到,基本原理相似,讀者可自行分析。
三、顯示RemoteViews內容——AppWidgetHostView
圖一中為RemoteViews提供了內容之後,AppWidgetHost會通過IAppWidgetHost.updateAppWidget()被通知到Remote端有更新,本地端把RemoteViews提供的內容顯示在AppWidgetHostView上。下面的順序圖描述這一過程。
圖三、本地顯示RemoteViews裡的內容
圖中:
1. 擷取RemoteViews裡Remote端(AppWidgetProvider)的packageName和layoutId,通過packageName建立遠端的context——remoteContext。[Seq#1~ 6]
2. 通過RemoteViews的apply()方法,真正開始執行偵聽Click操作的動作;通過遠端Layout獲得本地使用的View。[Seq#7~ 20]
2.1. 複製一個本地的LayoutInflater;[Seq#8]
2.2. 用複製出的LayoutInflater對remote端的layoutId執行Inflate,獲得Layout所描述的View的Hierarchy,亦即後面用到的rootView;[Seq#9~ 10]
2.3. 對2.2中獲得的view執行performApply。[Seq#11~ 19]
performApply()對所有mActions中的Action都執行apply()操作。這樣,
2.3.1 對於setOnClickPendingIntent來說,[Seq#12~ 15]
- 通過rootView(2.2獲得的Remote端的Layout的總的View)的findViewById(viewId),找到要偵聽的View;[Seq#13]
- 對找到的要偵聽的View設定Click的Listener。[Seq#14]
2.3.2對於ReflectionAction來說,[Seq#16~ 19]
- 通過rootView(2.2獲得的Remote端的Layout的總的View)的findViewById(viewId),找到要設定內容的對象View;[Seq#17]
- 然後通過Reflect機制,執行View實作類別裡的方法(比如這裡是setImageResource()),把相應的資源的Id設定給它. [Seq#18]
3. 把獲得的View加入到本地的View系統中。[Seq#21]
下面是ReflectionAction.apply()通過Reflect機制設定內容的程式碼片段(忽略了出錯處理和非關鍵區段):
@Override public void apply(View root) { final View view = root.findViewById(viewId); Class param = getParameterType(); // 通過this.type得到class:int.class Class klass = view.getClass(); // 這個類在Remote的Layout中定義,這裡為ImageView Method method = klass.getMethod(this.methodName, param); // methodName是實現View類裡的方法名:setImageResource(int) try { // 執行ImageView.setImageResource(value),value為resId method.invoke(view, this.value); } catch (Exception ex) { throw new ActionException(ex); } }
四、總結
RemoteViews的內容提供方,提供顯示的資源和偵聽點擊事件的Intent;
RemoteViews的本地顯示方,通過RemoteViews獲得View中的顯示資源,並加入到本地的圖形系統中,完成Remote資源的本地顯示。
可進一步參考的文章
通過這一系列的其他文章,可獲得與本文關聯的資訊:
Android AppWidget架構
AppWidget系統架構。
Android中選取並綁定AppWidget
Launcher發起選取過程,此文中描述選取並綁定的過程,可結合本文看完整的選取/綁定/加入顯示系統的完整過程。
Android中AppWidget的分析與應用:AppWidgetProvider
是此文所描述的AppWidgetProvider建立RemoteViews,並設定了ImageViewResource和OnClickPendingIntent。
Android中Launcher對於AppWidget處理的分析:AppWidgetHost角色
此文描述的AppWidgetProvider提供的RemoteViews,在Launcher中開始真正應用RemoteViews的時機。
Android中RemoteViews的實現
本文。