Android-UI的渲染过程

前两天写的博客Android--setContentView的绘制流程Android--LayoutInflater的渲染过程
主要讲了Activity窗口的绘制层级,以及XML文件是如何对其中的节点View进行添加的。但是其中有很多细节我们都没有将:比如DecorView是如何添加到PhoneWindow中的;LayoutInflater只是解析了XML,并且将View添加到了父View中,但是View是如何真正渲染的,我们也没有细说说,今天我们就详细数一下这些问题

一、DecorView添加到PhoneWindow

我们都知道,当Activity执行到onResume这个生命周期的方法的时候,界面才会展示出来。所以呢,DecorView是在onResume方法执行的时候添加到PhoneWindow中的。具体怎么执行的,我们先从ActivityThread的handleResumeActivity方法中入手,因为onResume方法的入口是在这个方法中:

    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        .....
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        .....
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            ....
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }
        } ....

        ....
    }

第4行:从performResumeActivity方法进入,这里就是onResume方法的真正入口。onResume方法不是重点,我就不进入查找了

第6~12行:在这里,我们可以找到很多眼熟的代码,比如window就是PhoneWindow;decor就是我们说的DecorView。wm是WindowManager,WindowManager我们是第一次见到,它其实是一个接口,具体实现类是WindowManagerImpl。我就不一行一行的带着大家去找了,我直接贴出最终的依据:Window.java中可以看见如下代码,而这里的this,其实就是PhoneWindow

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

第19行:将DecorView对象添加到了WindowManager中,这里就是我们想要的地方,我们先看看WindowManagerImpl中的addView方法

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    
@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

这里有几个关键对象:

mGlobal:很明显,和WindowManager有关系。而且,我们看见WindowMangaerGlobal是全局的单例对象,而且我们在其源码中可以发现一下代码

    @UnsupportedAppUsage
    private final ArrayList<View> mViews = new ArrayList<View>();
    @UnsupportedAppUsage
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    @UnsupportedAppUsage
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();

所以,我们推测WindowManagerGlobal的作用就是管理整个进程的窗口信息

mParentWindow:我们刚刚说了,是PhoneWindow

params:是PhoneWindow的LayoutParams

我们进入WindowManagerGlobal的addView方法中查看:

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        ....

        synchronized (mLock) {
            .....

            int index = findViewLocked(view, false);
            ....

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

代码太多,浓缩了一下

第8行:这里主要就是判断了一下view已经添加到了WindowManager中,防止重复添加

第11行:ViewRootImpl这个类不知道大家是否熟悉,在小节的后面,我会说它的作用

第20行:我们进入ViewRootImpl的setView方法中查看

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                ....
                requestLayout();
                .....

                view.assignParent(this);
                ......
            }
        }
    }

浓缩的都是精华!!

查看requestLayout方法

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

查看scheduleTraversals方法

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

第5行:postCallback的第二个参数mTraversalRunnable是一个Runnable接口,所以我们查看它的run方法

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

run方法中,只有一个方法调用,我们查看doTraversal

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

performTraversals方法我们下一节说,我们先总结一下上面说的内容

说到这里,我相信大家应该有很多问题,因为我看到这里的时候,流程虽然很流畅,但是又很多概念似乎很模糊,很懵懂。我就从我自己的角度上来提出问题,并且一一解答吧

1.PhoneWindow,ViewRootImpl,DecorView它们的作用和关系到底是什么?

PhoneWindow:从Android--setContentView的绘制流程上来看,PhoneWindow应该是Activity中展示View层级的一部分。其实并不是这样,因为我们在PhoneWindow找不到任何渲染的代码,仅仅是包含了DecorView,而DecorView才是真正的View对象,真正的View树根节点(FrameLayout)。其实PhoneWIndow更多的作用是连接Activity与View之间的交互,是一个桥梁的作用,比如说setContentView的具体执行,设置标题内容以及颜色,状态栏导航栏颜色获取等等。
ViewRootImpl:PhoneWindows虽然执行了setContentView但是并没有对DecorView进行层级的渲染,也就是去调用measure,layout,draw,那这些动作在哪里执行的呢?就是在ViewRootImpl中执行的,也就是我们将要说的performTraversals方法。当然,也不仅限于此,还包括事件的分发

DecorView:DecorView应该是最好理解的,View树的根节点,所有的绘制都是从这里开始的

2.DecorView到底是什么时候添加到PhoneWindow中的?

如果上面的概念理解了,这个问题我们就知道了。DecorView其实并没有真正的添加到PhoneWindow中,因为PhoneWindow本身也不是View,只是和PhoneWindow做了绑定,而且这个绑定过程在setContentView中就已经执行了。上面所说的流程,其实是创建了ViewRootImpl,然后执行了绘制过程,所以DecorView才会展示出来

二、View的绘制过程

其实到这里,绘制反而不是什么问题了,因为我们知道,绘制就是调用View的onMeasure,onLayout,onDraw方法了。而在上面我们所说的performTraversals中,存在performMeasure,performLayout,performDraw三个方法与之相对应。具体的调用过程我就不去捋了,我就说一下measure

measure

在measure中,需要注意的是MeasureSpec。MeasureSpec是一个32位的整形值,MeasureSpec的前两位表示测量模式,后30位表示测量的大小,从而保存了两个信息。

size表示了高度或宽度,单位是像素;而mode分为了三种:MeasureSpec.UNSPECIFIED,MeasureSpec.EXACTLY,MeasureSpec.AT_MOST。MeasureSpec.UNSPECIFIED基本不用,常用的只有EXACTLY和AT_MOST。

EXACTLY:精准模式,表示这个View的宽高是一个确定值

AT_MOST:最大模式,表示这个View的宽高能达到的最大值

那么,onMeasure中的两个参数是如何得到的呢?
事实上,一般来讲,当一个 ViewGroup 测量自身的时候,会先遍历它的子 View,并调用子 View 的measure方法进行测量,而measure方法又会调用onMeasure回调。
也就是说,View的onMeasure回调方法中的两个参数是父容器计算并传递过来的

下表反应了正常情况下父容器是如何得到一个子 View 的 MeasureSpec 的。其中横轴表示父容器的 MeasureSpec,纵轴表示了该 View 在布局文件中设置的宽或高,表中为该 View 的 MeasureSpec 的 mode 与 size。

子View在xml中 \ 父MeasureSpecEXACTLYAT_MOST
确定的数值mode: EXACTLY
size: 布局中设置的大小
mode: EXACTLY
size: 布局中设置的大小
wrap_contentmode: AT_MOST
size: 父容器specSize
mode: AT_MOST
size: 父容器specSize
match_parentmode: EXACTLY
size: 父容器specSize
mode: AT_MOST
size: 父容器specSize

也就是说:

  1. 当布局中宽或高填写的固定值,那么对应的 mode 为 EXACTLY ,size 为填写的值;
  2. 当布局中的宽或高填写的wrap_content,那么对应的 mode 为 AT_MOST,size 为父容器的 size;
  3. 当布局中的宽或高填写的match_parent,那么顾名思义,mode 和 size 都与父容器相同。

参考文章:【Android】MeasureSpec简述 - 简书

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值