Android测量布局绘制的起点

879 阅读3分钟

一切从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去执行测量布局和绘制。