Android 理解RemoteViews
導語
什麼是遠端view呢?它和遠端service一樣,RemoteViews可以在其他程序中顯示。我們可以跨程序更新它的介面。在Android中,主要有兩種場景:通知欄和桌面小部件。
本章先簡單介紹通知欄和桌面小部件應用,接著分析RemoteViews內部機制,最後分析RemoteViews的意義並給出一個例項。
主要內容
- RemoteViews的應用
- RemoteViews的內部機制
- RemoteViews的意義
具體內容
RemoteViews的應用
通知欄主要是通過NotificationManager的notify方法實現。桌面小部件是通過APPWidgetProvider來實現。APPWidgetProvider本質是一個廣播。RemoteViews執行在系統的SystemServer程序。
RemoteViews在通知欄的應用
我們用到自定義通知,首先要提供一個佈局檔案,然後通過RemoteViews來載入,可以自定義通知的樣式。更新view時,通過RemoteViews提供的一系列方法。如果給一個控制元件加點選事件,要使用PendingIntent。
RemoteViews在桌面小部件的應用
AppWidgetProvider是實現桌面小部件的類,本質是一個BroadcastReceiver。開發步驟如下:
- 定義小部件介面。 ofollow,noindex">程式碼
- 定義小部件配置資訊。 程式碼
- 定義小部件實現類,繼承AppWidgetProvider。 程式碼
上面的例子實現了一個簡單地桌面小部件,在小部件上顯示一張圖片,點選後會旋轉一週。 - 在AndroidManifest.mxl中宣告小部件。
receiver android:name=".MyAppWidgetProvider" > <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget_provider_info" > </meta-data> <intent-filter> <action android:name="com.ryg.chapter_5.action.CLICK" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> </receiver>
第一個action用於識別小部件的單擊,第二個action作為小部件的標識必須存在。
AppWidgetProvider除了onUpdate方法,還有一系列方法。這些方法會自動被onReceive方法呼叫。當廣播到來以後,AppWidgetProvider會自動根據廣播的action通過onReceive方法分發廣播。
- onEnable:該小部件第一次新增到桌面時呼叫,新增多次只在第一次呼叫。
- onUpdate:小部件被新增或者每次小部件更新時呼叫,更新時機由updatePeriodMillis指定,每個週期小部件都會自動更新一次。
- onDeleted:每刪除一次桌面小部件都會呼叫一次。
- onDisabled:最後一個該型別的桌面小部件被刪除時呼叫。
- onReceive:內建方法,用於分發具體事件給以上方法。
PendingIntent概述
PendingIntent表示一種處於待定的狀態的intent。典型場景是RemoteViews新增單擊事件。通過send和cancel方法來發送和取消待定intent。
PendingIntent支援三種待定意圖:
- static PendingIntent:getActivity(Context context, int requestCode, Intent intent, int flags)獲得一個PendingIntent,效果相當於Context.startActivity(Intent)。
- static PendingIntent:getService(Context context, int requestCode, Intent intent, int flags)獲得一個PendingIntent,效果相當於Context.startService(Intent)。
- static PendingIntent:getBroadcast(Context context, int requestCode, Intent intent, int flags)獲得一個PendingIntent,效果相當於Context.sendBroadcast(Intent)。
其中requestCode多數情況下設為0即可,requestCode會影響flags的效果。
PendingIntent的匹配規則:
如果兩個PendingIntent,它們內部的Intent相同且requestCode也相同,那這兩個PendingIntent就是相同的。
Intent的匹配規則:
如果兩個intent的ComponentName和intent-filter相同,那麼這兩個intent相同。Extras不參與匹配過程。
flags引數的含義:
- FLAG_ONE_SHOT
當前的PendingIntent只能被使用一次,然後就會被自動cancel,如果後續還有相同的PendingIntent,它們的send方法會呼叫失敗。對於通知欄來說,同類的通知只能使用一次,後續的通知將無法開啟。 - FLAG_NO_CREATE
當前的PendingIntent不會主動建立,如果當前PendingIntent之前不存在(匹配的PendingIntent),那麼獲取PendingIntent失敗。這個flag很少使用。 - FLAG_CANCEL_CURRENT
當前的PendingIntent如果存在(匹配的PendingIntent),那麼它們都會被cancel,然後系統建立一個新的PendingIntent。對於通知欄來說,那些被cancel的訊息單擊後將無法開啟。 - FLAG_UPDATE_CURRENT
當前PendingIntent如果已經存在(匹配的PendingIntent),那麼它們都會被更新。即intent中的extras會被替換成最新的。
舉例:
在manager.notify(id,notification)中,如果id是常量,那麼多次呼叫notify只能彈出一個通知,後續的通知會把前面的通知完全替代。而如果每次id都不同,那麼會彈出多個通知。
如果id每次都不同且PendingIntent不匹配,那麼flags不會對通知之間造成干擾。
如果id不同且PendingIntent匹配:
- 如果採用了FLAG_ONE_SHOT標記位,那麼後續通知中的PendingIntent會和第一條通知完全一致,包括extras,單擊任何一條通知後,剩下的通知均無法再開啟,當所有的通知被清除後,會再次重複這一過程。
- 如果採用FLAG_CANCEL_CURRENT,那麼只有最新的通知可以開啟。
- 如果採用FLAG_UPDATE_CURRENT,那麼之前彈出的通知中的PendingIntent會被更新,與最新一條的通知完全一致,包括extras,並且這些通知都可以開啟。
RemoteViews的內部機制
構造方法
public RemoteViews(String packageName, int layoutId)
第一個引數是當前應用的包名,第二個引數是待載入的佈局檔案。
RemoteViews並不支援所有的view型別,支援型別如下:
- Layout:FrameLayout、LinearLayout、RelativeLayout、GridLayout。
- View:AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper,ListView,GridView、StackView、AdapterViewFlipper、ViewStub。
- RemoteViews不支援以上view的子類。
訪問RemoteViews的view元素,必須通過一系列set方法完成:
方法名 | 作用 |
---|---|
setTextViewText(int viewId,CharSequence text) | 設定TextView的文字內容 第一個引數是TextView的id 第二個引數是設定的內容。 |
setTextViewTextSize(int viewId, int units, float size) | 設定TextView的字型大小 第二個引數是字型的單位。 |
setTextColor(int viewId, int color) | 設定TextView字型顏色。 |
setImageViewResource(int viewId, int srcId) | 設定ImageView的圖片 |
setInt(int viewId,String methodName, int value) | 反射呼叫View物件的引數型別為Int的方法 比如上述的setImageViewResource的方法內部就是這個方法實現 因為srcId為int型引數。 |
setLong setBoolean | 類似於setInt。 |
setOnClickPendingIntent(int viewId,PendingIntent pendingIntent) | 新增點選事件的方法 |
大部分set方法是通過反射來完成的。
RemoteViews內部機制
通知欄和小元件分別由NotificationManager(NM)和AppWidgetManager(AWM)管理,而NM和AWM通過Binder分別和SystemService程序中的NotificationManagerService以及AppWidgetService中載入的,而它們執行在系統的SystemService中,這就和我們程序構成了跨程序通訊。
首先RemoteViews會通過Binder傳遞到SystemService程序,因為RemoteViews實現了Parcelable介面,因此它可以跨程序傳輸,系統會根據RemoteViews的包名等資訊拿到該應用的資源;然後通過LayoutInflater去載入RemoteViews中的佈局檔案。接著系統會對View進行一系列介面更新任務,這些任務就是之前我們通過set來提交的。set方法對View的更新並不會立即執行,會記錄下來,等到RemoteViews被載入以後才會執行。
為了提高效率,系統沒有直接通過Binder去支援所有的View和View操作。而是提供一個Action概念,Action同樣實現Parcelable介面。系統首先將View操作封裝到Action物件並將這些物件跨程序傳輸到SystemService程序,接著SystemService程序執行Action物件的具體操作。遠端程序通過RemoteViews的apply方法來進行View的更新操作,RemoteViews的apply方法會去遍歷所有的Action物件並呼叫他們的apply方法。這樣避免了定義大量的Binder介面,也避免了大量IPC操作。
apply和reApply的區別在於:apply會載入佈局並更新介面,而reApply則只會更新介面。RemoteViews在初始化介面時會呼叫apply方法,後續更新介面呼叫reApply方法。

關於單擊事件,RemoteViews中只支援發起PendingIntent,不支援onClickListener那種模式。setOnClickPendingIntent用於給普通的View設定單擊事件,不能給集合(ListView/StackView)中的View設定單擊事件(開銷大,系統禁止了這種方式)。如果要給ListView/StackView中的item設定單擊事件,必須將setPendingIntentTemplate和setOnClickFillInIntent組合使用才可以。
RemoteViews的意義
當一個應用需要更新另一個應用的某個介面,我們可以選擇用AIDL來實現,但如果更新比較頻繁,效率會有問題,同時AIDL介面就可能變得很複雜。如果採用RemoteViews就沒有這個問題,但RemoteViews僅支援一些常用的View,如果介面的View都是RemoteViews所支援的,那麼就可以考慮採用RemoteViews。