Android源码分析(View的绘制流程)

  • 欢迎关注我的公众号
    公众号
  • 本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

源码基于安卓8.0分析结果

  • View是何时开始绘制的?Activity走了onCreate方法吗?这篇文章就是从程序的入口ActivityThread入口程序,去解释View中的measure()方法View中的layoutView中的draw怎么开始调用的,非常有意思!虽然好多的技术文档,在半个月前已经做好了,这篇文章,对我自己来讲的话,是个很好的复习~~
  • 为了更好地阐述着这篇文章,我这里就直接抛出结论了,为啥会这样的,在下篇文章会讲到,这里就记住一点,在Activity onResume后,调用了View onAttachedToWindow 才会开始View measure
    Android源码分析(View的绘制流程)_第1张图片
    Activity的生命周期和View的生命周期.jpg
  • 为什么会这样子?先看ActivityThread类里面有个内部private class H extends Handler这就是系统的Handler,具体分析请看Android源码分析(Handler机制),里面有个case RESUME_ACTIVITY,获取焦点
      case RESUME_ACTIVITY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
                    SomeArgs args = (SomeArgs) msg.obj;
                    handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
                            args.argi3, "RESUME_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
       break;
  • handleResumeActivity()方法,这里只截取了关键的代码
 final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
            return;
        }
        mSomeActivitiesChanged = true;
        //在这里执行performResumeActivity的方法中会执行Activity的onResume()方法
        r = performResumeActivity(token, clearHide, reason);
        if (r != null) {
            final Activity a = r.activity;

            if (localLOGV) Slog.v(
                    TAG, "Resume " + r + " started activity: " +
                            a.mStartedActivity + ", hideForNow: " + r.hideForNow
                            + ", finished: " + a.mFinished);
            final int forwardBit = isForward ?
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
            boolean willBeVisible = !a.mStartedActivity;
            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManager.getService().willActivityBeVisible(
                            a.getActivityToken());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            //PhoneWindow在这里获取到
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                //DecorView在这里获取到
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //获取ViewManager对象,在这里getWindowManager()实质上获取的是ViewManager的子类对象WindowManager
                // TODO: 2018/5/24 WindowManager
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    // Normally the ViewRoot sets up callbacks with the Activity
                    // in addView->ViewRootImpl#setView. If we are instead reusing
                    // the decor view we have to notify the view root that the
                    // callbacks may have changed.
                    //获取ViewRootImpl对象
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
        }
}
  • 第一点分析得出performResumeActivity()肯定先于wm.addView(decor, l);执行的~这也是为啥我们 Activity先获取焦点了,才去绘制View
  • performResumeActivity(),可以得出调用的是r.activity.performResume();
    Android源码分析(View的绘制流程)_第2张图片
    performResumeActivity
  • 关于r.activity.performResume();;这里也可以,看出,在activity 中的fragment获取焦点要晚于activity,虽然这是常识。注意这个方法mInstrumentation.callActivityOnResume(this);;然后才会执行onPostResume;这也就是为什么,Activity先获取焦点,后执行onPostResume();
 final void performResume() {
        performRestart();
        mInstrumentation.callActivityOnResume(this);
        mCalled = false;
       //这里也可以,看出,在activity 中的fragment获取焦点要晚于activity,虽然这是常识
        mFragments.dispatchResume();
        mFragments.execPendingActions();
        onPostResume();
    }
  • 关注这个方法mInstrumentation.callActivityOnResume(this);果然不出所料,这里执行了activity.onResume();;

    Android源码分析(View的绘制流程)_第3张图片
    callActivityOnResume.png

  • 既然在上面知道了,activity 获取焦点,会在上面执行,那么View的绘制就会在下面的函数中进行。

    • 1、获取PhoneWindow; activity.getWindow(),Window类的唯一子类
    • 2、获取window.getDecorView();DecorView,PhoneWindow的内部类,private final class DecorView extends FrameLayout ,安卓的事件分发和它密切相关Android源码分析(事件传递),也就是从Activity 传递到 ViewGroup的过程~~
    • 3、获取ViewManager wm = a.getWindowManager();,其实也就是activity.getWindowManager(),也就是获取的是ViewManager的子类对象WindowManager,这里的知道WindowManager其实也是一个接口.
      Android源码分析(View的绘制流程)_第4张图片
      image.png
    • 4、 wm.addView(decor, l);,也就是到这里来了,WindowManager.addView(decor,l).
 //PhoneWindow在这里获取到
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                //DecorView在这里获取到
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //获取ViewManager对象,在这里getWindowManager()实质上获取的是ViewManager的子类对象WindowManager
                // TODO: 2018/5/24 WindowManager
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    // Normally the ViewRoot sets up callbacks with the Activity
                    // in addView->ViewRootImpl#setView. If we are instead reusing
                    // the decor view we have to notify the view root that the
                    // callbacks may have changed.
                    //获取ViewRootImpl对象
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        //在这里WindowManager将DecorView添加到PhoneWindow中
                        wm.addView(decor, l);
                    }
                }
  • 分析到这里来了,会通过WindowManager.addView(decor,l).我们需要去找WindowManager的实现。WindowManagerImpl;
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
   @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
}
  • 去寻找WindowManagerGlobaladdView()方法。这里有个单利模式,在源码好多地方使用的单利模式都是这样,并没有进行双重判断,在老牌的图片加载框架ImageLoader也是这样获取单利对象,如果想了解更多设计模式的姿势,可以看这片文章二十三种设计模式.
  public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }
  • 在这里!就是WindowManagerGlobal.addView()的关键的方法,我做了两个注释,一个是view.setLayoutParams(wparams);,这个方法非常有意思,最近在研究ViewGroup的源码,发现不论什么情况下,View或者是ViewGroup都会有两次测量,这里是根本的原因,我先给结论。
    api26:执行2次onMeasure、1次onLayout、1次onDraw。
    api25-24:执行2次onMeasure、2次onLayout、1次onDraw,
    api23-21:执行3次onMeasure、2次onLayout、1次onDraw,
    api19-16:执行2次onMeasure、2次onLayout、1次onDraw,
    API等级24:Android 7.0 Nougat
    API等级25:Android 7.1 Nougat
    API等级26:Android 8.0 Oreo
    API等级27:Android 8.1 Oreo
    后续我会做一篇文章详细解释下,为什么会这样,这里不过多的解释了,自提一句,非常有意思的代码!以前还会有两次的layout,说明谷歌也在优化安卓 framework。todo
  public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
     ...
      root = new ViewRootImpl(view.getContext(), display);
            //view setLLayoutParams()在这里
      view.setLayoutParams(wparams);
      try {
                // TODO: 2018/6/4  这里呢?就是ViewRootImpl 调用的setView的方法,就在这里
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
  • ok,现在继续的关注这个方法ViewRootImpl.setView(view, wparams, panelParentView)
  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {   try {
                 // TODO: 2018/6/4 这里传入的attrs 决定了View 或者是ViewGroup是否会onMeasure 两次
                mWindowAttributes.copyFrom(attrs);
                } catch (RemoteException e) {
                   
                    // TODO: 2018/5/24 就会调动这里的来
                    unscheduleTraversals();
                } finally {
                    if (restore) {
                        attrs.restore();
                    } if (res < WindowManagerGlobal.ADD_OKAY) {
                    
                    // TODO: 2018/5/24 就会调动这里的来
                    unscheduleTraversals();}
                }
  • unscheduleTraversals(),没有Activity获取焦点的时候,这个方法肯定会执行
    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }
  • 关注mTraversalRunnable对象
  final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
  • doTraversal()方法,Traversal翻译过来就是遍历的意思~~

    // TODO: 2018/5/24  到这里来了 ---->     Traversal 遍历
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            performTraversals();
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

performTraversals这里就是整个View绘制的开始,所有的绘制,都会从这里开始,虽然这个方法代码有点多,但是关键的地方我都做了注释,下面一步一步的分析

   /**
     * 最终会调动到这里
     * ViewRootImpl的performTraversals()方法完成具体的视图绘制流程
     */
    private void performTraversals() {
        // cache mView since it is used so much below...
        //mView 就是DecorView根布局
        final View host = mView;

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals");
            host.debug();
        }
        //如果host=null  或者是mAdded=false 直接就return了
        if (host == null || !mAdded)
            return;
        //是否正在遍历
        mIsInTraversal = true;
        //是否马上需要绘制View
        mWillDrawSoon = true;
        boolean windowSizeMayChange = false;
        boolean newSurface = false;
        boolean surfaceChanged = false;
        /**
         * 这个WindowMananger 这里标记了 是否是需要 onMeasure 两次 哈哈
         */
        // TODO: 2018/6/4
        WindowManager.LayoutParams lp = mWindowAttributes;
        /*
        顶层视图DecorView所需要的窗口的宽度和高度
         */
        int desiredWindowWidth;
        int desiredWindowHeight;

        final int viewVisibility = getHostVisibility();
        final boolean viewVisibilityChanged = !mFirst
                && (mViewVisibility != viewVisibility || mNewSurfaceNeeded);
        final boolean viewUserVisibilityChanged = !mFirst &&
                ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));

        WindowManager.LayoutParams params = null;
        if (mWindowAttributesChanged) {
            mWindowAttributesChanged = false;
            surfaceChanged = true;
            params = lp;
        }
        CompatibilityInfo compatibilityInfo =
                mDisplay.getDisplayAdjustments().getCompatibilityInfo();
        if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
            params = lp;
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            if (mLastInCompatMode) {
                params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
                mLastInCompatMode = false;
            } else {
                params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
                mLastInCompatMode = true;
            }
        }

        mWindowAttributesChangesFlag = 0;

        Rect frame = mWinFrame;
        //在构造方法中mFirst已经设置为true,表示是否是第一次绘制DecorView
        if (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;

            final Configuration config = mContext.getResources().getConfiguration();
            // TODO: 2018/5/25 注意这个方法内部做了什么
            /*
             return lp.type == TYPE_STATUS_BAR_PANEL
                || lp.type == TYPE_INPUT_METHOD
                || lp.type == TYPE_VOLUME_OVERLAY;
             */
            // 如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要的窗口的宽度和高度
            //就是除了状态栏
            if (shouldUseDisplaySize(lp)) {
                // NOTE -- system code, won't try to do compat mode.
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {
                //否者顶层视图DecorView所需要的窗口的宽度和高度就是整个屏幕的宽度
                desiredWindowWidth = dipToPx(config.screenWidthDp);
                desiredWindowHeight = dipToPx(config.screenHeightDp);
            }

            // We used to use the following condition to choose 32 bits drawing caches:
            // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
            // However, windows are now always 32 bits by default, so choose 32 bits
            mAttachInfo.mUse32BitDrawingCache = true;
            mAttachInfo.mHasWindowFocus = false;
            mAttachInfo.mWindowVisibility = viewVisibility;
            mAttachInfo.mRecomputeGlobalAttributes = false;
            mLastConfigurationFromResources.setTo(config);
            mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
            // Set the layout direction if it has not been set before (inherit is the default)
            if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
                host.setLayoutDirection(config.getLayoutDirection());
            }
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);
            //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);

        } else {
            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
                mFullRedrawNeeded = true;
                mLayoutRequested = true;
                windowSizeMayChange = true;
            }
        }

        if (viewVisibilityChanged) {
            mAttachInfo.mWindowVisibility = viewVisibility;
            host.dispatchWindowVisibilityChanged(viewVisibility);
            if (viewUserVisibilityChanged) {
                host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
            }
            if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
                endDragResizing();
                destroyHardwareResources();
            }
            if (viewVisibility == View.GONE) {
                // After making a window gone, we will count it as being
                // shown for the first time the next time it gets focus.
                mHasHadWindowFocus = false;
            }
        }

        // Non-visible windows can't hold accessibility focus.
        if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
            host.clearAccessibilityFocus();
        }

        // Execute enqueued actions on every traversal in case a detached view enqueued an action
        getRunQueue().executeActions(mAttachInfo.mHandler);

        boolean insetsChanged = false;

        boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
        if (layoutRequested) {

            final Resources res = mView.getContext().getResources();

            if (mFirst) {
                // make sure touch mode code executes by setting cached value
                // to opposite of the added touch mode.
                mAttachInfo.mInTouchMode = !mAddedTouchMode;
                ensureTouchModeLocally(mAddedTouchMode);
            } else {
                if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
                    insetsChanged = true;
                }
                if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
                    insetsChanged = true;
                }
                if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
                    insetsChanged = true;
                }
                if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
                    if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                            + mAttachInfo.mVisibleInsets);
                }
                if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
                    insetsChanged = true;
                }
                if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) {
                    insetsChanged = true;
                }
                if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                        || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                    windowSizeMayChange = true;

                    if (shouldUseDisplaySize(lp)) {
                        // NOTE -- system code, won't try to do compat mode.
                        Point size = new Point();
                        mDisplay.getRealSize(size);
                        desiredWindowWidth = size.x;
                        desiredWindowHeight = size.y;
                    } else {
                        Configuration config = res.getConfiguration();
                        desiredWindowWidth = dipToPx(config.screenWidthDp);
                        desiredWindowHeight = dipToPx(config.screenHeightDp);
                    }
                }
            }

            // Ask host how big it wants to be
            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
        }

        if (collectViewAttributes()) {
            params = lp;
        }
        if (mAttachInfo.mForceReportNewAttributes) {
            mAttachInfo.mForceReportNewAttributes = false;
            params = lp;
        }

        if (mFirst || mAttachInfo.mViewVisibilityChanged) {
            mAttachInfo.mViewVisibilityChanged = false;
            int resizeMode = mSoftInputMode &
                    WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
            // If we are in auto resize mode, then we need to determine
            // what mode to use now.
            if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
                final int N = mAttachInfo.mScrollContainers.size();
                for (int i=0; i

5、第一次绘制的时候,这个标记一定是didLayout一定是true,一定会走到这个方法里面去performLayout(lp, mWidth, mHeight);

     final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            // TODO: 2018/5/25  执行布局操作
            performLayout(lp, mWidth, mHeight);
        }
}
  • 关于performLayout这个方法,直接会调用host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());,也就是View的layout的方法。
  private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                               int desiredWindowHeight) {
final View host = mView;
  try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
           //测试层级
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
 } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

