Android中的ViewRootImpl類原始碼解析
ViewRoot目前這個類已經沒有了,是老版本中的一個類,在Android2.2以後用ViewRootImpl代替ViewRoot,對應於ViewRootImpl.java,他是連結WindowManager和DecorView的紐帶,另外View的繪製也是通過ViewRootImpl來完成的。
它的主要作用我的總結為如下:
A:連結WindowManager和DecorView的紐帶,更廣一點可以說是Window和View之間的紐帶。
B:完成View的繪製過程,包括measure、layout、draw過程。
C:向DecorView分發收到的使用者發起的event事件,如按鍵,觸屏等事件。
注:如果分析不對的地方,歡迎批評指正。
一、連結WindowManager和DecorView。
首先說第一個主要作用,連結WindowManager和DecorView,在ViewRootImpl.java中,開始的註釋如下:
?1 2 3 4 5 6 7 |
/**
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
* detail of {@link WindowManagerGlobal}. *
* {@hide}
*/
|
通過這一段註釋,我們知道,ViewRootImpl他是View樹的樹根,但它卻又不是View,實現了View與WindowManager之間的通訊協議,具體的實現詳情在WindowManagerGlobal這個類中。
那麼View與WindowManager之間是怎麼建立聯絡的呢,WindowManager所提供的功能很簡單,常用的只有三個方法,即新增View,更新View和刪除View,當然還有其它功能哈,比如改變Window的位置,WindowManager操作Window的過程更像是在操作Window中的View,這三個方法定義在ViewManager中,而WindowManager繼承了ViewManager。
1 2 3 4 5 |
public
interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
*
|
Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. *
Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a * secondary {@link Display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }
Window是一個抽象的概念,每一個Window都對應著一個View和一個ViewRootImpl,Window又通過ViewRootImpl與View建立聯絡,因此Window並不是實際存在的,他是以View的形式存在的。這點從WindowManager的定義也可以看出,它提供的三個介面方法addView,updateView,removeView都是針對View的,這說明View才是Window的實體,在實際使用中無法直接訪問Window,對Window的訪問必須通過WindowManager。而對Window的訪問(新增,更新,刪除)都是通過ViewRootImpl實現的。這裡以Window的新增過程為例,刪除過程,更新過程就不再贅述了。Window的新增過程
Window的新增過程需要通過WindowManager的addView來實現,WindowManager又是一個介面,它的實現類是WindowManagerImpl,在WindowManagerImpl中的三大操作如下:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Override
public
void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public
void updateViewLayout(View view, ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public
void removeView(View view) {
mGlobal.removeView(view,
false );
}
@Override
public
void removeViewImmediate(View view) {
mGlobal.removeView(view,
true );
}
|
可以看出,WindowManagerImpl又呼叫了WindowManagerGloble的三大操作方法,這正好說明了ViewRootImpl類上面一開始那個註釋了。This is for the most part an internal implementationdetail of {@link WindowManagerGlobal}.
addView方法原始碼如下:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
public
void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if
(view == null ) {
throw
new IllegalArgumentException( "view must not be null" );
}
if
(display == null ) {
throw
new IllegalArgumentException( "display must not be null" );
}
if
(!(params instanceof
WindowManager.LayoutParams)) {
throw
new IllegalArgumentException( "Params must be WindowManager.LayoutParams" );
}
final
WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
if
(parentWindow != null ) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
else {
// If there's no parent and we're running on L or above (or in the
// system context), assume we want hardware acceleration.
final
Context context = view.getContext();
if
(context != null
&& context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView =
null ;
synchronized
(mLock) {
// Start watching for system property changes.
if
(mSystemPropertyUpdater == null ) {
mSystemPropertyUpdater =
new Runnable() {
@Override
public void
run() {
synchronized
(mLock) {
for
( int
i = mRoots.size() - 1 ; i >=
0 ; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int
index = findViewLocked(view, false );
if
(index >= 0 ) {
if
(mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
}
else {
throw
new IllegalStateException( "View "
+ view
+
" has already been added to the window manager." );
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if
(wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final
int count = mViews.size();
for
( int
i = 0 ; i < count; i++) {
if
(mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
root =
new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try
{
root.setView(view, wparams, panelParentView);
}
catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized
(mLock) {
final
int index = findViewLocked(view,
false );
if
(index >= 0 ) {
removeViewLocked(index,
true );
}
}
throw
e;
}
}
|
addView方法主要分為如下幾步:
1、檢查引數是否合法
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if
(view == null ) {
throw
new IllegalArgumentException( "view must not be null" );
}
if
(display == null ) {
throw
new IllegalArgumentException( "display must not be null" );
}
if
(!(params instanceof
WindowManager.LayoutParams)) {
throw
new IllegalArgumentException( "Params must be WindowManager.LayoutParams" );
}
final
WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
if
(parentWindow != null ) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
|
2、建立ViewRootImpl並將View新增到列表中
在WindowManagerGlobal內部有如下幾個列表比較重要:
?1 2 3 4 5 6 |
private
final ArrayList<view> mViews =
new ArrayList<view>();
private
final ArrayList<viewrootimpl> mRoots =
new ArrayList<viewrootimpl>();
private
final ArrayList<windowmanager.layoutparams> mParams =
new
ArrayList<windowmanager.layoutparams>();
private
final ArraySet<view> mDyingViews =
new ArraySet<view>();
</view></view></windowmanager.layoutparams></windowmanager.layoutparams></viewrootimpl></viewrootimpl></view></view>
|
在上面的宣告中嗎,mViews儲存的是所有Window所對應的View,mRoots儲存的是所有Window所對應的ViewRootImpl,mParams儲存的是所有Window所對應的佈局引數,而mDyingViews儲存了那些正在被刪除的View物件,或者說是那些已經呼叫removeView方法但是還沒有刪除的Window物件。在addView方法中通過如下方式將Window的一系列物件新增到列表中。 ?
1 2 3 4 5 6 7 |
root = new
ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
|
這個步驟由ViewRootImpl的setView方法來完成,
?1 |
root.setView(view, wparams, panelParentView);
|
在setView內部會通過requestLayout來完成非同步重新整理請求,requestLayout最終會呼叫performTraversals方法來完成View的繪製,原始碼註釋如下:差不多意思就是在新增Window之前先完成第一次layout佈局過程,以確保在收到任何系統事件後面重新佈局。
?1 2 3 4 |
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
|
接著會通過WindowSession最終來完成Window的新增過程。在下面的程式碼中mWindowSession型別是IWindowSession,它是一個Binder物件,真正的實現類是Session,也就是說這其實是一次IPC過程,遠端呼叫了Session中的addToDisPlay方法。
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
try
{
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes =
true ;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
}
catch (RemoteException e) {
mAdded =
false ;
mView =
null ;
mAttachInfo.mRootView =
null ;
mInputChannel =
null ;
mFallbackEventHandler.setView( null );
unscheduleTraversals();
setAccessibilityFocus( null ,
null );
throw
new RuntimeException( "Adding window failed" , e);
}
|
Session中的addToDisPlay方法如下:Session這個類在package com.android.server.wm ?
1 2 3 4 5 6 7 |
@Override
public
int addToDisplay(IWindow window, int
seq, WindowManager.LayoutParams attrs,
int
viewVisibility, int
displayId, Rect outContentInsets,
InputChannel outInputChannel) {
return
mService.addWindow( this , window, seq, attrs, viewVisibility, displayId,
outContentInsets, outInputChannel);
}
|
WindowManager——>WindowManagerGobal——>ViewRootImpl——>Session——>WindowManagerService
那麼WindowManager又是如何與DecorView相連的呢,最終DecorView肯定是要新增到Window上的,而Window的具體實現類是PhoneWindow,因為DecorView嵌入在Window上,如圖所示:
在ActivityThread中,當Activity物件被建立完畢後,會將DecorView新增到Window中,同時會建立ViewRootImpl物件,並將ViewRootImpl物件和DecorView建立關聯,可以參考一下程式碼,在ActvityThread中,也就是ViewRootImpl是DecorView的父元素,但是ViewRootImpl並不是View。
?1 2 3 4 5 6 7 8 9 10 11 12 |
<span style= "white-space:pre" > </span>r.window = r.activity.getWindow();
View decor = r.window.getDecorView(); //獲得DecorView
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if
(a.mVisibleFromClient) {
a.mWindowAdded =
true ;
wm.addView(decor, l); //通過WindowManager新增decorView
}
|
我們平時在Activity呼叫setContentView會呼叫PhoneWindow的setContentView,最後會呼叫DecorView的addView方法,這也說明了我們新增的View是DecorView的子元素。
二、完成View的繪製過程
整個View樹的繪圖流程是在ViewRootImpl類的performTraversals()方法(這個方法巨長)開始的,該函式做的執行過程主要是根據之前設定的狀態,判斷是否重新計算檢視大小(measure)、是否重新放置檢視的位置(layout)、以及是否重繪 (draw),其核心也就是通過判斷來選擇順序執行這三個方法中的哪個,如下:
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private
void performTraversals() {
......
//最外層的根檢視的widthMeasureSpec和heightMeasureSpec由來
//lp.width和lp.height在建立ViewGroup例項時等於MATCH_PARENT
int
childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int
childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout( 0 ,
0 , mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
}
|
performTraversals方法會經過measure、layout和draw三個過程才能將一個View繪製出來,所以View的繪製是ViewRootImpl完成的,另外當手動呼叫invalidate,postInvalidate,requestInvalidate也會最終呼叫performTraversals,來重新繪製View。其中requestLayout()方法會呼叫measure過程和layout過程,不會呼叫draw過程,也不會重新繪製任何View包括該呼叫者本身。
三、向DecorView分發事件。
這裡的事件不僅僅包括MotionEvent,還有KeyEvent。我們知道View的時間分發順序為Activity——>Window——>View,那麼Activity的事件來源在哪裡呢?這是個需要思考的問題,答案和ViewRootImpl有很大的關係。
首先,事件的根本來源來自於Native層的嵌入式硬體,然後會經過InputEventReceiver接受事件,然後交給ViewRootImpl,將事件傳遞給DecorView,DecorView再交給PhoneWindow,PhoneWindow再交給Activity。這樣看來,整個體系的事件分發順序為:
那麼這一過程又是怎麼實現的呢?
首先看ViewRootImpl的dispatchInputEvent方法。
?1 2 3 4 5 6 7 8 |
public
void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = event;
args.arg2 = receiver;
Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
msg.setAsynchronous( true );
mHandler.sendMessage(msg);
}
|
InputEvent輸入事件,它有2個子類:KeyEvent和MotionEvent,其中KeyEvent表示鍵盤事件,而MotionEvent表示點選事件,這裡InputEventReceiver譯為輸入事件接收者,顧名思義,就是用於接收輸入事件,然後交給ViewRootImpl的dispatchInputEvent方法去分發處理。可以看到mHandler將邏輯切換到UI執行緒,程式碼如下。
?
1 |
final
ViewRootHandler mHandler = new
ViewRootHandler();
|
?
1 2 |
<span style= "color:#333333;" >
final class
ViewRootHandler extends
Handler {
</span>
|
@Override public void handleMessage(Message msg) { switch (msg.what) {
?
1 |
<span style= "white-space:pre" > </span>.........
|
1 |
case MSG_DISPATCH_INPUT_EVENT: { SomeArgs args = (SomeArgs)msg.obj; InputEvent event = (InputEvent)args.arg1; InputEventReceiver receiver = (InputEventReceiver)args.arg2; enqueueInputEvent(event, receiver, 0, true); args.recycle(); } break;
?
1 |
1 |
<span style= "white-space:pre" > </span>.................
|
1 |
<span style= "white-space:pre" > </span> }
|
{ ?
1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
void
enqueueInputEvent(InputEvent event,
InputEventReceiver receiver,
int flags,
boolean processImmediately) {
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// Always enqueue the input event in order, regardless of its time stamp.
// We do this because the application or the IME may inject key events
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
QueuedInputEvent last = mPendingInputEventTail;
if
(last == null ) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
}
else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount +=
1 ;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
if
(processImmediately) {
doProcessInputEvents();
}
else {
scheduleProcessInputEvents();
}
}
|
enqueueInputEvent方法又會呼叫doProcessInputEvents方法或者scheduleProcessInputEvents方法,這其實是同步或者同步處理訊息佇列的,同步或者非同步根據傳入的標誌位processImmediately來判斷。scheduleProcessInputEvents方法只是利用mHandler向UI執行緒傳送了一個message,程式碼如下: ?
1 2 3 4 5 6 7 8 |
private
void scheduleProcessInputEvents() {
if
(!mProcessInputEventsScheduled) {
mProcessInputEventsScheduled =
true ;
Message msg = mHandler.obtainMessage(MSG_PROCESS_INPUT_EVENTS);
msg.setAsynchronous( true );
mHandler.sendMessage(msg);
}
}
|
UI執行緒處理的程式碼為:
相關推薦
Android中的ViewRootImpl類原始碼解析
ViewRoot目前這個類已經沒有了,是老版本中的一個類,在Android2.2以後用ViewRootImpl代替ViewRoot,對應於ViewRootImpl.java,他是連結WindowManager和DecorView的紐帶,另外View的繪製也是通過ViewRootImpl來完成的。 它的主要作
Android執行緒池(四)ThreadPoolExecutor類原始碼解析
使用ThreadPoolExecutor private final int CORE_POOL_SIZE = 4;//核心執行緒數 private final int MAX_POOL_SIZE = 5;//最大執行緒數 priv
android中Log類的封裝
col mark pre class 打印日誌 static sta 日誌 blog 1.為了方便的使用Log打印日誌,以及後續方便撤銷日誌打印,所以對Log類進行封裝是一件好事。 1 package market.phone; 2 3 import androi
Android中Calendar類的用法總結
jsb ews 寫法 需要 key data- minute bar 來講 Calendar是Android開發中需要獲取時間時必不可少的一個工具類,通過這個類可以獲得的時間信息還是很豐富的,下面做一個總結,以後使用的時候就不用總是去翻書或者查資料了。 在獲取時間之前要先獲
Android中三種常用解析XML的方式(DOM、SAX、PULL)簡介及區別
字符串 lan win name屬性 Coding 空間 toc log fin XML在各種開發中都廣泛應用,Android也不例外。作為承載數據的一個重要角色,如何讀寫XML成為Android開發中一項重要的技能。今天就由我向大家介紹一下在Android平臺下幾種常見的
Java集合類原始碼解析:AbstractMap
目錄 引言 原始碼解析 抽象函式entrySet() 兩個集合檢視 操作方法 兩個子類 參考: 引言 今天學習一個Java集合的一個抽象類 AbstractMap ,AbstractMap 是Map介面的 實現類之一,也是HashMap、T
Java集合類原始碼解析:HashMap (基於JDK1.8)
目錄 前言 HashMap的資料結構 深入原始碼 兩個引數 成員變數 四個構造方法 插入資料的方法:put() 雜湊函式:hash() 動態擴容:resize() 節點樹化、紅黑樹的拆分 節點樹化
Java集合類原始碼解析:Vector
引言 之前的文章我們學習了一個集合類 ArrayList,今天講它的一個兄弟 Vector。 為什麼說是它兄弟呢?因為從容器的構造來說,Vector 簡直就是 ArrayList 的翻版,也是基於陣列的資料結構,不同的是,Vector的每個方法都加了 synchronized 修飾符,是執行緒安全的。 類
Android Handler訊息機制原始碼解析
好記性不如爛筆頭,今天來分析一下Handler的原始碼實現 Handler機制是Android系統的基礎,是多執行緒之間切換的基礎。下面我們分析一下Handler的原始碼實現。 Handler訊息機制有4個類合作完成,分別是Handler,MessageQueue,Looper,Message Handl
Thread類原始碼解析
原始碼版本:jdk8 其中的部分論證和示例程式碼:Java_Concurrency 類宣告: Thread本身實現了Runnable介面 Runnable:任務,《java程式設計思想》中表示該命名不好,或許叫Task更好; Thread:執行緒,執行任務的載體; public class T
你一定會需要的FutureTask線上程池中應用和原始碼解析
FutureTask 是一個支援取消的非同步處理器,一般線上程池中用於非同步接受callable返回值。 主要實現分三部分: 封裝 Callable,然後放到執行緒池中去非同步執行->run。 獲取結果-> get。 取消任務-> cancel。 接下來主
Android PriorityQueue和PriorityBlockingQueue原始碼解析
尊重原創,轉載請標明出處 http://blog.csdn.net/abcdef314159 原始碼:\sources\Android-25 PriorityQueue通過名字也可以看的出來,是優先佇列,PriorityBlockingQueue是優先阻
Android 7.0 startActivity()原始碼解析
本文並不是非常詳細地解釋startActivity()原始碼每行程式碼的具體作用(實際上也根本做不到),所以我省略了很多程式碼,只保留了最核心的程式碼。我研究這段原始碼的目的是解決以下幾個我在開發應用的過程中所思考的問題: 是通過何種方式生成一個新的Activity類的,是
Android中Application類總結
application類的使用 要使用自定義的Application,首先就是要自己新建一個Application的子類,然後把它的名字寫在manifest檔案裡面的application標籤裡的android:name屬性就行,如我的Application子類名字
Android Hawk資料庫的原始碼解析,Github開源專案,基於SharedPreferences的的儲存框架
今天看了朋友一個專案用到了Hawk,然後寫了這邊文章 一、瞭解一下概念 Android Hawk資料庫github開源專案 Hawk是一個非常便捷的資料庫.操作資料庫只需一行程式碼,能存任何資料型別. 相信大家應該很熟悉SharedPreferences。它是一種輕量級的儲存簡單配置
Java集合類原始碼解析:ArrayList
目錄 前言 今天學習一個Java集合類使用最多的類 ArrayList , ArrayList 繼承了 AbstractList,並實現了List 和 RandomAccess 等介面, public class ArrayList<E> extends AbstractList<E>
Java集合類原始碼解析:LinkedHashMap
前言 今天繼續學習關於Map家族的另一個類 LinkedHashMap 。先說明一下,LinkedHashMap 是繼承於 HashMap 的,所以本文只針對 LinkedHashMap 的特性學習,跟HashMap 相關的一些特性就不做進一步的解析了,大家有疑惑的可以看之前的博文。 深入解析 LinkedH
Android中Path類的lineTo方法和quadTo方法畫線的區別
當我們需要在螢幕上形成畫線時,Path類的應用是必不可少的,而Path類的lineTo和quadTo方法實現的繪製線路形式也是不一樣的,下面就以程式碼的實現來直觀的探究這兩個方法的功能實現區別; 1. Path--->quadTo(float x1, fl
Swift-->R.swift帶你體驗Android中R類的便利
安裝需要2點: 1:需要執行一段指令碼 "$PODS_ROOT/R.swift/rswift" "$SRCROOT" 2:指令碼生成的檔案R.generated.swift,需要複製到專案中, 這樣才能愉快的使用R.swift 使
java中object類原始碼
package java.lang; public class Object { /* 一個本地方法,具體是用C(C++)在DLL中實現的,然後通過JNI呼叫。*/ private static native void registerNa