Android View 的工作原理

栏目: IOS · Android · 发布时间: 6年前

内容简介:文章主要参考书籍《Android 开发艺术探索》(任玉刚 著),与书籍主要区别:View 的三大流程:测量流程(mesure)、布局流程(layout)、绘制流程(draw)DecorView 作为顶级 View,它继承自 FrameLayout,View 层的事件都记过 DecorView,然后才传递给 View

文章主要参考书籍《Android 开发艺术探索》(任玉刚 著),与书籍主要区别:

  • 源代码基于 Android 19,Android 版本变化导致的变化会标注说明
  • 追加大量流程图,源代码追加更详细注释,以方便理解与记忆
  • 扩展了一些书籍上未说明的源代码、流程说明

前言

View 的三大流程:测量流程(mesure)、布局流程(layout)、绘制流程(draw)

目录

  • 一、初识 ViewRootImpl 和 PhoneWindow.DecorView
  • 二、理解 View.MeasureSpec
  • 小总结
  • MeasureSpec 格式
  • MeasureSpec 和 ViewGroup.LayoutParams 关系
  • MeasureSpec 计算的流程与结果
  • DecorView 的 MeasureSpec 计算源码分析
  • 普通 View 的 MeasureSpec 计算源码分析
  • 三、View 的 measure 过程
  • 小总结
  • 相关 API
  • 从 DecorView 到普通 View 的 measure 原理分析
  • ViewGroup 的 measure 原理分析
  • 普通 View 的 measure 原理分析
  • 在 Activity 生命周期中获取测量结果解决方案
  • 四、View 的 layout 过程
  • 小总结
  • 相关 API
  • 从 DecorView 到普通 View 的 layout 原理分析
  • 普通 View 的 layout 原理分析
  • 五、View 的 draw 过程
  • 小总结
  • 相关 API
  • 从 DecorView 到普通 View 的 draw 原理分析
  • 普通 View 的 draw 原理分析
  • ViewGroup 的 draw 原理分析

一、初识 ViewRootImpl 和 PhoneWindow.DecorView

DecorView 作为顶级 View,它继承自 FrameLayout,View 层的事件都记过 DecorView,然后才传递给 View

ViewRootImpl:View 阶级的最高级,View 的三大流程均由它实现,是连接 WindowManager 和 DecorView 的纽带,主要实现 View 与 WindowManager 之间的协议,可参考 WindowManagerGlobal。ViewRootImpl官方释义如下:

/**
 * The top of a view hierarchy, implementing the needed protocol between View
 * and the WindowManager.  This is for the most part an internal implementation
 * detail of {@link WindowManagerGlobal}.
 */
public final class ViewRootImpl implements ...
复制代码

ActivityThread 中,当 Activity 创建完毕后,会将 DecorView 添加到 Window 中,同时创建 ViewRootImpl 对象,并将 ViewRootImpl 与 DecorView 建立关联

/**
 * WindowManagerGlobal 类
 */
public void addView(View, ViewGroup.LayoutParams, Display, Window) {
    ...
    root = new ViewRootImpl(view.getContext(), display);
    ...
    root.setView(view, wparams, panelParentView);
    ...
}
复制代码

View 绘制从 ViewRootImpl.performTraversals() 开始,依次完成测量流程(mesure)、布局流程(layout)、绘制流程(draw)。流程图如下:

Android View 的工作原理
/**
 * ViewRootImpl 类
 */
private void performTraversals() {
    /**
     * 参数说明
     * mWidth, mHeight: 窗口有效尺寸
     * lp: 窗口布局参数
     * desiredWindowWidth: 窗口尺寸
     * desiredWindowHeight: 窗口尺寸
     */

     ...
    // 获取根 View 的 MeasureSpec,并执行测量流程
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    ...
    // 布局流程
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);

    ...
    // 绘制流程
    performDraw();
    ...
}
复制代码
  • 测量过程[performMeasure()]完成后,View 高宽就测量完毕,在几乎所有情况下,它都等于 View 的最终高宽
  • 布局过程[performLayout()]决定了 View 的四个顶点的坐标,以及 View 的实际高宽
  • 绘制过程[performDraw()]决定了 View 的内容显示

二、理解 View.MeasureSpec

