简析View工作的调用流程

我们都知道Activity的生命周期流程,我们也知道View绘制的三个方法onMeasure、onLayout、onDraw。但是你知道在启动一个Activity时,它们是工作在哪个生命周期的吗?这边我开始做一个完整的分析。以下代码都是基于Android 8.0的源码进行分析的,由于本人能力有限,不喜勿喷。

首先用一个时序图来表示这一整个分析流程,performTraversals是View三大流程onMeasure、onLayout、onDraw方法执行开始的地方,我们的分析的就是是从ActivityThreadRootViewImpl执行performTraversals的过程,

image

ActivityThread
这个类是我们平时说的UI线程,但是他不是一个真的线程类。它是一个App程序运行的管理和执行的类,其中Activty的生命周期就是在这里通过Handler来执行的,我们可以定位到H这个内部类,它是一个Handler的子类,在handleMessage中包含着四大组件的生命周期的消息处理。

其中的这段代码就是代表Activity启动的开始,handleLaunchActivity是其中最重要的方法。

  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;

定位到handleLaunchActivity方法

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {

   ...
   //创建WSM
   WindowManagerGlobal.initialize();
   
   //在这里创建了Activity对象,执行onCreate和onStart方法
   Activity a = performLaunchActivity(r, customIntent);
   
   if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            
            //执行onResume方法,View的工作流程方法
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

            if (!r.activity.mFinished && r.startsNotResumed) {
                
                performPauseActivityIfNeeded(r, reason);

                if (r.isPreHoneycomb()) {
                    r.state = oldState;
                }
            }
        } else {
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                ActivityManager.getService()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
   ...

}

我们看看handleResumeActivity这个方法,这里首先会根据token来取得要处理的Activity,然后performResumeActivity这个方法才是真正执行onResume的方法,注意在onResume执行的时候,View还没绘制完成,这时候是拿不到View的宽高的,更不要说在onCreateonStart了。decor一开始会被设置为不可见,所以在onResume之前,界面对用户都是不可见的。从wm.addView(decor, l)这段代码开始才是真正的绘制过程,并且在r.activity.makeVisible();这里开始才把View设置为可见

final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
            ActivityClientRecord r = mActivities.get(token);
          
            ...
            
             // 在这个方法里面执行onResume的回调,实际上这时还没开始执行视图的测量,所以解释了为什么没办法在onResume中获取view的宽高
             r = performResumeActivity(token, clearHide, reason);
             if (r != null) {
             
                final Activity a = r.activity;
                
                ...
                
                if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                
                //这个view是DecorView,在Activity的attach方法中创建PhoneWindow时创建的。
                View decor = r.window.getDecorView();
                
                //decor一开始会被设置为不可见
                decor.setVisibility(View.INVISIBLE);
                
                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;
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        
                        //把Decorview添加到WindowManager中
                        wm.addView(decor, l);
                    } else {
                       
                        a.onWindowAttributesChanged(l);
                    }
                  }
               }
             }
            ...
             if (r.activity.mVisibleFromClient) {
                    //在这里把DecorView设置为可见,也就是界面可见
                    r.activity.makeVisible();
             }
            ...
}

上面代码的WindowManager是Activity在attach()方法中创建的。ViewManager是一个接口,WindowManager是继承于ViewManager的一个接口,WindowManager的实现类是WindowManagerImpl,所以上图代码中的addView调用的实际是WindowManagerImpl里面的方法

WindowManagerImpl
在WindowManagerImpl中找到了addView方法的实现,但是这边也很简单,它把addView的操作委托给了WindowManagerGlobal的addView方法

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

这几个类的关系可以用UML图表示为


image

WindowManagerGlobal

在这个方法中执行添加View的是ViewRootImpl,首先先创建了ViewRootImpl对象,然后把view、创建的ViewRootImpl和布局参数保存在三个数组当中,然后将添加工作托管给了ViewRootImpl

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
            
            ...
            
            //调整布局参数等
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
            
            //同一个view不能在WindowManager中添加两次
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }
            
            ...
            
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            
            //保存布局参数和view,在更新视图时用到
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            // do this last because it fires off messages to start doing things
            try {
                //真正完成视图工作流程的方法
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
            
    }

