当前位置:   article > 正文

Android应用篇 - 从源码角度来理解 View 的绘制流程_performtraversals onresume

performtraversals onresume

上一篇文章从几个场景分析了下 View 的绘制流程,这篇文章我们来 read the fuck code!

本文源代码基于 Android 7.0。 

 

目录:

  1. handleResumeActivity()
  2. performTraversals()
  3. performMeasure()
  4. performLayout()
  5. performDraw()

 

 

1. handleResumeActivity()

/base/core/java/android/app/ActivityThread.java

onResume() 之后才是 Activity 真正可见和可交互的状态,那就从 ActivityThread.handleResumeActivity() 作为入口。

  1. final void handleResumeActivity(IBinder token,
  2. boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
  3. // ...
  4. if (r.activity.mVisibleFromClient) {
  5. // 添加视图
  6. r.activity.makeVisible();
  7. }
  8. // ...
  9. }

狂删一通代码, 因为这不是讲 AMS 或者 WMS,我们这边只看调用链。makeVisible() 这便来到了 Activity 世界:

/base/core/java/android/app/Activity.java

  1. void makeVisible() {
  2. // 如果Window没有被添加,则addView添加decorView
  3. if (!mWindowAdded) {
  4. ViewManager wm = getWindowManager();
  5. // 进入WindowManagerGlobal.addView,这边是添加DecorView,根view
  6. wm.addView(mDecor, getWindow().getAttributes());
  7. mWindowAdded = true;
  8. }
  9. // 设置decorView可见
  10. mDecor.setVisibility(View.VISIBLE);
  11. }

wm.addView() 最终会调用到 WindowManagerGlobal.addView(),这个前面的 Framework 相关的文章有写过。

/base/core/java/android/view/WindowManagerGlobal.java

  1. // 添加View
  2. public void addView(View view, ViewGroup.LayoutParams params,
  3. Display display, Window parentWindow) {
  4. ViewRootImpl root;
  5. View panelParentView = null;
  6. synchronized (mLock) {
  7. // 每次调用global.addView()都会创建一个ViewRootImpl,它是decorView与WMS沟通的桥梁
  8. root = new ViewRootImpl(view.getContext(), display);
  9. // 设置LayoutParams
  10. view.setLayoutParams(wparams);
  11. // 加到mViews, mRoots, mParams集合中
  12. mViews.add(view);
  13. mRoots.add(root);
  14. mParams.add(wparams);
  15. }
  16. // do this last because it fires off messages to start doing things
  17. try {
  18. // ViewRootImpl设置View
  19. root.setView(view, wparams, panelParentView);
  20. } catch (RuntimeException e) {
  21. // ...
  22. }
  23. }

ViewRootImpl 实例化出来了,实例化出来然后被加到 mRoots (一个存放 ViewRootImpl 实例的 Arraylist) 中,同时 decorView 被添加进 mViews 中,最后我们看到调用了 root.setView() 方法。

/base/core/java/android/view/ViewRootImpl.java

  1. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  2. //...
  3. requestLayout();
  4. //...
  5. }

