美文网首页Android进阶之路Android开发Android开发经验谈
Android核心开发【UI绘制流程解析+原理】

Android核心开发【UI绘制流程解析+原理】

作者: 谁动了我的代码 | 来源:发表于2023-02-08 16:19 被阅读0次

    一、UI如何进行具体绘制

    UI从数据加载到具体展现的过程:

    image

    进程间的启动协作:

    image

    二、如何加载到数据

    应用从启动到onCreate的过程:

    image

    Activity生产过程详解:

    image image

    核心对象

    image

    绘制流程源码路径

    1、Activity加载ViewRootImpl

    ActivityThread.handleResumeActivity()
    --> WindowManagerImpl.addView(decorView, layoutParams)
    --> WindowManagerGlobal.addView()
    

    2、ViewRootImpl启动View树的遍历

    ViewRootImpl.setView(decorView, layoutParams, parentView)
    -->ViewRootImpl.requestLayout()
    -->scheduleTraversals()
    -->TraversalRunnable.run()
    -->doTraversal()
    -->performTraversals()(performMeasure、performLayout、performDraw)
    

    二、View绘制流程

    1、measure

    (1)MeasureSpec是什么?

    重写过onMeasure()方法都知道,测量需要用到MeasureSpec类获取View的测量模式和大小,那么这个类是怎样存储这两个信息呢?

    留心观察的话会发现,onMeasure方法的两个参数实际是32位int类型数据,即:

    00 000000 00000000 00000000 00000000

    而其结构为 mode + size ,前2位为mode,而后30位为size。

    ==> getMode()方法(measureSpec --> mode):

    private static final int MODE_SHIFT = 30;
    // 0x3转换为二进制即为:11
    // 左移30位后:11000000 00000000 00000000 00000000
    private static final int MODE_MASK = 0x3 << MODE_SHIFT;
     
    public static int getMode(int measureSpec) {
     // 与MODE_MASK按位与运算后,即将低30位清零,结果为mode左移30位后的值
     return (measureSpec & MODE_MASK);
    }
    

    getSize()方法同理。

    ==> makeMeasureSpec()方法(mode + size --> measureSpec):

    public static int makeMeasureSpec(
     @IntRange(from = 0,
      to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
     @MeasureSpecMode int mode) {
     if (sUseBrokenMakeMeasureSpec) {
      return size + mode;
     } else {
      return (size & ~MODE_MASK) | (mode & MODE_MASK);
     }
    }
    

    这里解释一下,按位或左侧为size的高2位清零后的结果,右侧为mode的低30位清零后的结果,两者按位或运算的结果正好为高2位mode、低30位size,例:

    01000000 00000000 00000000 00000000 |
    00001000 00001011 11110101 10101101 =
    01001000 00001011 11110101 10101101
    

    ==> 测量模式:

    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY  = 1 << MODE_SHIFT;
    public static final int AT_MOST  = 2 << MODE_SHIFT;
    

    UNSPECIFIED:父容器不对View作任何限制,系统内部使用。

    EXACTLY:精确模式,父容器检测出View大小,即为SpecSize;对应LayoutParams中的match_parent和指定大小的情况。

    AT_MOST:最大模式,父容器指定可用大小,View的大小不能超出这个值;对应wrap_content。

    (2)ViewGroup的测量流程

    回到ViewRootImpl的performMeasure方法,这里传入的参数为顶层DecorView的测量规格,其测量方式为:

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
     int measureSpec;
     switch (rootDimension) {
     
     case ViewGroup.LayoutParams.MATCH_PARENT:
      measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
      break;
     case ViewGroup.LayoutParams.WRAP_CONTENT:
      measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
      break;
     default:
      measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
      break;
     }
     return measureSpec;
    }
    

    match_parent和具体数值大小为EXACTLY模式,wrap_content则为AT_MOST模式。

    往下走,performMeasure方法中调用了DecorView的onMeasure方法,而DecorView继承自FrameLayout,可以看到FL的onMeasure方法中调用了measureChildWithMargins方法,并传入自身的测量规格:

    protected void measureChildWithMargins(View child,
      int parentWidthMeasureSpec, int widthUsed,
      int parentHeightMeasureSpec, int heightUsed) {
     final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
     
     final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
       mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
         + widthUsed, lp.width);
     final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
       mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
         + heightUsed, lp.height);
     
     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    即测量子控件的大小,测量规则详情可看getChildMeasureSpec方法,总结如下:

    childLayoutParams\parentSpecMode EXACTLY AT_MOST UNSPECIFIED
    dp EXACTLY/childSize EXACTLY/childSize EXCATLY/childSize
    match_parent EXACTLY/parentSize AT_MOST/parentSize UNSPECIFIED/0
    wrap_content AT_MOST/parentSize AT_MOST/parentSize UNSPECIFIED/0

    回到onMeasure方法,测完子控件之后,ViewGroup会经过一些计算,得出自身大小:

    // 加上padding
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
     
    // 检查是否小于最小宽度、最小高度
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
     
    // 检查Drawable的最小高度和宽度
    final Drawable drawable = getForeground();
    if (drawable != null) {
     maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
     maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }
     
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
      resolveSizeAndState(maxHeight, heightMeasureSpec,
        childState << MEASURED_HEIGHT_STATE_SHIFT));
    

    综上,ViewGroup的测量需要先测量子View的大小,而后结合padding等属性计算得出自身大小。

    (3)View的测量流程

    View.performMeasure()
    -->onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    -->setMeasuredDimension(int measuredWidth, int measuredHeight)
    -->setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)
    

    可以看到setMeasuredDimensionRaw()方法:

    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
     // 存储测量结果
     mMeasuredWidth = measuredWidth;
     mMeasuredHeight = measuredHeight;
     
     // 设置测量完成的标志位
     mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
    

    View不需要考虑子View的大小,根据内容测量得出自身大小即可。

    另外,View中的onMeasure方法中调用到getDefaultSize方法:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
       getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
     
    public static int getDefaultSize(int size, int measureSpec) {
     int result = size;
     int specMode = MeasureSpec.getMode(measureSpec);
     int specSize = MeasureSpec.getSize(measureSpec);
     
     switch (specMode) {
     case MeasureSpec.UNSPECIFIED:
      result = size;
      break;
     case MeasureSpec.AT_MOST:
     case MeasureSpec.EXACTLY:
      // 最终测量的结果都是父容器的大小
      result = specSize;
      break;
     }
     return result;
    }
    

    这里看到精确模式和最大模式,最终测量的结果都是父容器的大小,即布局中的wrap_content、match_parent以及数值大小效果都一样,这也就是自定义View一定要重写onMeasure方法的原因。

    2、layout

    布局相对测量而言要简单许多,从ViewRootImpl的performLayout方法出发,可以看到其中调用了DecorView的layout方法:

    // 实则为DecorView的left, top, right, bottom四个信息
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    

    进入layout方法,发现l、t、r、b被传递到了setFrame方法中,并设置给了成员变量:

    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
    

    所以,布局实际为调用View的layout方法,设置自身的l、t、r、b值。另外,layout方法中往下走,可以看到调用了onLayout方法,进入后发现为空方法。因而查看FrameLayout的onLayout方法:

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
     layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }
     
    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
     final int count = getChildCount();
     
     // 省略
     
     for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
       final LayoutParams lp = (LayoutParams) child.getLayoutParams();
     
       // 省略
     
       child.layout(childLeft, childTop, childLeft + width, childTop + height);
      }
     }
    }
    

    可以看到,进行一系列计算后,调用了child的layout方法,对子控件进行布局,同时子控件又会继续往下对自己的子控件布局,从而实现遍历。

    综上,布局实际为调用layout方法设置View位置,ViewGroup则需要另外实现onLayout方法摆放子控件。

    3、draw

    (1)绘制过程入口

    ViewRootImpl.performDraw()
    -->ViewRootImpl.draw()
    -->ViewRootImpl.drawSoftware()
    -->View.draw()
    

    (2)绘制步骤

    进入到View的draw方法中,可以看到以下一段注释:

    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *  1. Draw the background
     *  2. If necessary, save the canvas' layers to prepare for fading
     *  3. Draw view's content
     *  4. Draw children
     *  5. If necessary, draw the fading edges and restore layers
     *  6. Draw decorations (scrollbars for instance)
     */
    

    以上就是Android开发中的UI绘制原理及过程实现;更多技术探讨可进入查看《Android核心技术手册》进行学习。

    最后

    结合draw方法的源码,绘制过程的关键步骤如下:

    ==> 绘制背景:drawBackground(canvas)

    ==> 绘制自己:onDraw(canvas)

    ==> 绘制子view:dispatchDraw(canvas)

    ==> 绘制滚动条、前景等装饰:onDrawForeground(canvas)

    相关文章

      网友评论

        本文标题:Android核心开发【UI绘制流程解析+原理】

        本文链接:https://www.haomeiwen.com/subject/snvqkdtx.html