简析View工作的调用流程

作者: kisass | 来源:发表于2019-03-12 18:16 被阅读44次

    我们都知道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的宽高,因为这时测量还没完成

    相关文章

      网友评论

        本文标题:简析View工作的调用流程

        本文链接:https://www.haomeiwen.com/subject/oronpqtx.html