View繪製流程的開啓

上一篇文章《Activity的創建》中我們說到了Activity創建後會調用其onCreate生命週期,而我們的onCreate方法一般這麼寫

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

我一般會使用setContentView方法設置Activity的界面佈局,今天我們就看下它做了什麼

1.Activity#setContentView()

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

顯然Activity自己並沒有對其做處理,而是交給了getWindow方法處理,getWindow方法返回的是Activity中的全局變量mWindow,它是Window窗口類型。Window是一個抽象類,在Android中它唯一的子類是PhoneWindow,也就是說Activity的mWindow全局變量必定是PhoneWindow類型的。我們看下mWindow是如何被創建的。

我們在上一篇文章《Activity的創建》講到了Activity是在ActivityThreadperformLaunchActivity創建的,而在創建Activity後,會調用activity.attach方法將ContextApplicationActivity綁定,而就在該方法裏我們創建了PhoneWindow對象,我們來看下Activity#attach方法

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ...
    mWindow.setWindowManager(
        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
        mToken, mComponent.flattenToString(),
    ...
}    

我們可以看到mWindow確實爲PhoneWindow的實例

既然如此,我們接下來就要看一下PhoneWindowsetContentView方法

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        //1.初始化decor
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                                                       getContext());
        transitionTo(newScene);
    } else {
        //2.加載Activity設置的界面
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ...
}

在上面2處我們可以看到我們再Activity設置的佈局被解析關聯到了mContentParent,而mContentParent是一個View,他的註釋爲This is the view in which the window contents are placed. It is either mDecor itself, or a child of mDecor where the contents go.簡單翻譯爲 這是放置窗口內容的視圖。它是mDecor本身,或者內容所在的mDecor的child 。

其實mContentParent是在上面的註釋1出installDecor()方法獲得的,下面我們看下installDecor()方法源碼

2.PhoneWindow#installDecor()

private void installDecor() {
	...
    if (mDecor == null) {
        //1.初始化decor
        mDecor = generateDecor(-1);
        ...
    } 
    ...
    if (mContentParent == null) {
        //2.初始化ContentParent
        mContentParent = generateLayout(mDecor);
        ...
    }
    ...                      
}                               

我們可以看到在2處我們通過generateLayout得到了ContentParent

我們看下generateLayout方法

protected ViewGroup generateLayout(DecorView decor) {
	int layoutResource;
	...
	layoutResource = R.layout.screen_simple;
	...
	mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
	ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	...
	return contentParent;
}

可以看到我們調用了PhoneWindowfindViewById方法,我們繼續看它的源碼

@Nullable
public <T extends View> T findViewById(@IdRes int id) {
    return getDecorView().findViewById(id);
}

PhoneWindow類自己並沒有處理而是交給了getDecorView()獲取的view對象處理

我們來看下PhoneWindowgetDecorView()方法

@Override
public final View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
    	//看這裏,是不是很眼熟
        installDecor();
    }
    return mDecor;
}

這裏返回了一個mDecor對象,大家可以看上面installDecor方法裏使用generateDecor去獲取了mDecor的對象,我們再繼續看一下generateDecor方法;

protected DecorView generateDecor(int featureId) {
	...
	return new DecorView(context, featureId, this, getAttributes());
}

直接簡單粗暴的創建了一個DecorView對象,而DecorView實際上是一個繼承了FrameLayoutViewGroup

也就是說我們再generateLayout方法裏調用ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)獲取到contentParent實際上就是在DecorView裏找到一個id爲ID_ANDROID_CONTENT(其值爲com.android.internal.R.id.content)的ViewGroup但是我們翻遍DecorView也找不到他含有一個id爲ID_ANDROID_CONTENT的child,那麼它只能是後期添加到DecorView裏面的。

我們繼續回到generateLayout方法有一樣代碼mDecor.onResourcesLoaded(mLayoutInflater, layoutResource),看名字我們猜測它就是爲了加載佈局

layoutResource的賦值爲佈局R.layout.screen_simple(這只是一種情況下的佈局)

而我們看R.layout.screen_simple的內容

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

在這裏我們看到R.id.content,我們猜測mDecor.onResourcesLoaded方法將這個佈局添加爲自己的子佈局,後面又用findViewById獲取到了id爲R.id.contentFrameLayout

我們看下DecorViewonResourcesLoaded方法

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    ...
    final View root = inflater.inflate(layoutResource, null);
    ...
    addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) root;
    ...
}

