Android View 的工作原理

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

内容简介:文章主要参考书籍《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 的工作原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

轻量级Django

轻量级Django

茱莉亚·埃尔曼 (Julia Elman)、马克·拉温 (Mark Lavin) / 侯荣涛、吴磊 / 中国电力出版社; 第1版 / 2016-11-1 / 35.6

自Django 创建以来,各种各样的开源社区已经构建了很多Web 框架,比如JavaScript 社区创建的Angular.js 、Ember.js 和Backbone.js 之类面向前端的Web 框架,它们是现代Web 开发中的先驱。Django 从哪里入手来适应这些框架呢?我们如何将客户端MVC 框架整合成为当前的Django 基础架构? 本书讲述如何利用Django 强大的“自支持”功......一起来看看 《轻量级Django》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具