好熟悉的方法名,看看它里面做了什么:

  1. @Override
  2. public void requestLayout() {
  3. if (!mHandlingLayoutInLayoutRequest) {
  4. checkThread();
  5. mLayoutRequested = true;
  6. scheduleTraversals();
  7. }
  8. }
  9. void checkThread() {
  10. if (mThread != Thread.currentThread()) {
  11. throw new CalledFromWrongThreadException(
  12. "Only the original thread that created a view hierarchy can touch its views.");
  13. }
  14. }
  15. void scheduleTraversals() {
  16. if (!mTraversalScheduled) {
  17. mTraversalScheduled = true;
  18. mChoreographer.postCallback(
  19. Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  20. // ...
  21. }
  22. }
  23. final class TraversalRunnable implements Runnable {
  24. @Override
  25. public void run() {
  26. doTraversal();
  27. }
  28. }
  29. final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  30. void doTraversal() {
  31. if (mTraversalScheduled) {
  32. mTraversalScheduled = false;
  33. // ...
  34. performTraversals();
  35. // ...
  36. }
  37. }
  • checkThread():只能在主线程更新 UI。
  • performTraversals()。

接下来的篇幅将从 performTraversals() 这个方法作为入口来写。

 

 

2. performTraversals()

/base/core/java/android/view/ViewRootImpl.java

  1. private void performTraversals() {
  2. // ...
  3. int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
  4. int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
  5. // 测量
  6. performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
  7. // 布局
  8. performLayout(lp, mWidth, mHeight);
  9. // 绘制
  10. performDraw();
  11. // ...
  12. }

接着分三个小节来展开说这三个方法。

 

 

3. performMeasure()

/base/core/java/android/view/ViewRootImpl.java

  1. private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
  2. try {
  3. mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  4. } finally {
  5. }
  6. }

盗用一张图 - View 树的 measure 流程,画图太费时费力了: 


这里调用了 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec) 这个方法,那么这里的 mView 是什么东西呢,当然是从根视图开始 measure 了,所以就是 decorView 了。decorView 是个 FrameLayout所以我们就来看 FrameLayout 的measure() 方法。FrameLayout 里面没有 measure(),只有 onMeasure(),measure() 方法在其父类 View 里,measure() 方法中会调用 onMeasure():

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. int count = getChildCount();
  4. final boolean measureMatchParentChildren =
  5. MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
  6. MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
  7. mMatchParentChildren.clear();
  8. int maxHeight = 0;
  9. int maxWidth = 0;
  10. int childState = 0;
  11. for (int i = 0; i < count; i++) {
  12. final View child = getChildAt(i);
  13. if (mMeasureAllChildren || child.getVisibility() != GONE) {
  14. measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
  15. // ...
  16. }
  17. }
  18. maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
  19. maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
  20. maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
  21. maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
  22. final Drawable drawable = getForeground();
  23. if (drawable != null) {
  24. maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
  25. maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
  26. }
  27. // setMeasuredDimension()
  28. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
  29. resolveSizeAndState(maxHeight, heightMeasureSpec,
  30. childState << MEASURED_HEIGHT_STATE_SHIFT));
  31. count = mMatchParentChildren.size();
  32. if (count > 1) {
  33. for (int i = 0; i < count; i++) {
  34. final View child = mMatchParentChildren.get(i);
  35. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  36. final int childWidthMeasureSpec;
  37. if (lp.width == LayoutParams.MATCH_PARENT) {
  38. final int width = Math.max(0, getMeasuredWidth()
  39. - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
  40. - lp.leftMargin - lp.rightMargin);
  41. childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
  42. width, MeasureSpec.EXACTLY);
  43. } else {
  44. childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
  45. getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
  46. lp.leftMargin + lp.rightMargin,
  47. lp.width);
  48. }
  49. final int childHeightMeasureSpec;
  50. if (lp.height == LayoutParams.MATCH_PARENT) {
  51. final int height = Math.max(0, getMeasuredHeight()
  52. - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
  53. - lp.topMargin - lp.bottomMargin);
  54. childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
  55. height, MeasureSpec.EXACTLY);
  56. } else {
  57. childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
  58. getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
  59. lp.topMargin + lp.bottomMargin,
  60. lp.height);
  61. }
  62. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  63. }
  64. }
  65. }

遍历了父视图下面的子视图,然后调用 measureChildWithMargins() 方法来进行测量子视图,这个方法位于 ViewGroup 类中:

  1. protected void measureChildWithMargins(View child,
  2. int parentWidthMeasureSpec, int widthUsed,
  3. int parentHeightMeasureSpec, int heightUsed) {
  4. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  5. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  6. mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
  7. + widthUsed, lp.width);
  8. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  9. mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
  10. + heightUsed, lp.height);
  11. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  12. }

