【Android】从main函数到View的绘制流程

971 阅读10分钟
关于View的工作原理,大多数文章都只说View有measure、layout、draw三个过程,经过测量、布局、绘制,然后一个view就出来了。但是在这之前,Android系统做了什么,就不了解了。我想要“打破砂锅问到底”,这篇文章就从根源的mian函数开始,讲View的整个绘制流程捋一捋。

一、设计的类

Activity:与用户交互的基本界面。

Window: 最顶级的视图,所有View都要在add到Window里才能显示。Android中想要使用Window时,应该实例化它的实现类PhoneWindow。

PhoneWindow:这是一个特殊的Window类,每个Activity都有一个PhoneWindow。

WindowManager:顾名思义,这是Window的管理接口,它继承了ViewManager接口的ViewManager里面有addView(),removeView()和updateViewLayout()方法,用于管理View。

WindowManagerImpl:这是WindowManager的实现类,里面实现了addView()等方法。

DecorView:Activity最顶层的View,第一个添加进PhoneWindow的View,就是DecorView。DecorView继承FrameLayout,里面有个垂直线性布局,分成titleBar和content两个部分。在Activity中调用setContentView()方法设置的xml布局,就是添加到content中的。

ViewRootImpl:每个phoneWindow对应一个ViewRootImpl,它控制了View的绘制流程,measure()、layout()和draw的执行顺序,就是在ViewRootImpl中控制的。

二、从main开始

Android系统是用java写的,所有java程序的入口是main函数。Android系统的main函数在ActivityThread这个类中,来看一看:

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
	/**
	* Integrates the framework with Dalvik's sampling profiler.
	*/
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");
        //初始化Looper
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

可以看见,这里面基本都是做了一些系统级的初始化,然后启动looper,利用looper和handler来处理各种消息。这里,thread.attach()做了不少事情,我们进去看看。

private void attach(boolean system) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            //添加第一个Handler
            ViewRootImpl.addFirstDrawHandler(new Runnable() {
                @Override
                public void run() {
                    ensureJitEnabled();
                }
            });
            ……
        } else {
            // Don't set application object here -- if the system crashes,
            // we can't display an alert, we just want to die die die.
            android.ddm.DdmHandleAppName.setAppName("system_process",
                    UserHandle.myUserId());
           ……
        }

        // add dropbox logging to libcore
        DropBox.setReporter(new DropBoxReporter());

        ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {        
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
                synchronized (mResourcesManager) {
                    // We need to apply this change to the resources
                    // immediately, because upon returning the view
                    // hierarchy will be informed about it.
                    //我们需要立即将这些改变应用于资源,因为在返回时将查看视图层次结构。
                    if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) {
                        updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
                                mResourcesManager.getConfiguration().getLocales());

                        // This actually changed the resources!  Tell
                        // everyone about it.
                        if (mPendingConfiguration == null ||
                                mPendingConfiguration.isOtherSeqNewer(newConfig)) {
                            mPendingConfiguration = newConfig;

                            sendMessage(H.CONFIGURATION_CHANGED, newConfig);
                        }
                    }
                }
            }
            @Override
            public void onLowMemory() {
            }
            @Override
            public void onTrimMemory(int level) {
            }
    });
 }

这里省略了一些不需要关注的代码,我们可以看见,这个方法里出现了ViewRootImpl,我们看看它都干了些什么。从方法名来看,先是将一个Runnable添加进Handler,然后新建了一个配置回调。我们进入进去看看,实际上是不是这样呢?

//ViewRootImpl类中,这是一个Ruunable列表
static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList();
……//在ActivityThread类attach()中调用的方法
public static void addFirstDrawHandler(Runnable callback) {

    synchronized (sFirstDrawHandlers) {
        if (!sFirstDrawComplete) {
            sFirstDrawHandlers.add(callback);
        }
    }
}