小总结

  • MeasureSpec 是 Android 系统用来测量 View 的高宽的参数
  • 对于 DecorView,其 MeasureSpec 由窗口的尺寸和其自身的 LayoutParams 共同确定
  • 对于普通 View,其 MeasureSpec 有父容器的 MeasureSpec 和自身 LayoutParams 共同确定

1. 相关 API

获取计算结果:

  • int getMode(int)
  • int getSize(int)

2. MeasureSpec 格式

MeasureSpec 是32位 int 值,高2位表示 SpecMode,低30位表示 SpecSize

  • SpecMode:指测量模式,有三类:UNSPECIFIED、EXACTLY、AT_MOST
  • SpecSize:值在某个测量模式下的规格大小
  • UNSPECIFIED:未注明的,意思是父容器对 View 不做任何限制,要多大给多大,一般用于系统内部
  • EXACTLY:精确的,意思是明确 View 大小值, 对应 LayoutParams.MATCH_PARENT 和具体数值 这两种模式
  • AT_MOST:至多的,意思是 View 的大小值根据具体实现,但不能超过该值, 对应 LayoutParams.WRAP_CONTENT
/**
 * MeasureSpec 类
 */
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
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;

public static int makeMeasureSpec(int size, int mode) {
    if (sUseBrokenMakeMeasureSpec) {
        return size + mode;
    } else {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}

public static int getMode(int measureSpec) {
    return (measureSpec & MODE_MASK);
}

public static int getSize(int measureSpec) {
   return (measureSpec & ~MODE_MASK);
}
复制代码

3. MeasureSpec 和 ViewGroup.LayoutParams 关系

在 View 测量的时候,系统会将 View 的 LayoutParams 根据***父容器所施加的约束***转换成对应的 MeasureSpec,然后***根据这个 MeasureSpec 来测量 View 的高宽***

4. MeasureSpec 计算的流程与结果

DecorView 与普通 View 的 MeasureSpec 计算流程,分别如下:

Android View 的工作原理

DecorView 与普通 View 的 MeasureSpec 计算结果表,分别如下:

Android View 的工作原理

5. DecorView 的 MeasureSpec 计算源码分析

/**
 * ViewRootImpl 类
 */
/**
 * @param lp WindowManager.LayoutParams, 窗口布局参数
 * @param desiredWindowWidth int, 窗口尺寸
 * @param desiredWindowHeight int, 窗口尺寸
 */
private boolean measureHierarchy(...) {
    ...
    childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
}

/**
 * @param windowSize 窗口有效尺寸
 * @param rootDimension 窗口的布局参数尺寸
 * @return 根 View(DecorView) 的 MeasureSpec
 */
 /**
 * 官方释义
 * @param windowSize The available width or height of the window
 * @param rootDimension The layout params for one dimension (width or height) of the window
 * @return The measure spec to use to measure the root view
 */
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;
}
复制代码

6. 普通 View 的 MeasureSpec 计算源码分析

/**
 * ViewGroup 类
 */
/**
 * @param child 子 View
 * @param parentWidthMeasureSpec 父容器宽度
 * @param widthUsed 额外使用尺寸
 * @param parentHeightMeasureSpec 父容器高度
 * @param heightUsed 额外使用尺寸
 */
protected void measureChildWithMargins(View, int, int, int, int) {
    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);
}

/**
 * @param spec 父容器尺寸
 * @param padding 补白区尺寸
 * @param childDimension View 尺寸
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
复制代码

三、View 的 measure 过程

小总结

  • View.measure() 是 final 修饰的,所以无论是 View,ViewGroup 都不无法重写该方法,但 measure() 会调用 onMeasure(),所以可通过重写 onMeasure(),完成自定义控件的测量
  • 直接继承 View 的自定义控件需要重写 onMeasure() 并设置 WRAP_CONTENT 时的高宽,否则该设置相当与 MATCH_PARENT
  • ViewGroup 自身是没有重写 onMeasure() 的,原因是不同的 ViewGroup 子类是有不同的布局特性。所以直接继承 ViewGroup 的自定义控件需要重写 onMeasure()

1. 相关 API

获取测量结果:

  • int getMeasuredWidth()
  • int getMeasuredHeight()

测量相关方法:

  • void setMeasuredDimension(int, int)

  • int getPaddingLeft()

  • int getPaddingTop()

  • int getPaddingRight()

  • int getPaddingBottom()

  • int getPaddingStart()

  • int getPaddingEnd()

  • setPadding(int left, int top, int right, int bottom)

  • ViewParent getParent()

2. 从 DecorView 到普通 View 的 measure 原理分析

Android View 的工作原理
/**
 * ViewRootImpl 类
 */
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