根据父视图的大小还有 mode,以及自己的  Padding和 Margin 还有 LayoutParams 来测量,先看下这里面的方法getChildMeasureSpec():

  1. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  2. int specMode = MeasureSpec.getMode(spec);
  3. int specSize = MeasureSpec.getSize(spec);
  4. int size = Math.max(0, specSize - padding);
  5. int resultSize = 0;
  6. int resultMode = 0;
  7. switch (specMode) {
  8. case MeasureSpec.EXACTLY:
  9. if (childDimension >= 0) {
  10. resultSize = childDimension;
  11. resultMode = MeasureSpec.EXACTLY;
  12. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  13. resultSize = size;
  14. resultMode = MeasureSpec.EXACTLY;
  15. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  16. resultSize = size;
  17. resultMode = MeasureSpec.AT_MOST;
  18. }
  19. break;
  20. case MeasureSpec.AT_MOST:
  21. if (childDimension >= 0) {
  22. resultSize = childDimension;
  23. resultMode = MeasureSpec.EXACTLY;
  24. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  25. resultSize = size;
  26. resultMode = MeasureSpec.AT_MOST;
  27. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  28. resultSize = size;
  29. resultMode = MeasureSpec.AT_MOST;
  30. }
  31. break;
  32. case MeasureSpec.UNSPECIFIED:
  33. if (childDimension >= 0) {
  34. resultSize = childDimension;
  35. resultMode = MeasureSpec.EXACTLY;
  36. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  37. resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
  38. resultMode = MeasureSpec.UNSPECIFIED;
  39. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  40. resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
  41. resultMode = MeasureSpec.UNSPECIFIED;
  42. }
  43. break;
  44. }
  45. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  46. }

根据 mode 和 size 测量出自己的大小之后,会把 mode 和 size 调用 makeMeasureSpec 继续合成。

继续看 measureChildWithMargins() 方法最后会在调用 View 的 measure(),如果这个 View 还是个 ViewGoup 的话,那就会递归调用 measure() 方法,如果是 View 的话,那就会调用 View 的 measure(),也就是会调用到 onMeasure()。

 

 

4. performLayout()

/base/core/java/android/view/ViewRootImpl.java

测量完成后,就开始来布局了。

  1. private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
  2. int desiredWindowHeight) {
  3. // ...
  4. host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
  5. // ...
  6. }

和 measure() 一样,也是递归调用。看看 FrameLayout 的 layout(),在 FrameLayout 的父类 View 中:

  1. public void layout(int l, int t, int r, int b) {
  2. if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
  3. onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
  4. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  5. }
  6. int oldL = mLeft;
  7. int oldT = mTop;
  8. int oldB = mBottom;
  9. int oldR = mRight;
  10. boolean changed = isLayoutModeOptical(mParent) ?
  11. setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
  12. if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
  13. onLayout(changed, l, t, r, b);
  14. }
  15. // ...
  16. }
  17. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  18. }