//这是ViewRootImpl中唯一用到sFirstDrawHandlers的地方
private void draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;
    if (!surface.isValid()) {
        return;
    }

    if (DEBUG_FPS) {
        trackFPS();
    }

    if (!sFirstDrawComplete) {
        synchronized (sFirstDrawHandlers) {
            sFirstDrawComplete = true;
            final int count = sFirstDrawHandlers.size();
            for (int i = 0; i< count; i++) {
                //将Runnable推送到消息队列中
                mHandler.post(sFirstDrawHandlers.get(i));
            }
        }
    }

可以看见,确实如猜测那般,将Runable添加到了消息队列中,在draw()中执行。那Runnable执行的是什么任务呢?

void ensureJitEnabled() {
    if (!mJitEnabled) {
        mJitEnabled = true;
        dalvik.system.VMRuntime.getRuntime().startJitCompilation();
    }
}

这是调用系统dalvik虚拟机层的方法,启动Jit编译器,将改变的数据传递到底层,然后将View绘制到屏幕上。后面将新建一个ComponentCallbacks添加进ViewRootImpl中,我们进去看看:

//配置回调列表
static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList();
//attch()中调用的方法,将新建的ComponentCallbacks添加进来
public static void addConfigCallback(ComponentCallbacks callback) {
    synchronized (sConfigCallbacks) {
        sConfigCallbacks.add(callback);
    }
}
//改变配置,调用回调的onConfigureationChangerd()方法
void updateConfiguration(Configuration config, boolean force) {
    ……
    synchronized (sConfigCallbacks) {
        for (int i=sConfigCallbacks.size()-1; i>=0; i--) {
            sConfigCallbacks.get(i).onConfigurationChanged(config);
        }
    }
    ……
}
//在这个方法之中调用的updateConfiguration(),这个就是控制view绘制流程的主要方法

private void performTraversals() {
    ……
    updateConfiguration(new Configuration(mPendingConfiguration), !mFirst);
    ……
}

可见,在performTraversals()中,调用了回调的onConfigurationChanged()方法。而在attach()中添加的回调里,onConfigurationChanged()的作用是:“我们需要立即将这些改变应用于资源,因为在返回时将查看视图层次结构”。

由此可见,attch()方法中做的事情,就是为了使数据改变后,能及时将View绘制到屏幕上。

三、handMessager()中的处理

ActvityThread中,在main()里启动了looper,通过H的handleMessage()方法处理各种消息。代码如下:

private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = 100;
    public static final int PAUSE_ACTIVITY          = 101;
    ……
public void handleMessage(Message msg) {
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
    switch (msg.what) {
        case LAUNCH_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

            r.packageInfo = getPackageInfoNoCheck(
                    r.activityInfo.applicationInfo, r.compatInfo);
            handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
        case RELAUNCH_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
            ActivityClientRecord r = (ActivityClientRecord)msg.obj;
            handleRelaunchActivity(r);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        } break;
        ……
        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;        ……       Object obj = msg.obj;
       if (obj instanceof SomeArgs) {
           ((SomeArgs) obj).recycle();
       }
       if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
   }     ……
}

