### 5.2 RemoteViews的內部機制
RemoteViews的作用是在其他進程中顯示并更新View界面,為了更好地理解它的內部機制,我們先來看一下它的主要功能。首先看一下它的構造方法,這里只介紹一個最常用的構造方法: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類型,也就是說RemoteViews中不能使用除了上述列表中以外的View,也無法使用自定義View。比如如果我們在通知欄的RemoteViews中使用系統的EditText,那么通知欄消息將無法彈出并且會拋出如下異常:
E/StatusBar(765): couldn't inflate view for notification com.ryg.chapter_
5/0x2
E/StatusBar(765): android.view.InflateException: Binary XML file line #25:
Error inflating class android.widget.EditText
E/StatusBar(765): Caused by: android.view.InflateException: Binary XML file
line #25: Class not allowed to be inflated android.widget.EditText
E/StatusBar(765): at android.view.LayoutInflater.failNotAllowed
(LayoutInflater.java:695)
E/StatusBar(765): at android.view.LayoutInflater.createView
(LayoutInflater.java:628)
E/StatusBar(765): ... 21 more
上面的異常信息很明確,android.widget.EditText不允許在RemoteViews中使用。
RemoteViews沒有提供findViewById方法,因此無法直接訪問里面的View元素,而必須通過RemoteViews所提供的一系列set方法來完成,當然這是因為RemoteViews在遠程進程中顯示,所以沒辦法直接findViewById。表5-2列舉了部分常用的set方法,更多的方法請查看相關資料。
:-: 表5-2 RemoteViews的部分set方法