果然像我們猜測的一樣,將傳入的佈局解析後添加爲了自己的子View,那麼之前我們猜測的contentParent 爲id爲R.id.contentFrameLayout也就屬實了。

結合上面PhoneWindowsetContentView中我們將Activity的佈局文件加載到了contentParent 中我們腦海中可以建立一個View樹結構

DecorVeiw->佈局R.layout.screen_simple(LinearLayout)->R.id.contentFrameLayout->Activity定義的佈局

到了這裏我們好像也還是不清楚Activity怎麼渲染的,其實也對,上面的這些操作都是在onCreate生命週期裏處理的,我們知道onCreate生命週期時Activity根本不可見,只有在onResume生命週期裏Activity纔可見。

ActivityThreadhandleResumeActivity中會執行Activity的onResume生命週期

3. ActivityThread#handleResumeActivity()

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ...
    //調用Activity的onResume生命週期
    r = performResumeActivity(token, clearHide, reason);
    if (r != null) {
        final Activity a = r.activity;
        ...
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        ...
        //添加View到WM
        wm.addView(decor, l);
    }
    ...
}    

這裏將調用WindowManageraddView方法將DecorView添加,WindowMangeraddViewDecorView被渲染繪製到屏幕上顯示.

PhoneWindow只是負責處理一些應用窗口通用的邏輯(設置標題欄,導航欄等)。但是真正完成把一個 View 作爲窗口添加到 WMS的過程是由 WindowManager來完成的。

WindowManager 是接口類型,它是在Activityattach方法中調用PhoneWindowsetWindowManager方法設置的

mWindow.setWindowManager(
        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
        mToken, mComponent.flattenToString(),

我們來看下它的源碼

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
    ...
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);     
}            
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    return new WindowManagerImpl(mContext, parentWindow);
}

可以看到mWindowManagerWindowManagerImpl類的實例

那麼我們需要看一下WindowManagerImpladdView方法的源碼

WindowManagerImpl#addView

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

WindowManagerImpl什麼都沒有做,將其交給了mGlobal處理

mGlobalWindowManagerGlobal的實例

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
}        

我們繼續看WindowManagerGlobaladdView方法

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        ...
        //創建一個ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);
        ...
         try {
              root.setView(view, wparams, panelParentView);
         }
        ...
    }
        
}

可以看到,先是創建了一個ViewRootImpl,然後調用了其setView方法設置View

4.ViewRootImpl#setView

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    mView = view;
    ...
    //開啓繪製流程
    requestLayout();
    ...
    try{
        ...
        //將 View 添加到 WMS 中
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
    }
	...
}

先開啓View的繪製流程,確保View在被添加到Window上顯示到屏幕之前,已經完成測量和繪製操作,然後調用 mWindowSessionaddToDisplay方法將 View 添加到 WMS中。

mWindowSessionWindowManagerGlobal中的單例對象,實際上是 IWindowSession類型,真正的實現類是

System 進程中的 SessionSession會與WMS進行通信。將View相關的操作轉交給WMS。

我們重點看下requestLayout方法

5. ViewRootImpl#requestLayout

@Override
public void requestLayout() {
    ...
    scheduleTraversals();
}
void scheduleTraversals() {
    ...
    mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    ...    
}

mTraversalRunnable是一個TraversalRunnable類的實例,它繼承自Runnable

它的run方法

public void run() {
    doTraversal();
}
void doTraversal() {
    ...
    performTraversals();
    ...     
}

調用performTraversals

private void performTraversals() {
    ...
    //執行測量
	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //執行佈局
    performLayout(lp, mWidth, mHeight);
    ...
    //執行繪製
    performDraw();
    ...
}    

performTraversals方法裏開始執行測量、佈局、繪製流程

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    ...
    try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    ...
}

這裏調用mView.measure方法,而mView是之前的設置進來的DecorView,也就是說在這裏開啓了View樹的測量流程

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    ...
    final View host = mView;
    ...
    try {
        //開啓佈局
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    }
    ...
    
}            

這裏跟measure一樣調用DecorViewlayout方法,開啓了View樹的佈局流程。

private void performDraw() {
    ...
    try {
        draw(fullRedrawNeeded);
    }
    ...
}
private void draw(boolean fullRedrawNeeded) {
    ...
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty){
        return;
	}
    ...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) {
    ...
    //開始繪製
    mView.draw(canvas);
    ...
}            

performDraw經過一系列方法調用後最終調用DecorViewdraw方法開始繪製流程,而DecorView會對其子View進行非法draw事件

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章