天天看點

Window視窗機制——WindowManager,ViewRootImpl,View了解

在之前的文章中我們說到DecorView在handleResumeActivity方法中被綁定到了WindowManager,也就是調用了windowManager.addView(decorView)。而WindowManager的實作類是WindowManagerImpl,而它則是通過WindowManagerGlobal代理實作addView的,我們來看一下addView()方法

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;
	ViewRootImpl root;
    View panelParentView = null;

	root = new ViewRootImpl(view.getContext(), display);
	view.setLayoutParams(wparams);
	mViews.add(view);
	mRoots.add(root);
	mParams.add(wparams);
	//ViewRootImpl開始繪制view
	root.setView(view, wparams, panelParentView);
}

           

可以看到在WindowManagerGlobal的addView中,最後是調用了ViewRootImpl的setView方法,我們來看一下ViewRootImpl。

ViewRootImpl

ViewRootImpl是一個視圖層次結構的頂部,它實作了View與WindowManager之間所需要的協定,作為WindowManagerGlobal中大部分的内部實作,也就說WindowManagerGlobal方法最後還是調用到了ViewRootImpl。

addView,removeView,update調用順序

WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

	requestLayout();

	res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
}

           

在setView方法中,首先會調用到requestLayout(),表示添加Window之前先完成第一次layout布局過程,以確定在收到任何系統事件後面重新布局。requestLayout最終會調用performTraversals方法來完成View的繪制。

接着會通過WindowSession最終來完成Window的添加過程。在下面的代碼中mWindowSession類型是IWindowSession,它是一個Binder對象,真正的實作類是Session,也就是說這其實是一次IPC過程,遠端調用了Session中的addToDisPlay方法。

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

           

這裡的mService就是WindowManagerService,也就是說Window的添加請求,最終是通過WindowManagerService來添加的。

View通過ViewRootImpl來繪制

前面說到,ViewRootImpl調用到requestLayout()來完成View的繪制操作:

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

           

View繪制,先判斷目前線程,如果不是目前線程則抛出異常,當你在子線程更新UI沒使用handler的話就會抛出這個異常。一般在子線程操作UI都會調用到view.invalidate,而View的重繪會觸發ViewRootImpl的requestLayout,就會去判斷目前線程。

判斷完線程後,接着調用scheduleTraversals()。

void scheduleTraversals() {
	if (!mTraversalScheduled) {
		mTraversalScheduled = true;
		mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
	}
}

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

void doTraversal() {
	performTraversals();
}

private void performTraversals() {  
        ... 
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ...
        performDraw();
        ...
    }
//這幾個方法最終都會調用
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,requestLayout最終也會調用performTraversals,來重新繪制View。

View何時被繪制

以SetContentView為例,當Activity的onCreate調用到了setContentView後,view就會被繪制了嗎?肯定不是,setContentView隻是把需要添加的View的結構添加儲存在DecorView中,此時的DecorView還并沒有被繪制(沒有觸發view.measure,layout,draw)。

DecorView真正的繪制顯示是在activity.handleResumeActivity方法中DecorView被添加到WindowManager時候,也就是調用到windowManager.addView(decorView)。而在windowManager.addView方法中調用到windowManagerGlobal.addView,開始建立初始化ViewRootImpl,再調用到viewRootImpl.setView,最後是調用到viewRootImpl的performTraversals來進行view的繪制(measure,layout,draw),這個時候View才真正被繪制出來。

這也就是為什麼我們在onCreate方法中調用view.getMeasureHeight() = 0的原因,我們知道activity.handleResumeActivity最後調用到的是activity的onResume方法,但是按上面所說在onResume方法中調用就可以得到了嗎,答案肯定是否定的,因為ViewRootImpl繪制View并非是同步的,而是異步(Handler)。

而是通過以下方式監聽:

view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {

    }
});

           

因為在viewRootImpl的performTraversals的繪制最後,調用了

private void performTraversals() {
	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
	performLayout(lp, mWidth, mHeight);

	if (triggerGlobalLayoutListener) {
		mAttachInfo.mRecomputeGlobalAttributes = false;
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
	}
	performDraw();
}

           

dispatchOnGlobalLayout會觸發OnGlobalLayoutListener的onGlobalLayout()函數回調

但此時View并還沒有繪制顯示出來,隻是先調用了measure和layout,但也可以得到它的寬高了。

public class Window/PhoneWindow(){
	public WindowManager getWindowManager() {
        return mWindowManager;
    }
	
	public final Callback getCallback() {
        return mCallback;
    }

	public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

	private ViewRootImpl getViewRootImpl() {
        if (mDecor != null) {
            ViewRootImpl viewRootImpl = mDecor.getViewRootImpl();
            if (viewRootImpl != null) {
                return viewRootImpl;
            }
        }
        throw new IllegalStateException("view not added");
    }
}
public class Activity(){
	public Window getWindow() {
		//PhoneWindow
        return mWindow;
    }

	public WindowManager getWindowManager() {
		//WindowManagerImpl
        return mWindowManager;
    }
}

public class View(){
	public ViewRootImpl getViewRootImpl() {
        if (mAttachInfo != null) {
            return mAttachInfo.mViewRootImpl;
        }
        return null;
    }

	protected IWindow getWindow() {
        return mAttachInfo != null ? mAttachInfo.mWindow : null;
    }
}

           
  • 之是以說ViewRootImpl是View和WindowManager的橋梁,是因為在真正操控繪制View的是ViewRootImpl,View通過WindowManager來轉接調用ViewRootImpl;
  • 在ViewRootImpl未初始化建立的時候是可以進行子線程更新UI的,而它建立是在activity.handleResumeActivity方法調用,即DecorView被添加到WindowManager的時候;
  • ViewRootImpl繪制View的時候會先檢查目前線程是否是主線程,是才能繼續繪制下去;
    Window視窗機制——WindowManager,ViewRootImpl,View了解