/**
 * View 类
 */
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ...
}

/**
 * ViewGroup 类,见标题【MeasureSpec 和 LayoutParams 关系】
 */
protected void measureChildWithMargins(View, int, int, int, int) {
    ...
}
复制代码
  • measure() 是 final 修饰的,所以无论是 View,ViewGroup 都不无法重写该方法

3. ViewGroup 的 measure 原理分析

Android View 的工作原理
  • ViewGroup 自身是没有重写 onMeasure() 的,原因是不同的 ViewGroup 子类是有不同的布局特性
/**
 * ViewGroup 类
 */
/**
 * @param widthMeasureSpec ViewGroup 宽度
 * @param heightMeasureSpec ViewGroup 高度
 */
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

/**
 * @param child 子 View 的 MeasureSpec
 * @param parentWidthMeasureSpec ViewGroup 宽度
 * @param parentHeightMeasureSpec ViewGroup 高度
 */
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(
            parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight,
            lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(
            parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom,
            lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

/**
 * 见标题【2.4. MeasureSpec 计算】
 */
protected void measureChildWithMargins(View, int, int, int, int) {
    ...
}
复制代码

4. 普通 View 的 measure 原理分析

Android View 的工作原理
/**
 * View 类
 */
/**
 * @param widthMeasureSpec 宽度 MeasureSpec
 * @param heightMeasureSpec 高度 MeasureSpec
 */
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(
            getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

/**
 * @param size 默认大小
 * @param measureSpec 父容器约束
 * @return View 测量后的 MeasureSpec
 */
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;
}

/**
 * mBackground: Drawable 对象
 * mMinWidth: 对应 android.minWidth 属性
 * getMinimumWidth(): Drawable 对象的原始宽度
 */
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

/**
 * Drawable 类
 */
public int getMinimumWidth() {
    final int intrinsicWidth = getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

public int getMinimumHeight() {
    final int intrinsicHeight = getIntrinsicHeight();
    return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
复制代码

5. 在 Activity 生命周期中获取测量结果解决方案

由于 View 的 measure 过程与 Activity 生命周期不同步,所以在 Activity 生命周期中直接获取测量结果会有问题,解决方案有以下几种: 方法一:

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        int width = view.getMeasuredWidth();
        int height = view.getMeasuredHeight();
    }
}
复制代码

方法二:通过 post() 将消息投递到消息队列尾部,当 Looper 调用该消息时,View 已经初始化好了

@Override
protected void onStart() {
    super.onStart();

    view.post(new Runnable() {
        @Override
        public void run() {
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
        }
    });
}
复制代码

方法三:

@Override
protected void onStart() {
    super.onStart();

    ViewTreeObserver observer = view.getViewTreeObserver();
    observer.addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {
        @Override
        public void onGlobalFocusChanged(View oldFocus, View newFocus) {
            view.getViewTreeObserver().removeOnGlobalFocusChangeListener(this);
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
        }
    });
}
复制代码

方法四:不推荐,原因是 View 为 MATCH_PARENT 时,不可用

@Override
protected void onStart() {
    super.onStart();

    // View 为 WRAP_CONTENT 时
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST); // 取最大值
    int heghtMeasureSpec = widthMeasureSpec;
    view.measure(widthMeasureSpec, getMinimumHeight());
    int width = view.getMeasuredWidth();
    int height = view.getMeasuredHeight();

    // View 为 dp / px 时,假设高宽依次是100px,200px
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
    int heghtMeasureSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY);
    view.measure(widthMeasureSpec, getMinimumHeight());
    int width = view.getMeasuredWidth();
    int height = view.getMeasuredHeight();
}
复制代码

四、View 的 layout 过程

小总结

  • 与 measure 过程原因类似,直接继承 ViewGroup 的自定义控件需要重写 onLayout()

1. 相关 API

获取布局结果:

  • getTop()
  • getBottom()
  • getLeft()
  • getRight()
  • getWidth()
  • getHeight()

2. 从 DecorView 到普通 View 的 layout 原理分析

