Android神奇"控件"一一RemoteViews

作者簡介 原創(chuàng)微信公眾號郭霖 WeChat ID: guolin_blog

本篇是王月半子的第三篇投稿,各方面地講解了RemoteViews,所以文章篇幅不短,感興趣的朋友需要仔細讀一讀了。

王月半子的博客地址:

http://blog.csdn.net/wrg_20100512

RemoteViews是什么

先從表層意思理解 RemoteViews 感覺它是一個view的集合,而且和遠程有關(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?,F(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)用不同的遠程方法。

最終在MainActivity中處理消息,實現(xiàn)功能。(傳統(tǒng)的實現(xiàn)方式對應(yīng)著case 2和case 3)

大功告成,看一下效果吧!


這里我在綁定服務(wù)成功之后 ,相繼調(diào)用了兩次遠程服務(wù)來實現(xià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)效果如下:


細心的同學(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 最常用的兩個場景是NotificationAppWidget小部件,因為這兩者的界面都運行在其他進程進程,確切來說它們所屬 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 猜測這里就開始了遠程服務(wù)的調(diào)用,而這個遠程服務(wù)對象 mService 的類型是 IAppWidgetService。之后由 AppWidgetService 發(fā)送消息,AppWidgetHost 監(jiān)聽來自 AppWidgetService 的事件。這其中的細節(jié)涉及太多知識點,畢竟要扒的是RemoteViews。這是詳細分析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》

淘寶鏈接: https://s.click.taobao.com/t?e=m%3D2%26s%3DgKUfuKdAZKocQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67p2n%2BQBNMyE6Rku8%2Bpj6eJall3bs%2B3NRhNHnsKI%2BqxhyM0iVZhTFBom4YIorMPnmg8G0g2OJi%2FzmXHfenomYtn5EW9vzeG8LzfPUwktUBEmkxg5p7bh%2BFbQ%3D&pvid=10_106.6.161.154_3367_1490163222155

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容