上面的流程可以大概的总结成

  • 父窗口对子窗口的布局参数进行调整
  • 检查View的添加次数,同一个View不能在WindowManager中添加两次
  • 创建ViewRootImpl,将View、ViewRootImpl、布局参数保存在三个数组中,以供之后的查询之需
  • 调用ViewRootImpl.setView()函数,将控件交给ViewRootImpl进行托管。

ViewRootImpl
ViewRootImpl是一个非常重要的类,实现了ViewParent接口,作为整个控件树的根部,负责与WMS进行直接的通讯,负责管理Surface,负责触发控件的测量与布局,负责触发控件的绘制,同时也是输入事件的中转站,我们平时处理的事件分发也是通过这个类的中转处理之后才来到Activity和各层级的View的。ViewRootImpl如此的重要我们可以看看它的构造方法里面做了什么事情

public ViewRootImpl(Context context, Display display) {
        mContext = context;
        
        //从WindowManagerGlobal拿到IWindowSession,它是ViewRootImpl和WMS通信的代理
        mWindowSession = WindowManagerGlobal.getWindowSession();
        mDisplay = display;
        mBasePackageName = context.getBasePackageName();
        
        //把当前线程保存起来,即UI线程,在绘制时会对发起的thread和mThread进行比较,不一致时会抛出异常
        mThread = Thread.currentThread();
        mLocation = new WindowLeaked(null);
        mLocation.fillInStackTrace();
        mWidth = -1;
        mHeight = -1;
        mDirty = new Rect();
        mTempRect = new Rect();
        mVisRect = new Rect();
        mWinFrame = new Rect();
        mWindow = new W(this);
        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
        mViewVisibility = View.GONE;
        mTransparentRegion = new Region();
        mPreviousTransparentRegion = new Region();
        mFirst = true; // true for the first time the view is added
        mAdded = false;
        
        //创建AttachInfo对象,里面保存了Handler,window等
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);
        mAccessibilityManager = AccessibilityManager.getInstance(context);
        mAccessibilityManager.addAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager, mHandler);
        mHighContrastTextManager = new HighContrastTextManager();
        mAccessibilityManager.addHighTextContrastStateChangeListener(
                mHighContrastTextManager, mHandler);
        mViewConfiguration = ViewConfiguration.get(context);
        mDensity = context.getResources().getDisplayMetrics().densityDpi;
        mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
        mFallbackEventHandler = new PhoneFallbackEventHandler(context);
        
        //Choreographer是一个依附于当前线程的信号同步类,用于通过VSYNC特性进行界面绘制和刷新,界面的三大流程就是着他的回调事件里面进行的
        mChoreographer = Choreographer.getInstance();
        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);

        if (!sCompatibilityDone) {
            sAlwaysAssignFocus = true;

            sCompatibilityDone = true;
        }

        loadSystemProperties();
    }

我们定位到setView方法,其中刷新的方法是requestLayout,它是ViewParent接口的方法,用于刷新整个视图树,当视图有变动时都会通过这个方法来通知跟布局刷新

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     ...
     requestLayout();
     ...
 }

继续看requestLayout方法,这里先进行UI线程检查,然后开始计划视图树的遍历工作

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //ui线程检查,
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    

mThread就是ViewRootImpl初始化时保存起来的当前线程,它检查的并不是当前线程是否是UI线程,而是当前线程是否是操作线程。这个操作线程就是创建ViewRootImpl对象的线程,其实这里可以看出来操作不是一定子线程不能操作UI,只要创建和执行在同一个线程就是可以的

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

可以试试这段代码在activity执行会不会报错

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                MyDialog dialog = new MyDialog(MainActivity.this);
                dialog.show();
                Looper.loop();
            }
        }).start();