Android View 的工作原理
/**
 * ViewRootImpl 类
 */
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
    mLayoutRequested = false;
    mScrollMayChange = true;
    mInLayout = true;

    final View host = mView;
    ...

    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

        mInLayout = false;
        int numViewsRequestingLayout = mLayoutRequesters.size();
        if (numViewsRequestingLayout > 0) {
            // requestLayout() was called during layout.
            // If no layout-request flags are set on the requesting views, there is no problem.
            // If some requests are still pending, then we need to clear those flags and do
            // a full request/measure/layout pass to handle this situation.
            ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false);
            if (validLayoutRequesters != null) {
                ...
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
                ...
            }
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}
/**
 * View 类
 */
/**
 * 见下述【普通View的layout原理】
 */
public void layout(int l, int t, int r, int b) {
    ...
}
复制代码

3. 普通 View 的 layout 原理分析

Android View 的工作原理
  • 无论是 View 还是 ViewGroup 的 onLayout() 都是未定义,所以直接继承 ViewGroup 的自定义控件需要重写 onLayout()
/**
 * View 类
 */
/**
 * @param Left/Top:与父容器的相对偏移量
 * @param Right/Bottom: measure() 后与父容器的相对偏移量
 */
public void layout(int l, int t, int r, int b) {
    ...

    // 确定当前 View 的大小与相对父容器的位置
    boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    // 调用 onLayout(...)
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        ...
    }

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

/**
 * true: 当前 View(注意此处为父容器)是 ViewGroup,且模式为 LAYOUT_MODE_OPTICAL_BOUNDS
 * ViewGroup 模式有两种:
 * LAYOUT_MODE_CLIP_BOUNDS:默认模式,表示边界未加工的
 * LAYOUT_MODE_OPTICAL_BOUNDS:大体含义是支持特效,如阴影、暖色、冷色
 */
public static boolean isLayoutModeOptical(Object o) {
    return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}

/**
 * 设置 ViewGroup 为 LAYOUT_MODE_OPTICAL_BOUNDS 时,当前 ViewGroup 的位置
 */
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
    ...
    return setFrame(...);
}

/**
 * Assign a size and position to this view.
 * 指定当前 View 的大小与相对父容器的位置
 */
protected boolean setFrame(int left, int top, int right, int bottom) {
    ...
}

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

/**
 * ViewGroup 类
 */
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
复制代码

五、View 的 draw 过程

小总结

  • 有显示内容的自定义控件需要重写 onDraw(Canvas),以便绘制自身的显示内容

1. 相关 API

2. 从 DecorView 到普通 View 的 draw 原理分析

Android View 的工作原理
  • 由于源代码过于复杂,并且设计到硬件渲染,故上述流程仅供参考,下述贴出部分源代码,详细过程见 Android 源代码
/**
 * ViewRootImpl 类
 */
private void performDraw() {
    if (!mAttachInfo.mScreenOn && !mReportNextDraw) {
        return;
    }

    final boolean fullRedrawNeeded = mFullRedrawNeeded;
    mFullRedrawNeeded = false;

    mIsDrawing = true;
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
    try {
        draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    ...
}

private void draw(boolean fullRedrawNeeded) {
    ...

    if (!sFirstDrawComplete) {
        synchronized (sFirstDrawHandlers) {
            sFirstDrawComplete = true;
            final int count = sFirstDrawHandlers.size();
            for (int i = 0; i< count; i++) {
                mHandler.post(sFirstDrawHandlers.get(i));
            }
        }
    }

    scrollToRectOrFocus(null, false);

    ...

    final Rect dirty = mDirty;
    ...

    if (!dirty.isEmpty() || mIsAnimating) {
        if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
            // Draw with hardware renderer.
            ...

            attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, animating ? null : mCurrentDirty);
        } else {
            // If we get here with a disabled & requested hardware renderer, something went
            // wrong (an invalidate posted right before we destroyed the hardware surface
            // for instance) so we should just bail out. Locking the surface with software
            // rendering at this point would lock it forever and prevent hardware renderer
            // from doing its job when it comes back.
            // Before we request a new frame we must however attempt to reinitiliaze the
            // hardware renderer if it's in requested state. This would happen after an
            // eglTerminate() for instance.

            ...

            if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
                return;
            }
        }
    }

    if (animating) {
        mFullRedrawNeeded = true;
        scheduleTraversals();
    }
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff, boolean scalingRequired, Rect dirty) {
    // Draw with software renderer.
    Canvas canvas;
    ...

    try {
        ...

        try {
            ...

            mView.draw(canvas);

            drawAccessibilityFocusedDrawableIfNeeded(canvas);
        } finally {
            if (!attachInfo.mSetIgnoreDirtyState) {
                // Only clear the flag if it was not set during the mView.draw() call
                attachInfo.mIgnoreDirtyState = false;
            }
        }
    } finally {
        ...
    }
    return true;
}
/**
 * View 类
 */
