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

640?wx_fmt=png


今日科技快讯


据CNBC报道,社交网络巨头Facebook已承认向61家科技公司提供了其用户数据的特殊访问权限,此前该公司曾在2015年公开表示限制此类访问。Facebook在上周五晚些时候提交给美国国会的747页文件中承认,该公司在2015年5月宣布限制上述做法后,继续与61家硬件和软件制造商分享用户信息。


作者简介


本篇来自 豌豆射手_BiuBiu的投稿,分享了Android源码分析(View的绘制流程),一起来看看!希望大家喜欢。

豌豆射手_BiuBiu的博客地址:

https://www.jianshu.com/u/a58eb984bda4


正文


源码基于安卓8.0分析结果

View是何时开始绘制的?Activity走了onCreate方法吗?这篇文章就是从程序的入口ActivityThread入口程序,去解释View中的measure()方法、View中的layout、View中的draw怎么开始调用的,非常有意思!虽然好多的技术文档,在半个月前已经做好了,这篇文章,对我自己来讲的话,是个很好的复习~~

为了更好地阐述着这篇文章,我这里就直接抛出结论了,为啥会这样的,在下篇文章会讲到,这里就记住一点,在Activity onResume后,调用了View onAttachedToWindow 才会开始View measure

640?wx_fmt=jpeg

Activity的生命周期和View的生命周期.jpg

为什么会这样子?先看ActivityThread类里面有个内部private class H extends Handler这就是系统的Handler,具体分析请看Android源码分析(Handler机制)

https://www.jianshu.com/p/a2c53e96cae6

里面有个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 != 0true,
                            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();

640?wx_fmt=png

关于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();

640?wx_fmt=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其实也是一个接口.

640?wx_fmt=png

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);
    }
}

去寻找WindowManagerGlobal的addView()方法。这里有个单利模式,在源码好多地方使用的单利模式都是这样,并没有进行双重判断,在老牌的图片加载框架ImageLoader也是这样获取单利对象,如果想了解更多设计模式的姿势,可以看这片文章二十三种设计模式.

https://www.jianshu.com/p/4e01479b6a2c

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绘制的开始,所有的绘制,都会从这里开始,虽然这个方法代码有点多,但是关键的地方我都做了注释,下面一步一步的分析

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要的窗口的宽度和高度

// 如果窗口的类型是有状态栏的,那么顶层视图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);
            }

获得view宽高的测量规格

// TODO: 2018/5/25 //获得view宽高的测量规格,
                    // TODO: 2018/5/25 mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

注意这个对象WindowManager.LayoutParams lp ,如果说lp.horizontalWeight > 0.0f或者是lp.verticalWeight > 0.0f,那么measureAgain =true;horizontalWeight这个标记大概是这个意思指示额外空间的多少将被水平分配。如果视图指定0不应被拉伸。否则额外像素将被优先评估。在所有重量大于0的视图中。一般都指示出还有多少的水平的空间将要被分配。

/**
         * 这个WindowMananger 这里标记了 是
         */

        // TODO: 2018/6/4
        WindowManager.LayoutParams lp = mWindowAttributes;
              // TODO: 2018/5/25  这里是第一步的  执行测量的操作
           performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
          if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }

如果这个measureAgain=true的话,就会再次调用performMeasure(),通过代码可以发现这就调用了两次performMeasure;

其实我这里犯了一个错误,不是这样的子,这个标记不一定是为true。

if (measureAgain) {
                        if (DEBUG_LAYOUT) Log.v(mTag,
                                "And hey let's measure once more: width=" + width
                                        + " height=" + height);
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
   }

关于performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);方法,其实就是调用的是mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);,也就是View第一步是测量。

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

第一次绘制的时候,这个标记一定是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(00, host.getMeasuredWidth(), host.getMeasuredHeight());
           //测试层级
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
 } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

这两个标记也是!cancelDraw && !newSurface为true,那么就会走到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;
                }
            }
}

最后做了两张图(点击左下角的阅读原文可以看到清晰大图)

640?wx_fmt=jpeg

640?wx_fmt=jpeg


总结


说明几点

  • 如果感兴趣的,一定要去打个断点看一下这个流程

  • 限于作者水平有限,一定会存在有些错误,还望指出,谢谢


欢迎长按下图 -> 识别图中二维码

或者 扫一扫 关注我的公众号

640.png?

640?wx_fmt=jpeg

猜你喜欢

转载自blog.csdn.net/c10wtiybq1ye3/article/details/80906305