然后看scheduleTraversals方法,它通过Choreographer的回调来通知执行的时机。为什么要这样做呢?
首先来理解一下,图形界面的绘制,大概是有CPU准备数据,然后通过驱动层把数据交给GPU来进行绘制。图形API不允许CPU和GPU直接通信,所以就有了图形驱动(Graphics Driver)来进行联系。Graphics Driver维护了一个序列(Display List),CPU不断把需要显示的数据放进去,GPU不断取出来进行显示。

image

其中Choreographer起调度的作用。统一绘制图像到Vsync的某个时间点。这个就是VSYNC(垂直同步)的作用,我们都知道界面刷新速度每秒60帧以上时就不会感受到界面的卡顿,我们就可以理解为当VSYNC信号间隔是16毫秒时,我们就不会觉得卡顿了。

最后执行定位到了performTraversals方法,这个方法就是View里面onMeasure、onLayout、onDraw方法的起点。

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            
            //Choreographer的回调,里面执行界面的绘制
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

performTraversals方法非常长,这里下面是最简化后的工作流程,按顺序调用了performMeasure、performLayout、performDraw方法。而这三个方法由直接或者间距的调用了onMeasure、onLayout、onDraw方法。

private void performTraversals() {
 
     //定义预测量想要的宽高,对宽高进行赋值,这两个变量将是生成MeasureSpec参数SPEC_SIZE候选
     int desiredWindowWidth;
     int desiredWindowHeight;
     ...
     //执行RunQueue的任务,平时我们通过`view.post`发送的任务就是在这里被执行的。通过attachInfo里面的handler将Runnable对象发送到主线程执行
     getRunQueue().executeActions(attachInfo.mHandler);
     ...
     //1. 执行测量工作
       if (mApplyInsetsRequested) {
            mApplyInsetsRequested = false;
            mLastOverscanRequested = mAttachInfo.mOverscanRequested;
            dispatchApplyInsets(host);
            if (mLayoutRequested) {
               
                //执行performMeasure()对视图树进行测量
                windowSizeMayChange |= measureHierarchy(host, lp,
                        mView.getContext().getResources(),
                        desiredWindowWidth, desiredWindowHeight);
            }
        }
     ...
     //2. 执行布局
     performLayout(lp, mWidth, mHeight);
     ...
     //3. 执行视图绘制
     performDraw();
     ...

}

measureHierarchy的方法执行如下

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;
        int childHeightMeasureSpec;
        boolean windowSizeMayChange = false;

        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
                "Measuring " + host + " in display " + desiredWindowWidth
                + "x" + desiredWindowHeight + "...");

        boolean goodMeasure = false;
        
        //对于父控件是WRAP_CONTENT时,这里指的是浮动窗口,要进行测量参数的协商
        
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            // On large screens, we don't want to allow dialogs to just
            // stretch to fill the entire width of the screen to display
            // one line of text.  First try doing the layout at a smaller
            // size to see if it will fit.
            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                    + ", desiredWindowWidth=" + desiredWindowWidth);
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                
                //第一次测量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                        + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
                        + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
                        + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;
                } else {
                    // Didn't fit in that size... try expanding a bit.
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                            + baseSize);
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    
                    //第二次测量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                            + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                        goodMeasure = true;
                    }
                }
            }
        }

        if (!goodMeasure) {
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            
            //第三次测量
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            
            //view的测量尺寸和窗口尺寸不一致时告诉外面,窗口尺寸有可能变化
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals -- after measure");
            host.debug();
        }

        //窗口尺寸是否可能需要发生变化
        return windowSizeMayChange;
    }

上面的代码中首先会判断测量的View的宽度是否为WRAP_CONTENT,这种一般是悬浮窗口,如果是则可能会比普通的窗口多两次performMeasure,上层的View通过MeasureSpec指导子View的测量,我们平时在onMeasure(int widthMeasureSpec,int heightMeasureSpec)就是从这边开始往下传递的。它和子控件自身期望的尺寸工具决定了子控件最终的测量结果。我们具体看看getRootMeasureSpec计算结果

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

上面的方法决定了DecorView测量参数,可以得知

  • ViewGroup.LayoutParams.MATCH_PARENT,表示测量参数的大小就是窗口尺寸的大小,测量模式为MeasureSpec.EXACTLY。
  • ViewGroup.LayoutParams.WRAP_CONTENT,表示子控件可以是它所期望的尺寸,但是不得大于窗口尺寸。
  • 默认是固定大小,表示子控件的尺寸就是它设置的尺寸大小

