ViewRootImpl是一個及其重要的類,主要作用如下:
(1)將DecorView傳遞給WindowManagerSerive。
(2)完成View的繪制過程,包括measure、layout、draw過程。
(3)向DecorView分發收到的用戶發起的event事件,如按鍵,觸屏等事件。
此外,ViewRootImpl中還包含了兩個需要重點關注的內部類:
(1)final class ViewRootHandler extends Handler
用于向DecorView分發事件
(2)static class W extends IWindow.Stub
W是ViewRootImpl的一個嵌入類,也是一個Binder服務。通過mWindowsession.addToDisplay函數傳入WMS,用來在WMS中通過Binder回調。
/** * 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。ViewRootImpl的創建在ActivityThread中,調用棧如下: ActivityThread#handleResumeActivity()->WindowManagerImpl#addView()->WindowManagerGlobal#addView()->ViewRootImpl(view.getContext(),Display)。 1.1 構造函數選取ViewRootImpl構造函數的一部分源碼如下:mWindowSession = WindowManagerGlobal.getWindowSession(); mWindow = new W(this); mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this); 第一句mWindowSession賦值為WindowManagerGlobal.getWindowSession(),其返回值為一個Session對象,Session的實例化在WMS進程中進行的,ViewRootImpl中有一個Session的代理對象,所以可以通過Session主要調用WMS中一些方法。 第二、三句中的W繼承于IWindow.Stub,后者繼承于Binder又實現了IWindow接口,因此這個W是可以ipC的。第三句中將mWindow賦值給了View.AttachInfo中的mWindow對象,將mWindowSession賦值給了mSession變量。1.2 ViewRootImpl#setViewpublic void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; //略 requestLayout(); //略 try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } //略 } } } 首先,在setView內部會通過requestLayout來完成異步刷新請求,requestLayout最終會調用performTraversals方法來完成View的繪制。 接著,會通過WindowSession最終來完成Window的添加過程。上面的代碼中,mWindowSession類型是IWindowSession,它是一個Binder對象,真正的實現類是Session,也就是說這其實是一次IPC過程,遠程調用了Session中的addToDisPlay方法。 由此可知,接下去Window的添加請求就交給WindowManagerService去處理了。addView大概一個過程如下:WindowManager——>WindowManagerGobal——>ViewRootImpl——>Session——>WindowManagerService2、完成View的繪制過程
整個View樹的繪圖流程是在ViewRootImpl類的performTraversals()方法中進行的,該函數做的執行過程主要是根據之前設置的狀態,判斷是否重新計算視圖大小(measure)、是否重新放置視圖的位置(layout)、以及是否重繪 (draw),其核心也就是通過判斷來選擇順序執行這三個方法中的哪個,如下: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包括該調用者本身。3、向DecorView分發事件
這里的事件不僅僅包括MotionEvent,還有KeyEvent。我們知道View的時間分發順序為Activity——>Window——>View,那么Activity的事件來源在哪里呢?這是個需要思考的問題,答案和ViewRootImpl有很大的關系。 首先,事件的根本來源來自于硬件,經過native層,然后會經過InputEventReceiver接受事件,然后交給ViewRootImpl,將事件傳遞給DecorView,DecorView再交給PhoneWindow,PhoneWindow再交給Activity。這樣看來,整個體系的事件分發順序為:3.1 ViewRootImpl#dispatchInputEvent
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線程,代碼如下。final ViewRootHandler mHandler = new ViewRootHandler(); final class ViewRootHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { ........ { SomeArgs args = (SomeArgs)msg.obj; InputEvent event = (InputEvent)args.arg1; InputEventReceiver receiver = (InputEventReceiver)args.arg2; enqueueInputEvent(event, receiver, 0, true); args.recycle(); } break; ................. } (未完待續)
新聞熱點
疑難解答