从源头分析 Android 中 View 的绘制流程

View 的绘制流程 layout,measure,draw,追溯到源头,就是 Activity 创建时 View 的绘制。

从源码来看,大概会涉及到 Activity 的 attach, create, resume 这三个过程

Activity 的 attach 过程

启动 Activity 时,ActivityThread 的 performLaunchActivity 方法中先调用了 activity.attach 方法,attach 中创建了 mWindow 和 mWindowManager

mWindow = new PhoneWindow(this); // 创建了 window
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
    mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
    mWindow.setUiOptions(info.uiOptions);
}

mWindow.setWindowManager(
        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
        mToken, mComponent.flattenToString(),
        (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); // 这里创建了 windowManager 对象
if (mParent != null) {
    mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();

mWindow.setWindowManager 方法中创建了 mWindowManager

mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mDisplay, parentWindow);
    }

WindowManagerImpl 类中有个变量

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

到此就是 attch 方法创建的 window 相关的对象。

create 过程

onCreate 中 setContentView 调用了 PhoneWindow.setContentView, 这里创建了 DecoreView(继承自 FrameLayout) 对象,作为 PhoneWindow 的成员变量,并且给 DecoreView 添加了子 View, 根据不同的 window feature 添加不同的布局,但每个布局中都会有一个 id 为 content 的 View, 然后再把 activity 的 xml 文件添加到 content 中

整个 setContentView 添加的 View 就是这样子,到现在为止总结来说就是 Window 中有一个 DecoreView 对象,View 的层级图如下
view

resume 过程

onCreate 完成后,会接着调用 ActivityThread 的 handleResumeActivity 方法,主要看这几行代码

if (r.window == null && !a.mFinished && willBeVisible) {
    // r.window 在前面没有赋值,会走到这里的逻辑
    r.window = r.activity.getWindow();
    View decor = r.window.getDecorView();
    decor.setVisibility(View.INVISIBLE);
    ViewManager wm = a.getWindowManager(); // 这里的 wm 就是 attach 时的 mWindowManager, 是 WindowManagerImpl 对象
    WindowManager.LayoutParams l = r.window.getAttributes();
    a.mDecor = decor;
    l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
    l.softInputMode |= forwardBit;
    if (a.mVisibleFromClient) {
        a.mWindowAdded = true;
        wm.addView(decor, l); // ① 这里执行了 WindowManagerImpl.addView
    }
}

WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
        & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
        != forwardBit) {
    l.softInputMode = (l.softInputMode
            & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
            | forwardBit;
    if (r.activity.mVisibleFromClient) {
        ViewManager wm = a.getWindowManager(); // 这里的 wm 就是 attach 时的 mWindowManager, 是 WindowManagerImpl 对象
        View decor = r.window.getDecorView(); // decore 就是 create 时的 DecoreView
        wm.updateViewLayout(decor, l); // ② 这里这行了 WindowManagerImpl.updateViewLayout
    }
}

重点在上述代码中的注释 ①,②,WindowManagerImpl内部都是通过 mGlobal 来实现的,mGlobal 在 attach 过程中介绍过,是 WindowManagerGlobal 对象。
下面就先看 WindowManagerGlobal.addView 方法

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    root = new ViewRootImpl(view.getContext(), display); // 这里创建了 ViewRootImpl

    view.setLayoutParams(wparams);

    mViews.add(view);
    mRoots.add(root); // 把 ViewRootImpl 加进来
    mParams.add(wparams);
    ...
    root.setView(view, wparams, panelParentView);
}

接着看 WindowManagerGlobal.uodateViewLayout 方法

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    ...
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

    view.setLayoutParams(wparams);

    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index); // 这里得到的是 addView 时创建的 ViewRootImpl 对象
        mParams.remove(index);
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false); // ③ 这里又是重点
    }
}

到这里,注释 ① ② 都分析完了,上面代码中有个注释 ③,这是又一个重点,跟进代码里来看会发现到最后调用了一个方法 scheduleTraversals(),这个方法应该很熟悉了,很多文章都有写过绘制流程的入口就是这个方法。
接下来就是 scheduleTraversals 方法中会执行 mTraversalRunnable, TraversalRunnable 的 run 方法执行了 doTraversal, doTraversal 又调用了 performTraversals,然后这个方法就是一步一步绘制的流程了,会执行 layout,measure,draw 方法。


到这里启动一个 Activity 界面的绘制就很清晰了。
到这我们可以思考一个常见的问题,就是非 UI 线程更改 View 报的错 “Only the original thread that created a view hierarchy can touch its views”,在 onCreate 中创建子线程 setText 不报错的原因。
这句描述是在 ViewRootImpl.checkThread 报的,但 ViewRootImpl 对象是在 resume 时候创建的,所以 onCreate 中创建子线程修改 UI 时可能 ViewRootImpl 对象还没创建,所以就没有报错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值