6、这两个标记也是!cancelDraw && !newSurfacetrue,那么就会走到performDraw();

   if (!cancelDraw && !newSurface) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }
            // TODO: 2018/5/25 执行绘制的操作
            performDraw();
        } 
  • 关于performDraw();方法,直接调用的是draw(fullRedrawNeeded);
    private void performDraw() {
        mIsDrawing = true;
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}
  • 关于draw(fullRedrawNeeded);,会调用到这里来drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)
  private void draw(boolean fullRedrawNeeded) {
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
    }
  • 关于这个方法drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)。官方的解释为如果绘图成功,如果发生错误,则为false。返回false的,程序就发生了异常,也就是程序GG掉了,绘制失败,这里仅仅贴出关键的代码~~~,这样字,就调用到了mView.draw(canvas);
  private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                                 boolean scalingRequired, Rect dirty) {
    try {
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;
                // TODO: 2018/5/25   调用了View里面的draw方法
                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;
                }
            }
}

  • 最后做了两张图


    Android源码分析(View的绘制流程)_第5张图片
    View的绘制流程(一).jpg
Android源码分析(View的绘制流程)_第6张图片
View的绘制流程(二)ViewRootImpl.performTraversals.jpg
  • 说明几点
    • 如果感兴趣的,一定要去打个断点看一下这个流程
    • 限于作者水平有限,一定会存在有些错误,还望指出,谢谢

你可能感兴趣的:(Android源码分析(View的绘制流程))