H是一个Handler,集中处理各种各样的消息。启动Activity的时候,就是在LAUNCH_ACTIVITY分支下,handleLaunchActivity()中处理,进入该方法中看看。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;

    if (r.profilerInfo != null) {
        mProfiler.setProfiler(r.profilerInfo);
        mProfiler.startProfiling();
    }

    // Make sure we are running with the most recent config.
    handleConfigurationChanged(null, null);

    if (localLOGV) Slog.v(
        TAG, "Handling launch of " + r);

    // Initialize before creating the activity
    WindowManagerGlobal.initialize();
    //创建Activity,Activity的onCreate()和onStart()生命周期在其中执行
    Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        reportSizeConfigurations(r);
        Bundle oldState = r.state;
        //Activity的onResume()方法在其中执行,View也是在其中绘制。
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

        if (!r.activity.mFinished && r.startsNotResumed) {
            // The activity manager actually wants this one to start out paused, because it
            // needs to be visible but isn't in the foreground. We accomplish this by going
            // through the normal startup (because activities expect to go through onResume()
            // the first time they run, before their window is displayed), and then pausing it.
            // However, in this case we do -not- need to do the full pause cycle (of freezing
            // and such) because the activity manager assumes it can just retain the current
            // state it has.
            performPauseActivityIfNeeded(r, reason);

            // We need to keep around the original state, in case we need to be created again.
            // But we only do this for pre-Honeycomb apps, which always save their state when
            // pausing, so we can not have them save their state when restarting from a paused
            // state. For HC and later, we want to (and can) let the state be saved as the
            // normal part of stopping the activity.
            if (r.isPreHoneycomb()) {
                r.state = oldState;
            }
        }
    } else {
        // If there was an error, for any reason, tell the activity manager to stop us.
        try {
            ActivityManagerNative.getDefault()
                .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                        Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
}

记得Activity的生命周期中,都说onCreate()后Activity创建成功,onStart()后就可以看见,onResume()之后可以交互。由此可知,View的绘制应该是在onStart()与onResume()之间,经进入performLaunchActivity()和handleResumeActivity()查看,发现View是在handleResumeActivity()中绘制的。而且,当Activity未被完全遮住的情况下,从其他界面返回Activity,也需要对界面重新绘制,可见将绘制VIew的流程放在handleResumeActivity()里面是合理的。

进入handleResumeActivity()看看:

final void handleResumeActivity(IBinder token,
        ……
        if (r.window == null && !a.mFinished && willBeVisible) {
            //获取PhoneWindow
            r.window = r.activity.getWindow();
            //获取DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            //获取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 impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient && !a.mWindowAdded) {
                a.mWindowAdded = true;
                //将DecorView添加进Window之中
                wm.addView(decor, l);
            }
            ……
}

上面代码做的事情很简单,就是获取PhoneWindow,DecorView等的实例,并将DecorView添加进PhoneWindow之中。addView()的具体实现是在WindowManagerImpl类之中。

WindowManagerImp类中l:@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}


WindowManagerGlobal类中:public void addView(View view, ViewGroup.LayoutParams params,        Display display, Window parentWindow) {
        ……

        root = new ViewRootImpl(view.getContext(), display);
        //此处的view是DecorView
        view.setLayoutParams(wparams);

        //添加参数
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        ……
    // do this last because it fires off messages to start doing things
    root.setView(view, wparams, panelParentView);
    ……
}