/**
 * 见下述【普通View的draw原理】
 */
public void draw(Canvas canvas) {
    ...
}
复制代码

3. 普通 View 的 draw 原理分析

Android View 的工作原理
  • 绘制背景:Android 19 采用上述源码中的代码块实现。Android 21 则通过 drawBackground(Canvas) 实现
/**
 * 绘制步骤:
 * 1. 绘制背景
 * 2. 如果需要,在做淡入淡出处理前保存画布图层???(???:不确定,下同)
 * 3. 绘制 View 内容
 * 4. 绘制子 View 的内容
 * 5. 如果需要,绘制淡入淡出边界和恢复图层???
 * 6. 绘制附加内容,如滚轮等
 *
 * 官方释义:
 * 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)
 */
public void draw(Canvas canvas) {
    // 调整当前画布的边界???
    if (mClipBounds != null) {
        canvas.clipRect(mClipBounds);
    }
    // dirtyOpaque:true,透明
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    // Step 1, draw the background, if needed
    int saveCount;
    if (!dirtyOpaque) {
        final Drawable background = mBackground;
        if (background != null) {
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;

            if (mBackgroundSizeChanged) {
                background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
                mBackgroundSizeChanged = false;
            }

            if ((scrollX | scrollY) == 0) {
                background.draw(canvas);
            } else {
                canvas.translate(scrollX, scrollY);
                background.draw(canvas);
                canvas.translate(-scrollX, -scrollY);
            }
        }
    }

    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);

        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // we're done...
        return;
    }

    // 透明布局处理,貌似做了一些优化???
    ...
}
复制代码

4. ViewGroup 的 draw 原理分析

Android View 的工作原理
  • 由于该部分涉及的源代码复杂程度高,故上述流程仅供参考,下述贴出部分源代码,详细过程见 Android 源代码
  • 由于该部分涉及的源代码复杂程度高,所以基本只体现关键方法,其他则简要说明,请对照 Android 源码查看
/**
 * ViewGroup
 */
@Override
protected void dispatchDraw(Canvas canvas) {
    final int count = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;

    // 动画执行前准备:创建所有子 View 的绘制内容缓存、触发动画开始事件
    if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
        ...

        for (int i = 0; i < count; i++) {
            final View child = children[i];
            ...
            child.buildDrawingCache(true);
        }

        ...

        if (mAnimationListener != null) {
            mAnimationListener.onAnimationStart(controller.getAnimation());
        }
    }

    // 调整 padding:现场保护
    int saveCount = 0;
    final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
    if (clipToPadding) {
        saveCount = canvas.save();
        canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                mScrollX + mRight - mLeft - mPaddingRight,
                mScrollY + mBottom - mTop - mPaddingBottom);

    }

    // 绘制子 View 内容,并执行动画效果
    // We will draw our child's animation, let's reset the flag
    mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
    mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;

    boolean more = false;
    final long drawingTime = getDrawingTime();

    /* 是否按指定顺序绘制子 View 的内容 */
    if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
    } else {
        for (int i = 0; i < count; i++) {
            final View child = children[getChildDrawingOrder(count, i)];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
    }

    // 绘制隐藏子 View 的内容
    // Draw any disappearing views that have animations
    if (mDisappearingChildren != null) {
        final ArrayList<View> disappearingChildren = mDisappearingChildren;
        final int disappearingCount = disappearingChildren.size() - 1;
        // Go backwards -- we may delete as animations finish
        for (int i = disappearingCount; i >= 0; i--) {
            final View child = disappearingChildren.get(i);
            more |= drawChild(canvas, child, drawingTime);
        }
    }

    ...

    // 调整 padding:现场还原
    if (clipToPadding) {
        canvas.restoreToCount(saveCount);
    }

    // 状态标识等更新,并触发动画结束事件
    // mGroupFlags might have been updated by drawChild()
    flags = mGroupFlags;

    if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
        invalidate(true);
    }

    if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
            mLayoutAnimationController.isDone() && !more) {
        // We want to erase the drawing cache and notify the listener after the
        // next frame is drawn because one extra invalidate() is caused by
        // drawChild() after the animation is over
        mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
        final Runnable end = new Runnable() {
            public void run() {
                notifyAnimationListener();
            }
        };
        post(end);
    }
}

