作者簡介 原創(chuàng)微信公眾號郭霖 WeChat ID: guolin_blog
本篇是王月半子的第三篇投稿,各方面地講解了RemoteViews感局,所以文章篇幅不短,感興趣的朋友需要仔細(xì)讀一讀了呜投。
王月半子的博客地址:
http://blog.csdn.net/wrg_20100512
RemoteViews是什么
先從表層意思理解 RemoteViews 感覺它是一個view的集合朵耕,而且和遠(yuǎn)程有關(guān)系炫隶。那事實上它是什么呢?請看官方對它的說明:
從說明可以看出阎曹,RemoteViews 是用來描述一個視圖的伪阶,它描述的這個視圖將顯示在另外一個進程中煞檩,這也就符合了 RemoteViews 中 Remote 這層含義。同時說明里也說了 RemoteViews 提供了一些基本的操作方法來修改它描述的那個視圖的內(nèi)容栅贴。聽起來它還真像是個“控件”斟湃,那它真的是嗎?
看一下 RemoteViews 的類繼承關(guān)系:
從圖中發(fā)現(xiàn)檐薯,RemoteViews 與 View 沒有半毛錢的關(guān)系凝赛,它僅僅就是 Object 的一個子類,實現(xiàn)了 Parcelable 接口(這就為 RemoteViews 能夠?qū)崿F(xiàn)跨進程提供了條件)坛缕。所以從嚴(yán)格意義上來說墓猎,RemoteViews 并不是一個控件,它僅僅是為生成控件和修改控件屬性提供一系列的方法赚楚。
總結(jié):RemoteViews 就是為跨進程生成控件和修改控件屬性提供一系列方法的一個類陶衅。
說了 RemoteViews 是什么之后,咱們來看看為什么要用 RemoteViews直晨!
為什么要用RemoteViews
既然 RemoteViews 是用于跨進程更新UI的搀军,那咱們就來創(chuàng)造這么一個場景:
同一個應(yīng)用中有兩個 Activity,這兩個 Activity 分別處在不同的進程中:
MainActivity
TempActivity
其中MainActivity所屬的進程為 com.example.bjwangruigang.remoteviewstudy勇皇,TempActivity所屬的進程為 com.example.bjwangruigang.remoteviewstudy:remote≌志洌現(xiàn)在需要通過 TempActivity 來改變 MainActivity 中的視圖,也就是實現(xiàn)跨進程更新UI這么一個功能敛摘。具體來說就是在 MainActivity 中添加兩個 Button门烂。
傳統(tǒng)方式實現(xiàn)跨進程更新UI
拿到這個場景需求,結(jié)合跨進程和更新UI的知識兄淫,有以下幾個方案:
1.TempActivity 把要添加的兩個 Button 的布局的ID值通過 BroadcastRecriver 發(fā)送屯远,在 MainActivity 中注冊該廣播,同時獲取其中的布局ID值捕虽,通過 LayoutInflater 來繪制那兩個 Button慨丐,最后添加到 MainActivity 的布局中去。
2.TempActivity 通過 AIDL 這種方式將要添加的兩個 Button 的布局的ID值發(fā)送到 AIDLService 中泄私,通過 Handler 來發(fā)送消息房揭、處理消息。處理過程同樣是通過 LayoutInflater 來繪制那兩個 Button晌端,最后添加到 MainActivity 的布局中去捅暴。
其實這兩種方案大同小異,無非采用的進程間通信方式不同咧纠,后續(xù)的添加視圖是一模一樣的蓬痒。方案一采用廣播的形式來進行IPC通信,而方案二則采用AIDL這種相對原生的IPC方式漆羔。為了重溫AIDL梧奢,這里我采用 AIDL 的方式來實現(xiàn)上述效果瞪讼。
首先建立IViewManager.aidl
rebuild project 讓IDE工具自己生成 AIDL接口 對應(yīng)的Java文件。
建立ViewAIDLService文件
在TempActivity中綁定服務(wù)粹断,并在綁定成功后符欠,針對實現(xiàn)的功能調(diào)用不同的遠(yuǎn)程方法。
最終在MainActivity中處理消息瓶埋,實現(xiàn)功能希柿。(傳統(tǒng)的實現(xiàn)方式對應(yīng)著case 2和case 3)
大功告成,看一下效果吧养筒!
這里我在綁定服務(wù)成功之后 曾撤,相繼調(diào)用了兩次遠(yuǎn)程服務(wù)來實現(xiàn)兩種遠(yuǎn)程UI更新(修改 MainActivity 中 TextView 的內(nèi)容和為 MainActivity 中添加兩個 Button)。
那問題來了晕粪,如果這時候我們有其他的需求挤悉,比如我要為 Button 中修改內(nèi)容,這時候我們還需要在 IViewManager 添加新的接口巫湘,在 ViewAIDLService 實現(xiàn)接口装悲,當(dāng)然 MainActivity 中對應(yīng)的代碼同樣要修改,牽一發(fā)而動全身尚氛。除此以外诀诊,多次 IPC 帶來的開銷問題不容小覷。
終上所述阅嘶,傳統(tǒng)方式實現(xiàn)跨進程更新UI是可行的属瓣,但不得不提有以下弊端:
View 中的方法數(shù)比較多,在IPC中需要增加對應(yīng)的方法比較繁瑣讯柔。
View 的每一個方法都會涉及到IPC操作抡蛙,多次IPC帶來的開銷問題不容小覷。
View 中方法的某些參數(shù)可能不支持IPC傳輸魂迄。例如:OnClickListener粗截,它僅僅是個接口沒有序列化。
接下來我們來看看 RemoteViews 在實現(xiàn)上述功能有什么優(yōu)勢极祸。
RemoteViews實現(xiàn)跨進程更新UI
RemoteViews 實現(xiàn)跨進程更新UI同樣既可以通過 AIDL 也可以使用 BroadcastReceiver慈格,這里為了和傳統(tǒng)方式做下對比,只貼出AIDL方式的代碼遥金。
首先建立IremoteViewsManager.aidl
rebuild project 讓IDE工具自己生成 AIDL接口 對應(yīng)的java文件。
建立RemoteViewsAIDLService文件蒜田。
在 TempActivity 中綁定服務(wù)
最終在 MainActivity 中處理消息稿械,實現(xiàn)功能。(代碼在上面已經(jīng)貼出 對應(yīng)著case 1)?實現(xiàn)效果如下:
細(xì)心的同學(xué)可能發(fā)現(xiàn)冲粤,TempActivity 在綁定服務(wù)中的代碼中似乎為兩個 button 做了監(jiān)聽美莫。
是的页眯,這里是對 button 做了監(jiān)聽,媽媽再也不用擔(dān)心 OnClickListener 不能在 IPC 中傳遞了厢呵。
當(dāng)然 RemoteViews 的強大之處還不止體現(xiàn)在這窝撵,如果想修改 button 中的內(nèi)容,這時候你也不需要修改 IremoteViewsManager.aidl襟铭、RemoteViewsAIDLService 文件啦碌奉!你只需在傳遞 RemoteViews 之前添加一行代碼:
remoteViews.setCharSequence(R.id.firstButton,"setText","想改就改");
這里就不貼效果圖啦,anyway這都不重要寒砖。
最重要的是:整個過程只有一次IPC赐劣,只有一次哦,一次哦哩都。
整體來說魁兼,RemoteViews 就是為跨進程更新UI而生的,內(nèi)部封裝了多種方法用來跨進程更新UI漠嵌。但這也不代表 RemoteViews 是宇宙強無敵咐汞,因為它也有軟肋,它目前支持的布局和View有限:
layout:
FrameLayout LinearLayout RelativeLayout GridLayout
View:
AnalogClock button Chronometer ImageButton ImageView ProgressBar TextView ViewFlipper ListView GridView StackView AdapterViewFlipper ViewStub
不支持自定義View所以傳統(tǒng)的方式依舊是有用武之地的儒鹿。
深入理解RemoteViews
按著是什么碉考、為什么的規(guī)矩,接下來就是怎么用啦挺身。其實上面在介紹為什么用 RemoteViews 的時候已經(jīng)介紹了如何使用侯谁,但是并不是開發(fā)中常用的方式,僅僅是為了說明它相對于傳統(tǒng)的跨進程更新UI的優(yōu)勢在哪章钾。
RemoteViews 最常用的兩個場景是Notification和AppWidget小部件墙贱,因為這兩者的界面都運行在其他進程進程,確切來說它們所屬 systemServer 進程贱傀,所以 RemoteViews 是它兩的不二之選惨撇。
那這部分就結(jié)合著 AppWidget 使用 RemoteViews,深入學(xué)習(xí) RemoteViews 是怎么保證它強大的跨進程更新UI的優(yōu)勢的府寒。
這里需要注意兩個問題:
1.RemoteViews為什么可以通過一次IPC實現(xiàn)對多個View的操作魁衙。
2. 其他進程怎么獲取布局文件。
首先準(zhǔn)備 AppWidget 的所有文件:MyAppWidgetProvider株搔、要顯示的xml以及向 AndroidManifest.xml 中注冊 MyAppWidgetProvider 等等剖淀。
AndroidManifest.xml
MyAppWidgetProvider
接下來結(jié)合代碼來分析 RemoteViews 是怎么發(fā)揮它的優(yōu)勢的:
當(dāng)用戶將 AppWidget 拖到桌面上時,MyAppWidgetProvider 繼承 AppWidgetProvider 原有的 onReceive 方法纤房,回調(diào)其 onUpdate 方法
在 onWidgetUpdate 方法中建立 RemoteViews纵隔,之后調(diào)用 appWidgetManager 的 updateAppWidget 發(fā)起 IPC
這里實例化了 RemoteViews,先看 RemoteViews 的構(gòu)造函數(shù):
這里我們關(guān)注 RemoteViews 的 mLayoutId 成員變量。
之后 RemoteViews 調(diào)用了 setOnClickPendingIntent 方法捌刮。
remoteViews.setOnClickPendingIntent(R.id.imageView,pendingIntent);
setOnClickPendingIntent 方法在內(nèi)部利用 viewId, pendingIntent 生成 SetOnClickPendingIntent 對象碰煌,并將此對象作為參數(shù)傳入 addAction 中,這里不難看出 SetOnClickPendingIntent和Action 存在繼承或者實現(xiàn)的關(guān)系绅作。先看 addAction 的具體邏輯芦圾,發(fā)現(xiàn) addAction 中將傳入的參數(shù)添加至 RemoteViews 的成員變量 mActions 中。
看一下 Action 類:
Action 類為一個抽象類俄认,同時實現(xiàn)了 Parcelable 接口个少,支持 IPC。唯一的一個抽象方法 apply梭依。
再看涉及到的 Action 的子類 SetOnClickPendingIntent:
RemoteViews 的 setOnClickPendingIntent 方法可以這么理解:將添加監(jiān)聽的一個 View 動作稍算,封裝成一個 Action 類,保存在 RemoteViews 的 mActions 中役拴。其實查看 RemoteViews 的每一個 set 方法糊探,不難發(fā)現(xiàn)都是把對 View 操作的動作封裝成Action類,最終保存在 RemoteViews 的 mActions 中河闰。這個過程可以理解為:
到目前為止發(fā)現(xiàn) RemoteViews 更多承擔(dān)的是信息的一個載體科平,這些信息包括:要 顯示View的資源ID值、mActions 等等姜性。
接下來來看看 appWidgetManager.updateAppWidget 內(nèi)部發(fā)生了什么:
看到了RemoteException 猜測這里就開始了遠(yuǎn)程服務(wù)的調(diào)用瞪慧,而這個遠(yuǎn)程服務(wù)對象 mService 的類型是 IAppWidgetService。之后由 AppWidgetService 發(fā)送消息部念,AppWidgetHost 監(jiān)聽來自 AppWidgetService 的事件弃酌。這其中的細(xì)節(jié)涉及太多知識點,畢竟要扒的是RemoteViews儡炼。這是詳細(xì)分析AppWidget生成流程的一系列文章:
http://blog.csdn.net/thl789/article/details/7893292
AppWidgetHost 收到 AppWidgetService 發(fā)送的消息妓湘,創(chuàng)建 AppWidgetHostView,然后通過 AppWidgetService 查詢 appWidgetId 對應(yīng)的 RemoteViews乌询,最后把 RemoteViews 傳遞給 AppWidgetHostView 去 updateAppWidget榜贴。
updateAppWidget 的實現(xiàn)邏輯很好理解(當(dāng)然這里只是保留了主要的邏輯代碼),如果沒有加載過 remoteViews 的布局則調(diào)用 remoteViews.apply 方法妹田,若加載過了則調(diào)用 remoteViews.reapply 方法唬党。
其實這個時候所有的操作已經(jīng)處于 systemServer 進程中了,所要理解的也就是 remoteViews 的 apply 和 reapply 方法了鬼佣。由于 apply 比 reapply 方法中多了一道加載布局文件的程序驶拱,這里選擇分析 apply 的實現(xiàn)過程。
apply 的實現(xiàn)過程如下:
1.通過 RemoteViews 的 getLayoutId 方法獲取要顯示的資源ID值
2.利用 LayoutInflater 加載要加載的xml文件沮趣,生成View屯烦。
3.調(diào)用 RemoteViews 的 performApply 方法。
performApply 的流程相對簡單房铭,就是將前面存入 mActions 中的 Action 遍歷取出來驻龟,并調(diào)用 action 的 apply 方法。接下來再看具體的 ?Action 的 apply 的方法缸匪,就拿上面的 SetOnClickPendingIntent類 來分析這個過程吧翁狐!
實現(xiàn)的過程如下:
1.通過 View 的id值獲取對應(yīng)的 view(target)。
2.SetOnClickPendingIntent類 中的成員變量 pendingIntent 生成相應(yīng)的 OnClickListener凌蔬。
3.為 target 設(shè)置監(jiān)聽露懒。
當(dāng)然這里就分析了一個相對簡單的 Action,其他的 Action 邏輯也是相同的砂心,有的會使用反射技術(shù)來修改View的某些屬性懈词。
到這里關(guān)于 RemoteViews 的學(xué)習(xí)也就結(jié)束了,最后盜用別人的圖來進一步解釋下 RemoteView 內(nèi)部機制辩诞,至于上面兩個問題我想也不需要解釋太多了坎弯。
完。译暂。抠忘。。外永。崎脉。。伯顶。囚灼。。祭衩。灶体。。汪厨。赃春。。劫乱。织中。。衷戈。狭吼。
文章原創(chuàng)作者GuoLin 書籍推薦
郭林大神原創(chuàng)android 書籍:《第一行代碼 android》