我们再往下分析调用流程
此时测量工作已经来到了View层级了。performMeasure将measureHierarchy给予的widthSpec与heightSpec交给DecorView。而DecorView就是布局的顶级View,它是一个ViewGroup,实现了FrameLayout布局。从这里开始就会遍历视图树中的所有View的onMeasure方法。至此来到了我们熟悉的onMeasure流程

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

measure方法中没有任何进行测量的代码,只是调用了onMeasure方法,这里面还做对onMeasur的正确使用做了检查,当没有setMeasuredDimension时会抛出异常。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    
        ...
        
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                
                //调用onMeasure
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            
            //当开发者没有调用setMeasuredDimension时会抛出异常
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
         }
         ...
}

performLayout将会决定所有View四个顶点的位置,host.layout将会执行DecorView的布局流程,getMeasuredWidth和getMeasuredHeight是上一步measure得到的结果

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {

         final View host = mView;
         ...
         try {
         //decorView的布局
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            }
         ...
    }

host.layout将调用View里面的layout方法

 public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        //保存原始坐标
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        //将l, t, r, b设置给mLeft, mTop, mBottom, mRight
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        
            //从DecorView开始调用控件树的onLayout方法
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                //通知监听了布局变化的地方
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }

从上面可知layout做了如下三件事

  • 通过setFrame给View设置四个角度坐标
  • 调用onLayout,使控件树开始执行布局操作
  • 通知设置了View.addOnLayoutChangeListener()的地方

经过测量和布局之后,每个View已经知道自己都大小和位置了,最后我们来看看最终的绘制方法performDraw。

private void performDraw() {
    ...
     try {
         //调用draw方法进行实际的绘制
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
     ...
}

performDraw方法很简单只是调用draw方法进行实际的绘制,我们继续看看

private void draw(boolean fullRedrawNeeded) {
    ...
    
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            //当满足以下条件时将进行硬件绘制
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                 ...
                 
                 //硬件加速绘制
                 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
            } else {
                 ...
                 
                 //软件绘制
                 if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
     }
}

draw方法中会产生软件绘制和硬件加速绘制两个分支,我们看drawSoftware方法

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
           
        final Canvas canvas;
        try {
            
            ...

            //创建canvas
            canvas = mSurface.lockCanvas(dirty);

            if (left != dirty.left || top != dirty.top || right != dirty.right
                    || bottom != dirty.bottom) {
                attachInfo.mIgnoreDirtyState = true;
            }

            // TODO: Do this in native
            canvas.setDensity(mDensity);
        } 
         ...
         
         try {
                //进行canvas进行平移
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;
 
                //调用DecorView的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;
                }
            }
            
          ...
          
          try {
                //最后将绘制的内容显示出来
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                Log.e(mTag, "Could not unlock surface", e);
                mLayoutRequested = true;    // ask wm for a new surface next time.
                //noinspection ReturnInsideFinallyBlock
                return false;
            }
    }

drawSoftware方法主要进行了如下四步操作

  • 创建canvas
  • 进行canvas进行平移
  • 调用DecorView的draw方法,对控件树进行遍历绘制
  • 将绘制的内容显示出来

到这里View工作流程的onDraw就执行完了,回到handleResumeActivity的代码中,通过调用r.activity.makeVisible();把Activity的内容显示出来

Activity

   void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

从上面的调用过程我们把View的绘制和Activity的生命周期联系起来了,对于理解整个系统的运作有更深的理解,知道对于UI线程得知是一个相对的概念,vsync垂直同步的机制等。View的工作流程是一个非常复杂的过程,里面的每一个点都值得深入的分析,这里只是理清了三大流程的调用过程。

  • Activit启动时View的绘制过程是从生命周期的onResume开始的
  • Activity从onResume开始才是对用户可见的
  • 在onResume或者onCreate中没办法直接获得View的宽高,因为这时测量还没完成
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容