從表5-2中可以看出,原本可以直接調用的View的方法,現在卻必須要通過RemoteViews的一系列set方法才能完成,而且從方法的聲明上來看,很像是通過反射來完成的,事實上大部分set方法的確是通過反射來完成的。
下面描述一下RemoteViews的內部機制,由于RemoteViews主要用于通知欄和桌面小部件之中,這里就通過它們來分析RemoteViews的工作過程。我們知道,通知欄和桌面小部件分別由NotificationManager和AppWidgetManager管理,而NotificationManager和AppWidgetManager通過Binder分別和SystemServer進程中的NotificationManagerService以及AppWidgetService進行通信。由此可見,通知欄和桌面小部件中的布局文件實際上是在NotificationManagerService以及AppWidgetService中被加載的,而它們運行在系統的SystemServer中,這就和我們的進程構成了跨進程通信的場景。
首先RemoteViews會通過Binder傳遞到SystemServer進程,這是因為RemoteViews實現了Parcelable接口,因此它可以跨進程傳輸,系統會根據RemoteViews中的包名等信息去得到該應用的資源。然后會通過LayoutInflater去加載RemoteViews中的布局文件。在SystemServer進程中加載后的布局文件是一個普通的View,只不過相對于我們的進程它是一個RemoteViews而已。接著系統會對View執行一系列界面更新任務,這些任務就是之前我們通過set方法來提交的。set方法對View所做的更新并不是立刻執行的,在RemoteViews內部會記錄所有的更新操作,具體的執行時機要等到RemoteViews被加載以后才能執行,這樣RemoteViews就可以在SystemServer進程中顯示了,這就是我們所看到的通知欄消息或者桌面小部件。當需要更新RemoteViews時,我們需要調用一系列set方法并通過NotificationManager和AppWidgetManager來提交更新任務,具體的更新操作也是在SystemServer進程中完成的。
從理論上來說,系統完全可以通過Binder去支持所有的View和View操作,但是這樣做的話代價太大,因為View的方法太多了,另外就是大量的IPC操作會影響效率。為了解決這個問題,系統并沒有通過Binder去直接支持View的跨進程訪問,而是提供了一個Action的概念,Action代表一個View操作,Action同樣實現了Parcelable接口。系統首先將View操作封裝到Action對象并將這些對象跨進程傳輸到遠程進程,接著在遠程進程中執行Action對象中的具體操作。在我們的應用中每調用一次set方法,RemoteViews中就會添加一個對應的Action對象,當我們通過NotificationManager和AppWidgetManager來提交我們的更新時,這些Action對象就會傳輸到遠程進程并在遠程進程中依次執行,這個過程可以參看圖5-3。遠程進程通過RemoteViews的apply方法來進行View的更新操作,RemoteViews的apply方法內部則會去遍歷所有的Action對象并調用它們的apply方法,具體的View更新操作是由Action對象的apply方法來完成的。上述做法的好處是顯而易見的,首先不需要定義大量的Binder接口,其次通過在遠程進程中批量執行RemoteViews的修改操作從而避免了大量的IPC操作,這就提高了程序的性能,由此可見,Android系統在這方面的設計的確很精妙。
:-: 
圖5-3 RemoteViews的內部機制
上面從理論上分析了RemoteViews的內部機制,接下來我們從源碼的角度再來分析RemoteViews的工作流程。它的構造方法就不用多說了,這里我們首先看一下它提供的一系列set方法,比如setTextViewText方法,其源碼如下所示。
```
public void setTextViewText(int viewId, CharSequence text) {
setCharSequence(viewId, "setText", text);
}
```
在上面的代碼中,viewId是被操作的View的id, “setText”是方法名,text是要給TextView設置的文本,這里可以聯想一下TextView的setText方法,是不是很一致呢?接著再看setCharSequence的實現,如下所示。
```
public void setCharSequence(int viewId, String methodName, CharSequence
value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.
CHAR_SEQUENCE, value));
}
```
從setCharSequence的實現可以看出,它的內部并沒有對View進程直接的操作,而是添加了一個ReflectionAction對象,從名字來看,這應該是一個反射類型的動作。再看addAction的實現,如下所示。
private void addAction(Action a) {
…
if (mActions == null) {
mActions = new ArrayList<Action>();
}
mActions.add(a);
// update the memory usage stats
a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}
從上述代碼可以知道,RemoteViews內部有一個mActions成員,它是一個ArrayList,外界每調用一次set方法,RemoteViews就會為其創建一個Action對象并加入到這個ArrayList中。需要注意的是,這里僅僅是將Action對象保存起來了,并未對View進行實際的操作,這一點在上面的理論分析中已經提到過了。到這里setTextViewText這個方法的源碼已經分析完了,但是我們好像還是什么都不知道的感覺,沒關系,接著我們需要看一下這個ReflectionAction的實現就知道了。再看它的實現之前,我們需要先看一下RemoteViews的apply方法以及Action類的實現,首先看一下RemoteViews的apply方法,如下所示。
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result;
...
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// Clone inflater so we load resources from correct context and
// we don't add a filter to the static version returned by getSystem-
Service.
inflater = inflater.cloneInContext(inflationContext);
inflater.setFilter(this);
result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
rvToApply.performApply(result, parent, handler);
return result;
}
從上面代碼可以看出,首先會通過LayoutInflater去加載RemoteViews中的布局文件,RemoteViews中的布局文件可以通過getLayoutId這個方法獲得,加載完布局文件后會通過performApply去執行一些更新操作,代碼如下所示。
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
if (mActions ! = null) {
handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
final int count = mActions.size();
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
a.apply(v, parent, handler);
}
}
}
performApply的實現就比較好理解了,它的作用就是遍歷mActions這個列表并執行每個Action對象的apply方法。還記得mAction嗎?每一次的set操作都會對應著它里面的一個Action對象,因此我們可以斷定,Action對象的apply方法就是真正操作View的地方,實際上的確如此。
RemoteViews在通知欄和桌面小部件中的工作過程和上面描述的過程是一致的,當我們調用RemoteViews的set方法時,并不會立刻更新它們的界面,而必須要通過Notification-Manager的notify方法以及AppWidgetManager的updateAppWidget才能更新它們的界面。實際上在AppWidgetManager的updateAppWidget的內部實現中,它們的確是通過RemoteViews的apply以及reapply方法來加載或者更新界面的,apply和reApply的區別在于:apply會加載布局并更新界面,而reApply則只會更新界面。通知欄和桌面小插件在初始化界面時會調用apply方法,而在后續的更新界面時則會調用reapply方法。這里先看一下BaseStatusBar的updateNotificationViews方法中,如下所示。
private void updateNotificationViews(NotificationData.Entry entry,
StatusBarNotification notification, boolean isHeadsUp) {
final RemoteViews contentView = notification.getNotification().
contentView;
final RemoteViews bigContentView = isHeadsUp
? notification.getNotification().headsUpContentView
: notification.getNotification().bigContentView;
final Notification publicVersion = notification.getNotification().
publicVersion;
final RemoteViews publicContentView = publicVersion ! = null ? public-
Version.contentView : null;
// Reapply the RemoteViews
contentView.reapply(mContext, entry.expanded, mOnClickHandler);
...
}
很顯然,上述代碼表示當通知欄界面需要更新時,它會通過RemoteViews的reapply方法來更新界面。
接著再看一下AppWidgetHostView的updateAppWidget方法,在它的內部有如下一段代碼:
mRemoteContext = getRemoteContext();
int layoutId = remoteViews.getLayoutId();
// If our stale view has been prepared to match active, and the new
// layout matches, try recycling it
if (content == null && layoutId == mLayoutId) {
try {
remoteViews.reapply(mContext, mView, mOnClickHandler);
content = mView;
recycled = true;
if (LOGD) Log.d(TAG, "was able to recycled existing layout");
} catch (RuntimeException e) {
exception = e;
}
}
// Try normal RemoteView inflation
if (content == null) {
try {
content = remoteViews.apply(mContext, this, mOnClickHandler);
if (LOGD) Log.d(TAG, "had to inflate new layout");
} catch (RuntimeException e) {
exception = e;
}
}
從上述代碼可以發現,桌面小部件在更新界面時也是通過RemoteViews的reapply方法來實現的。
了解了apply以及reapply的作用以后,我們再繼續看一些Action的子類的具體實現,首先看一下ReflectionAction的具體實現,它的源碼如下所示。
private final class ReflectionAction extends Action {
ReflectionAction(int viewId, String methodName, int type, Object value) {
this.viewId = viewId;
this.methodName = methodName;
this.type = type;
this.value = value;
}
...
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler
handler) {
final View view = root.findViewById(viewId);
if (view == null) return;
Class<? > param = getParameterType();
if (param == null) {
throw new ActionException("bad type: " + this.type);
}
try {
getMethod(view, this.methodName, param).invoke(view, wrapArg
(this.value));
} catch (ActionException e) {
throw e;
} catch (Exception ex) {
throw new ActionException(ex);
}
}
}
通過上述代碼可以發現,ReflectionAction表示的是一個反射動作,通過它對View的操作會以反射的方式來調用,其中getMethod就是根據方法名來得到反射所需的Method對象。使用ReflectionAction的set方法有:setTextViewText、setBoolean、setLong、setDouble等。除了ReflectionAction,還有其他Action,比如TextViewSizeAction、ViewPaddingAction、SetOnClickPendingIntent等。這里再分析一下TextViewSizeAction,它的實現如下所示。
private class TextViewSizeAction extends Action {
public TextViewSizeAction(int viewId, int units, float size) {
this.viewId = viewId;
this.units = units;
this.size = size;
}
...
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler
handler) {
final TextView target = (TextView) root.findViewById(viewId);
if (target == null) return;
target.setTextSize(units, size);
}
public String getActionName() {
return "TextViewSizeAction";
}
int units;
float size;
public final static int TAG = 13;
}
TextViewSizeAction的實現比較簡單,它之所以不用反射來實現,是因為setTextSize這個方法有2個參數,因此無法復用ReflectionAction,因為ReflectionAction的反射調用只有一個參數。其他Action這里就不一一進行分析了,讀者可以查看RemoteViews的源代碼。
關于單擊事件,RemoteViews中只支持發起PendingIntent,不支持onClickListener那種模式。另外,我們需要注意setOnClickPendingIntent、setPendingIntentTemplate以及setOnClickFillInIntent它們之間的區別和聯系。首先setOnClickPendingIntent用于給普通View設置單擊事件,但是不能給集合(ListView和StackView)中的View設置單擊事件,比如我們不能給ListView中的item通過setOnClickPendingIntent這種方式添加單擊事件,因為開銷比較大,所以系統禁止了這種方式;其次,如果要給ListView和StackView中的item添加單擊事件,則必須將setPendingIntentTemplate和setOnClickFillInIntent組合使用才可以。
- 前言
- 第1章 Activity的生命周期和啟動模式
- 1.1 Activity的生命周期全面分析
- 1.1.1 典型情況下的生命周期分析
- 1.1.2 異常情況下的生命周期分析
- 1.2 Activity的啟動模式
- 1.2.1 Activity的LaunchMode
- 1.2.2 Activity的Flags
- 1.3 IntentFilter的匹配規則
- 第2章 IPC機制
- 2.1 Android IPC簡介
- 2.2 Android中的多進程模式
- 2.2.1 開啟多進程模式
- 2.2.2 多進程模式的運行機制
- 2.3 IPC基礎概念介紹
- 2.3.1 Serializable接口
- 2.3.2 Parcelable接口
- 2.3.3 Binder
- 2.4 Android中的IPC方式
- 2.4.1 使用Bundle
- 2.4.2 使用文件共享
- 2.4.3 使用Messenger
- 2.4.4 使用AIDL
- 2.4.5 使用ContentProvider
- 2.4.6 使用Socket
- 2.5 Binder連接池
- 2.6 選用合適的IPC方式
- 第3章 View的事件體系
- 3.1 View基礎知識
- 3.1.1 什么是View
- 3.1.2 View的位置參數
- 3.1.3 MotionEvent和TouchSlop
- 3.1.4 VelocityTracker、GestureDetector和Scroller
- 3.2 View的滑動
- 3.2.1 使用scrollTo/scrollBy
- 3.2.2 使用動畫
- 3.2.3 改變布局參數
- 3.2.4 各種滑動方式的對比
- 3.3 彈性滑動
- 3.3.1 使用Scroller7
- 3.3.2 通過動畫
- 3.3.3 使用延時策略
- 3.4 View的事件分發機制
- 3.4.1 點擊事件的傳遞規則
- 3.4.2 事件分發的源碼解析
- 3.5 View的滑動沖突
- 3.5.1 常見的滑動沖突場景
- 3.5.2 滑動沖突的處理規則
- 3.5.3 滑動沖突的解決方式
- 第4章 View的工作原理
- 4.1 初識ViewRoot和DecorView
- 4.2 理解MeasureSpec
- 4.2.1 MeasureSpec
- 4.2.2 MeasureSpec和LayoutParams的對應關系
- 4.3 View的工作流程
- 4.3.1 measure過程
- 4.3.2 layout過程
- 4.3.3 draw過程
- 4.4 自定義View
- 4.4.1 自定義View的分類
- 4.4.2 自定義View須知
- 4.4.3 自定義View示例
- 4.4.4 自定義View的思想
- 第5章 理解RemoteViews
- 5.1 RemoteViews的應用
- 5.1.1 RemoteViews在通知欄上的應用
- 5.1.2 RemoteViews在桌面小部件上的應用
- 5.1.3 PendingIntent概述
- 5.2 RemoteViews的內部機制
- 5.3 RemoteViews的意義
- 第6章 Android的Drawable
- 6.1 Drawable簡介
- 6.2 Drawable的分類
- 6.2.1 BitmapDrawable2
- 6.2.2 ShapeDrawable
- 6.2.3 LayerDrawable
- 6.2.4 StateListDrawable
- 6.2.5 LevelListDrawable
- 6.2.6 TransitionDrawable
- 6.2.7 InsetDrawable
- 6.2.8 ScaleDrawable
- 6.2.9 ClipDrawable
- 6.3 自定義Drawable
- 第7章 Android動畫深入分析
- 7.1 View動畫
- 7.1.1 View動畫的種類
- 7.1.2 自定義View動畫
- 7.1.3 幀動畫
- 7.2 View動畫的特殊使用場景
- 7.2.1 LayoutAnimation
- 7.2.2 Activity的切換效果
- 7.3 屬性動畫
- 7.3.1 使用屬性動畫
- 7.3.2 理解插值器和估值器 /
- 7.3.3 屬性動畫的監聽器
- 7.3.4 對任意屬性做動畫
- 7.3.5 屬性動畫的工作原理
- 7.4 使用動畫的注意事項
- 第8章 理解Window和WindowManager
- 8.1 Window和WindowManager
- 8.2 Window的內部機制
- 8.2.1 Window的添加過程
- 8.2.2 Window的刪除過程
- 8.2.3 Window的更新過程
- 8.3 Window的創建過程
- 8.3.1 Activity的Window創建過程
- 8.3.2 Dialog的Window創建過程
- 8.3.3 Toast的Window創建過程
- 第9章 四大組件的工作過程
- 9.1 四大組件的運行狀態
- 9.2 Activity的工作過程
- 9.3 Service的工作過程
- 9.3.1 Service的啟動過程
- 9.3.2 Service的綁定過程
- 9.4 BroadcastReceiver的工作過程
- 9.4.1 廣播的注冊過程
- 9.4.2 廣播的發送和接收過程
- 9.5 ContentProvider的工作過程
- 第10章 Android的消息機制
- 10.1 Android的消息機制概述
- 10.2 Android的消息機制分析
- 10.2.1 ThreadLocal的工作原理
- 10.2.2 消息隊列的工作原理
- 10.2.3 Looper的工作原理
- 10.2.4 Handler的工作原理
- 10.3 主線程的消息循環
- 第11章 Android的線程和線程池
- 11.1 主線程和子線程
- 11.2 Android中的線程形態
- 11.2.1 AsyncTask
- 11.2.2 AsyncTask的工作原理
- 11.2.3 HandlerThread
- 11.2.4 IntentService
- 11.3 Android中的線程池
- 11.3.1 ThreadPoolExecutor
- 11.3.2 線程池的分類
- 第12章 Bitmap的加載和Cache
- 12.1 Bitmap的高效加載
- 12.2 Android中的緩存策略
- 12.2.1 LruCache
- 12.2.2 DiskLruCache
- 12.2.3 ImageLoader的實現446
- 12.3 ImageLoader的使用
- 12.3.1 照片墻效果
- 12.3.2 優化列表的卡頓現象
- 第13章 綜合技術
- 13.1 使用CrashHandler來獲取應用的crash信息
- 13.2 使用multidex來解決方法數越界
- 13.3 Android的動態加載技術
- 13.4 反編譯初步
- 13.4.1 使用dex2jar和jd-gui反編譯apk
- 13.4.2 使用apktool對apk進行二次打包
- 第14章 JNI和NDK編程
- 14.1 JNI的開發流程
- 14.2 NDK的開發流程
- 14.3 JNI的數據類型和類型簽名
- 14.4 JNI調用Java方法的流程
- 第15章 Android性能優化
- 15.1 Android的性能優化方法
- 15.1.1 布局優化
- 15.1.2 繪制優化
- 15.1.3 內存泄露優化
- 15.1.4 響應速度優化和ANR日志分析
- 15.1.5 ListView和Bitmap優化
- 15.1.6 線程優化
- 15.1.7 一些性能優化建議
- 15.2 內存泄露分析之MAT工具
- 15.3 提高程序的可維護性