Android測量佈局繪製的起點

稀飯_發表於2018-07-18

一切從setContentView方法開始

原始碼把佈局檔案設定給window物件

public void setContentView(@LayoutRes int layoutResID) {
    
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}複製程式碼

那麼檢視window的setContentView方法

@Override
public void setContentView(int layoutResID) {
    //mContentParent為null,執行installDecor方法。 
    if (mContentParent == null) {
        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 {
        //把佈局新增到mContentParent中
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ...
}複製程式碼

mContentParent又是什麼?

跟蹤installDecor方法

private void installDecor() {
    //建立Decor物件
    if (mDecor == null) {
        mDecor = generateDecor(-1);
    }
    if (mContentParent == null) {
        //獲取mContentParent物件
        mContentParent = generateLayout(mDecor);

    }
    ...
}複製程式碼

接著跟蹤generateLayout方法

protected ViewGroup generateLayout(DecorView decor) {

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
   
    return contentParent;
}複製程式碼

至此,我們初始化了DecorView佈局,並且把我們佈局檔案載入到了DecorView中。

同時例項化了Window

我們回到開始,去查詢Window物件在哪裡建立的,通過原始碼可以發現在Activity#attach中對其進行了賦值操作(在Activity啟動過程中,會執行ActivityThread#performLaunchActivity方法,在這個方法中會呼叫Activity#attach方法)。

在Activity在啟動流程中當執行了ActivityThread#performLaunchActivity方法後還會執行ActivityThread#handleResumeActivity方法,在這個方法中首先會呼叫Activity的onResume方法,接著呼叫Activity的makeVisible方法。

void makeVisible() {
    if (!mWindowAdded) {
        //獲取WindowManager
        ViewManager wm = getWindowManager();
        //通過WindowManager完成window和DecorView的關聯
        wm.addView(mDecor, getWindow().getAttributes());

        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);//然後讓佈局顯示
}複製程式碼

那麼WindowManager是怎麼完成window和DecorView的關聯的呢?

繼續跟蹤原始碼,找windowManager類中的addView方法,windowManager是一個介面,我們找他的實現類WindowManagerImpl

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

發現WindowManagerImpl#addView並沒有實現具體細節,而是交WindowManagerGlobal中的addView去處理。

    public void addView(View view, ViewGroup.LayoutParams params,
                        Display display, Window parentWindow) {
        ViewRootImpl root;
        synchronized (mLock) {
//            例項化ViewRootImpl,它是WindowManager和DecorView的處理類
            root = new ViewRootImpl(view.getContext(), display);
            //這是佈局引數
            view.setLayoutParams(wparams);
            
            mViews.add(view);//儲存所有Window對應的View
            mRoots.add(root);//儲存所有Window對應的ViewRootImpl 
            mParams.add(wparams);//儲存所有Window對應的佈局引數 
        }
        try {
            //接著呼叫ViewRootImpl中的setView方法
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
         
        }
    }複製程式碼

注意:WindowManagerGlobal類裡邊有所有View的物件。

接著來看ViewRootImpl#setView方法

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            //內部最終會呼叫performTraversals方法開啟View的繪製流程。
            requestLayout();
            try {
                //通過IWindowSession來完成Window的新增過程,這是和系統的IPC過程,我分析不出來了
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } catch (RemoteException e) {

            } finally {

            }

        }

    }
}複製程式碼

看怎麼開啟繪製的還是可以分析出來的

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        //呼叫這個方法
        scheduleTraversals();
    }
}複製程式碼
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //傳送runnable
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}複製程式碼
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}複製程式碼
private void performTraversals() {
...
//執行測量
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//執行佈局
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//執行繪製
        performDraw();
    }複製程式碼

在performTraversals方法依次會執行performMeasure、performLayout以及performDraw來完成對頂級View的測量、佈局、繪製。

總結上邊原始碼我們可以看出setContentView方法做的事情有:
  • 把佈局掛載到DecorView中
  • 通過WindowManager新增到window到螢幕上(ipc過程不會分析)
  • 通過ViewRootImpl完成window和DecorView的關聯(Ipc過程不會分析)
  • ViewRootImpl中去觸發performTraversals去執行測量,佈局,和繪製的執行

那麼我們就可以說activity中的setContentView方法出發了ViewRootImpl類中的performTraversals去執行測量佈局和繪製。







相關文章