/**
 * 绘制 View 内容,并执行动画效果
 */
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

/**
 * View 类
 */
/**
 * 该方法只能通过 ViewGroup.drawChild() 调用
 */
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ...

    // 动画执行
    final Animation a = getAnimation();
    if (a != null) {
        more = drawAnimation(parent, drawingTime, a, scalingRequired);
        ...
    } else {
        ...
    }
    ...

    // 显示内容准备
    DisplayList displayList = null;
    Bitmap cache = null;
    boolean hasDisplayList = false;
    if (caching) {
        ...
        hasDisplayList = canHaveDisplayList();
        ...
        cache = getDrawingCache(true);
        ...
    }
    ...
    displayList = getDisplayList();
    ...

    final boolean hasNoCache = cache == null || hasDisplayList;
    final boolean offsetForScroll = cache == null && !hasDisplayList &&
            layerType != LAYER_TYPE_HARDWARE;

    // 画布调整
    ...
    restoreTo = canvas.save();
    ...
    canvas.translate(mLeft - sx, mTop - sy);
    ...
    canvas.clipRect(0, 0, mRight - mLeft, mBottom - mTop);
    ...

    // 绘制 View 内容
    if (hasNoCache) {
        boolean layerRendered = false;
        ...

        if (!layerRendered) {
            if (!hasDisplayList) {
                // Fast path for layouts with no backgrounds
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else {
                    draw(canvas);
                }
            } else {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((HardwareCanvas) canvas).drawDisplayList(displayList, null, flags);
            }
        }
    } else if (cache != null) {
        ...
        canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
    }

    // 画布调整:现场还原
    if (restoreTo >= 0) {
        canvas.restoreToCount(restoreTo);
    }

    // 状态标识等更新,结束动画效果
    if (a != null && !more) {
        if (!hardwareAccelerated && !a.getFillAfter()) {
            onSetAlpha(255);
        }
        parent.finishAnimatingView(this, a);
    }

    if (more && hardwareAccelerated) {
        if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
            // alpha animations should cause the child to recreate its display list
            invalidate(true);
        }
    }

    mRecreateDisplayList = false;

    return more;
}
复制代码

参考

  • 《Android 开发艺术探索》(任玉刚 著) 第四章 View 的工作原理
  • Android 19 源代码(主要)
  • Android 21 源代码

声明

限于作者水平有限,出错难免,请积极拍砖! 欢迎任何形式的转载,转载请保留本文原文链接: juejin.im/post/5c07a6…

结言

花了 N 久的时间把这篇博客给写完了。以后复习或继续深入理解 View 工作原理也轻松了许多。

该篇博客主要以流程以及源代码注释来说明 View 工作原理。论文字描述的详细程度要比任大大的《Android 开发艺术探索》要简略许多,比较不容易阅读。所以还是推荐大家阅读任大大的《Android 开发艺术探索》。或者查看[URL](本来想在这里贴任大大的原创博客地址,但找不着了,笑哭。)

另外,CSDN 居然无故把我的账户给封了,无语啊。没奈何,只好把博客搬到稀土掘金上了


以上所述就是小编给大家介绍的《Android View 的工作原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

现代操作系统(原书第4版)

现代操作系统(原书第4版)

[荷] Andrew S. Tanenbaum、[荷] Herbert Bos / 陈向群、马洪兵 等 / 机械工业出版社 / 2017-7 / 89.00

Andrew S. Tanenbaum教授编写的教材《现代操作系统》现在已经是第4版了。第4版在保持原有特色的基础上,又增添了许多新的内容,反映了当代操作系统的发展与动向,并不断地与时俱进。 对比第3版,第4版有很多变化。一些是教材中多处可见的细微变化,一些是就某一功能或机制增加了对最新技术的介绍,如增加了futex同步原语、读–复制–更新(Read-Copy-Update)机制以及6级RA......一起来看看 《现代操作系统(原书第4版)》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具