里面会调用 onLayout(),onLayout() 是一个空实现,子类可以在里面做一些自己的事,来看看 FrameLayout 的 onLayout():

  1. @Override
  2. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  3. layoutChildren(left, top, right, bottom, false /* no force left gravity */);
  4. }
  5. void layoutChildren(int left, int top, int right, int bottom,
  6. boolean forceLeftGravity) {
  7. final int count = getChildCount();
  8. final int parentLeft = getPaddingLeftWithForeground();
  9. final int parentRight = right - left - getPaddingRightWithForeground();
  10. final int parentTop = getPaddingTopWithForeground();
  11. final int parentBottom = bottom - top - getPaddingBottomWithForeground();
  12. for (int i = 0; i < count; i++) {
  13. final View child = getChildAt(i);
  14. if (child.getVisibility() != GONE) {
  15. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  16. final int width = child.getMeasuredWidth();
  17. final int height = child.getMeasuredHeight();
  18. int childLeft;
  19. int childTop;
  20. int gravity = lp.gravity;
  21. if (gravity == -1) {
  22. gravity = DEFAULT_CHILD_GRAVITY;
  23. }
  24. final int layoutDirection = getLayoutDirection();
  25. final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
  26. final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
  27. switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
  28. case Gravity.CENTER_HORIZONTAL:
  29. childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
  30. lp.leftMargin - lp.rightMargin;
  31. break;
  32. case Gravity.RIGHT:
  33. if (!forceLeftGravity) {
  34. childLeft = parentRight - width - lp.rightMargin;
  35. break;
  36. }
  37. case Gravity.LEFT:
  38. default:
  39. childLeft = parentLeft + lp.leftMargin;
  40. }
  41. switch (verticalGravity) {
  42. case Gravity.TOP:
  43. childTop = parentTop + lp.topMargin;
  44. break;
  45. case Gravity.CENTER_VERTICAL:
  46. childTop = parentTop + (parentBottom - parentTop - height) / 2 +
  47. lp.topMargin - lp.bottomMargin;
  48. break;
  49. case Gravity.BOTTOM:
  50. childTop = parentBottom - height - lp.bottomMargin;
  51. break;
  52. default:
  53. childTop = parentTop + lp.topMargin;
  54. }
  55. child.layout(childLeft, childTop, childLeft + width, childTop + height);
  56. }
  57. }
  58. }

递归调用过程,和 measure() 流程一样。

 

 

5. performDraw()

盗图,画图好累,码字好累 - draw 流程图:

/base/core/java/android/view/ViewRootImpl.java

  1. private void performDraw() {
  2. // ...
  3. try {
  4. draw(fullRedrawNeeded);
  5. } finally {
  6. mIsDrawing = false;
  7. }
  8. // ...
  9. }
  10. private void draw(boolean fullRedrawNeeded) {
  11. // ...
  12. if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
  13. return;
  14. }
  15. // ...
  16. }
  17. private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
  18. boolean scalingRequired, Rect dirty) {
  19. // ...
  20. mView.draw(canvas);
  21. // ...
  22. }

和上面的 measure、layout 一样,来看看 View 的 draw():

  1. public void draw(Canvas canvas) {
  2. // 画背景
  3. if (!dirtyOpaque) {
  4. drawBackground(canvas);
  5. }
  6. boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
  7. boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
  8. if (!verticalEdges && !horizontalEdges) {
  9. // 回调 onDraw()
  10. if (!dirtyOpaque) onDraw(canvas);
  11. // 分发 draw()
  12. dispatchDraw(canvas);
  13. // 绘制 foreground
  14. onDrawForeground(canvas);
  15. return;
  16. }
  17. // ... 后面情况也是这几个步骤
  18. }

先画背景,然后 onDraw() 画自己,接着 dispatchDraw() 分发绘制子 View,最后画前景。来看看 FrameLayout 对dispatchDraw() 的实现,在它的父类 ViewGroup 中:

  1. @Override
  2. protected void dispatchDraw(Canvas canvas) {
  3. // ...
  4. final int childrenCount = mChildrenCount;
  5. final View[] children = mChildren;
  6. for (int i = 0; i < childrenCount; i++) {
  7. if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
  8. more |= drawChild(canvas, child, drawingTime);
  9. }
  10. }
  11. // Draw any disappearing views that have animations
  12. if (mDisappearingChildren != null) {
  13. for (int i = disappearingCount; i >= 0; i--) {
  14. more |= drawChild(canvas, child, drawingTime);
  15. }
  16. }
  17. // ...
  18. }
  19. rotected boolean drawChild(Canvas canvas, View child, long drawingTime) {
  20. return child.draw(canvas, this, drawingTime);
  21. }

最后又是一个递归调用过程。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/738035
推荐阅读
相关标签
  

闽ICP备14008679号