ViewRootImpl类中:/**
 * We have one child
 */
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        ……
        mWindowAttributesChanged = true;      //遍历调度,这里面执行绘制的方法   
      scheduleTraversals();
    }
}void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //Posts a callback to run on the next frame.
        //在下一帧执行回调,即执行 mTraversalRunnable
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
// mTraversalRunnable是一个TraversalRunnable内部类
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();TraversalRunnable类:
final class TraversalRunnable implements Runnable {    @Override
    public void run() {
        doTraversal();
    }
}
ViewRootImpl类中:
void doTraversal() {    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        //执行遍历
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

在WindowManagerImpl中将DecorView添加进PhoneWindow的时候,调用了ViewRootImpl的setView方法,启动TraversalRunnable任务,由该任务的doTraversal方法来调用performTraversals()方法,遍历对View进行绘制。经过重重调用,终于找到了绘制View的重头戏了。

四、performTraversals()控制流程

View的绘制为什么会先执行onMeasure(),再执行onLayout()和onDraw(),这必然有一个地方的代码控制了其执行的先后顺序,这个控制的地方,就在performTraversals()之中。

performTraversals()方法比较长,让我们分段慢慢看吧:

private void performTraversals() {
    // cache mView since it is used so much below...
    //这里的mView就是前面setView()方法里设置的View,即DecorView,先将它缓存起来。
    final View host = mView;

    //然后是一堆初始化
    ……

    Rect frame = mWinFrame;
    //如果是第一次执行,则会初始化宽高、可见性等属性,如果不是第一次,则获取以前的宽高
    if (mFirst) {
        mFullRedrawNeeded = true;
        mLayoutRequested = true;

        final Configuration config = mContext.getResources().getConfiguration();
        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 {
            desiredWindowWidth = dipToPx(config.screenWidthDp);
            desiredWindowHeight = dipToPx(config.screenHeightDp);
        }
        ……
        // 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;
        }
    }
    ……
   //判断是否重新请求布局
    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
   ……
   //判断是否改变尺寸
   windowShouldResize |= mActivityRelaunched;
   //第一次绘制、改变了尺寸、重新布局等,走该分支
  if (mFirst || windowShouldResize || insetsChanged ||
      viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
        ……
        //没有被停止或者请求下一步的绘制
       if (!mStopped || mReportNextDraw) {
                /**
                 * Ensure that the touch mode for this window is set, and if it is changing,
                 * take the appropriate action.
                 */
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                //焦点、宽高、内容元素、配置等改变,则需要重新测量
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                     // Ask host how big it wants to be
                    //测量方法,这里就是View的mesure流程,会调用OnMeasure方法
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
                    //如果设置了weight属性,则需要重新测量宽高。
                    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;
                    }

                    if (measureAgain) {
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }
        //不是第一次,没有改变配置等情况,走该分支,可能需要移动Window,不做测量
        } else {
            // Not the first pass and no window/insets/visibility change but the window
            // may have moved and we need check that and if so to update the left and right
            // in the attach info. We translate only the window frame since on window move
            // the window manager tells us only for the new frame but the insets are the
            // same and we do not want to translate them more than once.
            maybeHandleWindowMove(frame);
        }


        //重新布局
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {

            //布局流程,里面会调用View的OnLayout()方法
            performLayout(lp, mWidth, mHeight);

            // By this point all views have been sized and positioned
            // We can compute the transparent area
            //这里已经知道了viewd的尺寸和位置,然后加上parent的位置,才是真正的位置
            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
                // start out transparent
                // TODO: AVOID THAT CALL BY CACHING THE RESULT?
                host.getLocationInWindow(mTmpLocation);
                mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
                        mTmpLocation[0] + host.mRight - host.mLeft,
                        mTmpLocation[1] + host.mBottom - host.mTop);

                host.gatherTransparentRegion(mTransparentRegion);
                if (mTranslator != null) {
                    mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
                }

                if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
                    mPreviousTransparentRegion.set(mTransparentRegion);
                    mFullRedrawNeeded = true;
                    // reconfigure window manager
                    try {
                        mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
                    } catch (RemoteException e) {
                    }
                }
            }
        }    
        ……
        //设置内部插入元素、设置焦点等
        ……
        // Remember if we must report the next draw.
        //第一次绘制时,要通知window
        if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
            reportNextDraw();
        }

        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
        //绘制没被取消,并且没有新的需要绘制的画面过来,则绘制当前画面,否则重头开始改流程
        if (!cancelDraw && !newSurface) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }

            //绘制,里面会调用View的OnDraw()方法
            performDraw();
        } else {
            if (isViewVisible) {
                // Try again
                scheduleTraversals();
            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

        mIsInTraversal = false;
}

一个长长的performTraversals()方法从头过了一遍,它做的事情就是对View进行各种花样的判断,最终确定View的状态,然后对View按顺序进行Measure,Layout,和Draw。有时候可能该流程的每个步骤不是唯一的,比如设置了weight会进行两次Measure,有时要可能会出现意外情况导致不会Draw。

View的Measure、Layout、Draw三个流程已经是老生常谈了。下面我这个小学生也学着谈一谈吧。

五、Measure()测量

……(未完待续)