簡(jiǎn)析View工作的調(diào)用流程

我們都知道Activity的生命周期流程郁稍,我們也知道View繪制的三個(gè)方法onMeasure俱病、onLayout、onDraw搂漠。但是你知道在啟動(dòng)一個(gè)Activity時(shí)迂卢,它們是工作在哪個(gè)生命周期的嗎?這邊我開(kāi)始做一個(gè)完整的分析。以下代碼都是基于Android 8.0的源碼進(jìn)行分析的冷守,由于本人能力有限刀崖,不喜勿噴。

首先用一個(gè)時(shí)序圖來(lái)表示這一整個(gè)分析流程拍摇,performTraversals是View三大流程onMeasure亮钦、onLayout、onDraw方法執(zhí)行開(kāi)始的地方充活,我們的分析的就是是從ActivityThreadRootViewImpl執(zhí)行performTraversals的過(guò)程蜂莉,

image

ActivityThread
這個(gè)類(lèi)是我們平時(shí)說(shuō)的UI線程,但是他不是一個(gè)真的線程類(lèi)混卵。它是一個(gè)App程序運(yùn)行的管理和執(zhí)行的類(lèi)映穗,其中Activty的生命周期就是在這里通過(guò)Handler來(lái)執(zhí)行的,我們可以定位到H這個(gè)內(nèi)部類(lèi)幕随,它是一個(gè)Handler的子類(lèi)蚁滋,在handleMessage中包含著四大組件的生命周期的消息處理。

其中的這段代碼就是代表Activity啟動(dòng)的開(kāi)始赘淮,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) {

   ...
   //創(chuàng)建WSM
   WindowManagerGlobal.initialize();
   
   //在這里創(chuàng)建了Activity對(duì)象,執(zhí)行onCreate和onStart方法
   Activity a = performLaunchActivity(r, customIntent);
   
   if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            
            //執(zhí)行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這個(gè)方法走诞,這里首先會(huì)根據(jù)token來(lái)取得要處理的Activity,然后performResumeActivity這個(gè)方法才是真正執(zhí)行onResume的方法蛤高,注意在onResume執(zhí)行的時(shí)候蚣旱,View還沒(méi)繪制完成,這時(shí)候是拿不到View的寬高的戴陡,更不要說(shuō)在onCreateonStart了塞绿。decor一開(kāi)始會(huì)被設(shè)置為不可見(jiàn),所以在onResume之前猜欺,界面對(duì)用戶都是不可見(jiàn)的位隶。從wm.addView(decor, l)這段代碼開(kāi)始才是真正的繪制過(guò)程,并且在r.activity.makeVisible();這里開(kāi)始才把View設(shè)置為可見(jiàn)

final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
            ActivityClientRecord r = mActivities.get(token);
          
            ...
            
             // 在這個(gè)方法里面執(zhí)行onResume的回調(diào)开皿,實(shí)際上這時(shí)還沒(méi)開(kāi)始執(zhí)行視圖的測(cè)量,所以解釋了為什么沒(méi)辦法在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();
                
                //這個(gè)view是DecorView篮昧,在Activity的attach方法中創(chuàng)建PhoneWindow時(shí)創(chuàng)建的赋荆。
                View decor = r.window.getDecorView();
                
                //decor一開(kāi)始會(huì)被設(shè)置為不可見(jiàn)
                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設(shè)置為可見(jiàn),也就是界面可見(jiàn)
                    r.activity.makeVisible();
             }
            ...
}

上面代碼的WindowManager是Activity在attach()方法中創(chuàng)建的懊昨。ViewManager是一個(gè)接口窄潭,WindowManager是繼承于ViewManager的一個(gè)接口,WindowManager的實(shí)現(xiàn)類(lèi)是WindowManagerImpl酵颁,所以上圖代碼中的addView調(diào)用的實(shí)際是WindowManagerImpl里面的方法

WindowManagerImpl
在WindowManagerImpl中找到了addView方法的實(shí)現(xiàn)嫉你,但是這邊也很簡(jiǎn)單月帝,它把a(bǔ)ddView的操作委托給了WindowManagerGlobal的addView方法

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

這幾個(gè)類(lèi)的關(guān)系可以用UML圖表示為


image

WindowManagerGlobal

在這個(gè)方法中執(zhí)行添加View的是ViewRootImpl,首先先創(chuàng)建了ViewRootImpl對(duì)象幽污,然后把view嚷辅、創(chuàng)建的ViewRootImpl和布局參數(shù)保存在三個(gè)數(shù)組當(dāng)中,然后將添加工作托管給了ViewRootImpl

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
            
            ...
            
            //調(diào)整布局參數(shù)等
            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;
            }
        }
            
            //同一個(gè)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);
            
            //保存布局參數(shù)和view距误,在更新視圖時(shí)用到
            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;
            }
            
    }

上面的流程可以大概的總結(jié)成

  • 父窗口對(duì)子窗口的布局參數(shù)進(jìn)行調(diào)整
  • 檢查View的添加次數(shù)簸搞,同一個(gè)View不能在WindowManager中添加兩次
  • 創(chuàng)建ViewRootImpl,將View准潭、ViewRootImpl趁俊、布局參數(shù)保存在三個(gè)數(shù)組中,以供之后的查詢之需
  • 調(diào)用ViewRootImpl.setView()函數(shù)刑然,將控件交給ViewRootImpl進(jìn)行托管寺擂。

ViewRootImpl
ViewRootImpl是一個(gè)非常重要的類(lèi),實(shí)現(xiàn)了ViewParent接口泼掠,作為整個(gè)控件樹(shù)的根部怔软,負(fù)責(zé)與WMS進(jìn)行直接的通訊,負(fù)責(zé)管理Surface武鲁,負(fù)責(zé)觸發(fā)控件的測(cè)量與布局爽雄,負(fù)責(zé)觸發(fā)控件的繪制,同時(shí)也是輸入事件的中轉(zhuǎn)站沐鼠,我們平時(shí)處理的事件分發(fā)也是通過(guò)這個(gè)類(lèi)的中轉(zhuǎn)處理之后才來(lái)到Activity和各層級(jí)的View的挚瘟。ViewRootImpl如此的重要我們可以看看它的構(gòu)造方法里面做了什么事情

public ViewRootImpl(Context context, Display display) {
        mContext = context;
        
        //從WindowManagerGlobal拿到IWindowSession,它是ViewRootImpl和WMS通信的代理
        mWindowSession = WindowManagerGlobal.getWindowSession();
        mDisplay = display;
        mBasePackageName = context.getBasePackageName();
        
        //把當(dāng)前線程保存起來(lái)饲梭,即UI線程乘盖,在繪制時(shí)會(huì)對(duì)發(fā)起的thread和mThread進(jìn)行比較,不一致時(shí)會(huì)拋出異常
        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;
        
        //創(chuàng)建AttachInfo對(duì)象憔涉,里面保存了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是一個(gè)依附于當(dāng)前線程的信號(hào)同步類(lèi),用于通過(guò)VSYNC特性進(jìn)行界面繪制和刷新兜叨,界面的三大流程就是著他的回調(diào)事件里面進(jìn)行的
        mChoreographer = Choreographer.getInstance();
        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);

        if (!sCompatibilityDone) {
            sAlwaysAssignFocus = true;

            sCompatibilityDone = true;
        }

        loadSystemProperties();
    }

我們定位到setView方法穿扳,其中刷新的方法是requestLayout,它是ViewParent接口的方法国旷,用于刷新整個(gè)視圖樹(shù)矛物,當(dāng)視圖有變動(dòng)時(shí)都會(huì)通過(guò)這個(gè)方法來(lái)通知跟布局刷新

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

繼續(xù)看requestLayout方法,這里先進(jìn)行UI線程檢查跪但,然后開(kāi)始計(jì)劃視圖樹(shù)的遍歷工作

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //ui線程檢查履羞,
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    

mThread就是ViewRootImpl初始化時(shí)保存起來(lái)的當(dāng)前線程,它檢查的并不是當(dāng)前線程是否是UI線程,而是當(dāng)前線程是否是操作線程忆首。這個(gè)操作線程就是創(chuàng)建ViewRootImpl對(duì)象的線程爱榔,其實(shí)這里可以看出來(lái)操作不是一定子線程不能操作UI,只要?jiǎng)?chuàng)建和執(zhí)行在同一個(gè)線程就是可以的

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

可以試試這段代碼在activity執(zhí)行會(huì)不會(huì)報(bào)錯(cuò)

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

然后看scheduleTraversals方法糙及,它通過(guò)Choreographer的回調(diào)來(lái)通知執(zhí)行的時(shí)機(jī)详幽。為什么要這樣做呢?
首先來(lái)理解一下丁鹉,圖形界面的繪制妒潭,大概是有CPU準(zhǔn)備數(shù)據(jù),然后通過(guò)驅(qū)動(dòng)層把數(shù)據(jù)交給GPU來(lái)進(jìn)行繪制揣钦。圖形API不允許CPU和GPU直接通信雳灾,所以就有了圖形驅(qū)動(dòng)(Graphics Driver)來(lái)進(jìn)行聯(lián)系。Graphics Driver維護(hù)了一個(gè)序列(Display List)冯凹,CPU不斷把需要顯示的數(shù)據(jù)放進(jìn)去谎亩,GPU不斷取出來(lái)進(jìn)行顯示。

image

其中Choreographer起調(diào)度的作用宇姚。統(tǒng)一繪制圖像到Vsync的某個(gè)時(shí)間點(diǎn)匈庭。這個(gè)就是VSYNC(垂直同步)的作用,我們都知道界面刷新速度每秒60幀以上時(shí)就不會(huì)感受到界面的卡頓浑劳,我們就可以理解為當(dāng)VSYNC信號(hào)間隔是16毫秒時(shí)阱持,我們就不會(huì)覺(jué)得卡頓了。

最后執(zhí)行定位到了performTraversals方法魔熏,這個(gè)方法就是View里面onMeasure衷咽、onLayout、onDraw方法的起點(diǎn)蒜绽。

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            
            //Choreographer的回調(diào)镶骗,里面執(zhí)行界面的繪制
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

performTraversals方法非常長(zhǎng),這里下面是最簡(jiǎn)化后的工作流程躲雅,按順序調(diào)用了performMeasure鼎姊、performLayout、performDraw方法相赁。而這三個(gè)方法由直接或者間距的調(diào)用了onMeasure相寇、onLayout、onDraw方法钮科。

private void performTraversals() {
 
     //定義預(yù)測(cè)量想要的寬高裆赵,對(duì)寬高進(jìn)行賦值,這兩個(gè)變量將是生成MeasureSpec參數(shù)SPEC_SIZE候選
     int desiredWindowWidth;
     int desiredWindowHeight;
     ...
     //執(zhí)行RunQueue的任務(wù)跺嗽,平時(shí)我們通過(guò)`view.post`發(fā)送的任務(wù)就是在這里被執(zhí)行的。通過(guò)attachInfo里面的handler將Runnable對(duì)象發(fā)送到主線程執(zhí)行
     getRunQueue().executeActions(attachInfo.mHandler);
     ...
     //1. 執(zhí)行測(cè)量工作
       if (mApplyInsetsRequested) {
            mApplyInsetsRequested = false;
            mLastOverscanRequested = mAttachInfo.mOverscanRequested;
            dispatchApplyInsets(host);
            if (mLayoutRequested) {
               
                //執(zhí)行performMeasure()對(duì)視圖樹(shù)進(jìn)行測(cè)量
                windowSizeMayChange |= measureHierarchy(host, lp,
                        mView.getContext().getResources(),
                        desiredWindowWidth, desiredWindowHeight);
            }
        }
     ...
     //2. 執(zhí)行布局
     performLayout(lp, mWidth, mHeight);
     ...
     //3. 執(zhí)行視圖繪制
     performDraw();
     ...

}

measureHierarchy的方法執(zhí)行如下

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;
        
        //對(duì)于父控件是WRAP_CONTENT時(shí),這里指的是浮動(dòng)窗口桨嫁,要進(jìn)行測(cè)量參數(shù)的協(xié)商
        
        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);
                
                //第一次測(cè)量
                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);
                    
                    //第二次測(cè)量
                    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);
            
            //第三次測(cè)量
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            
            //view的測(cè)量尺寸和窗口尺寸不一致時(shí)告訴外面植兰,窗口尺寸有可能變化
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }

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

        //窗口尺寸是否可能需要發(fā)生變化
        return windowSizeMayChange;
    }

上面的代碼中首先會(huì)判斷測(cè)量的View的寬度是否為WRAP_CONTENT,這種一般是懸浮窗口璃吧,如果是則可能會(huì)比普通的窗口多兩次performMeasure楣导,上層的View通過(guò)MeasureSpec指導(dǎo)子View的測(cè)量,我們平時(shí)在onMeasure(int widthMeasureSpec,int heightMeasureSpec)就是從這邊開(kāi)始往下傳遞的畜挨。它和子控件自身期望的尺寸工具決定了子控件最終的測(cè)量結(jié)果筒繁。我們具體看看getRootMeasureSpec計(jì)算結(jié)果

    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測(cè)量參數(shù),可以得知

  • ViewGroup.LayoutParams.MATCH_PARENT巴元,表示測(cè)量參數(shù)的大小就是窗口尺寸的大小毡咏,測(cè)量模式為MeasureSpec.EXACTLY。
  • ViewGroup.LayoutParams.WRAP_CONTENT逮刨,表示子控件可以是它所期望的尺寸呕缭,但是不得大于窗口尺寸。
  • 默認(rèn)是固定大小修己,表示子控件的尺寸就是它設(shè)置的尺寸大小

我們?cè)偻路治稣{(diào)用流程
此時(shí)測(cè)量工作已經(jīng)來(lái)到了View層級(jí)了恢总。performMeasure將measureHierarchy給予的widthSpec與heightSpec交給DecorView。而DecorView就是布局的頂級(jí)View睬愤,它是一個(gè)ViewGroup片仿,實(shí)現(xiàn)了FrameLayout布局。從這里開(kāi)始就會(huì)遍歷視圖樹(shù)中的所有View的onMeasure方法尤辱。至此來(lái)到了我們熟悉的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方法中沒(méi)有任何進(jìn)行測(cè)量的代碼砂豌,只是調(diào)用了onMeasure方法,這里面還做對(duì)onMeasur的正確使用做了檢查,當(dāng)沒(méi)有setMeasuredDimension時(shí)會(huì)拋出異常。

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
                
                //調(diào)用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
            
            //當(dāng)開(kāi)發(fā)者沒(méi)有調(diào)用setMeasuredDimension時(shí)會(huì)拋出異常
            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將會(huì)決定所有View四個(gè)頂點(diǎn)的位置葵姥,host.layout將會(huì)執(zhí)行DecorView的布局流程俊犯,getMeasuredWidth和getMeasuredHeight是上一步measure得到的結(jié)果

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將調(diào)用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;
        }

        //保存原始坐標(biāo)
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        //將l, t, r, b設(shè)置給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開(kāi)始調(diào)用控件樹(shù)的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();
                //通知監(jiān)聽(tīng)了布局變化的地方
                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做了如下三件事

  • 通過(guò)setFrame給View設(shè)置四個(gè)角度坐標(biāo)
  • 調(diào)用onLayout,使控件樹(shù)開(kāi)始執(zhí)行布局操作
  • 通知設(shè)置了View.addOnLayoutChangeListener()的地方

經(jīng)過(guò)測(cè)量和布局之后工腋,每個(gè)View已經(jīng)知道自己都大小和位置了,最后我們來(lái)看看最終的繪制方法performDraw。

private void performDraw() {
    ...
     try {
         //調(diào)用draw方法進(jìn)行實(shí)際的繪制
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
     ...
}

performDraw方法很簡(jiǎn)單只是調(diào)用draw方法進(jìn)行實(shí)際的繪制蓄拣,我們繼續(xù)看看

private void draw(boolean fullRedrawNeeded) {
    ...
    
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            //當(dāng)滿足以下條件時(shí)將進(jìn)行硬件繪制
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                 ...
                 
                 //硬件加速繪制
                 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
            } else {
                 ...
                 
                 //軟件繪制
                 if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
     }
}

draw方法中會(huì)產(chǎn)生軟件繪制和硬件加速繪制兩個(gè)分支,我們看drawSoftware方法

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

            //創(chuàng)建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 {
                //進(jìn)行canvas進(jìn)行平移
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;
 
                //調(diào)用DecorView的draw方法努隙,對(duì)控件樹(shù)進(jìn)行遍歷繪制
                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 {
                //最后將繪制的內(nèi)容顯示出來(lái)
                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方法主要進(jìn)行了如下四步操作

  • 創(chuàng)建canvas
  • 進(jìn)行canvas進(jìn)行平移
  • 調(diào)用DecorView的draw方法球恤,對(duì)控件樹(shù)進(jìn)行遍歷繪制
  • 將繪制的內(nèi)容顯示出來(lái)

到這里View工作流程的onDraw就執(zhí)行完了,回到handleResumeActivity的代碼中荸镊,通過(guò)調(diào)用r.activity.makeVisible();把Activity的內(nèi)容顯示出來(lái)

Activity

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

從上面的調(diào)用過(guò)程我們把View的繪制和Activity的生命周期聯(lián)系起來(lái)了咽斧,對(duì)于理解整個(gè)系統(tǒng)的運(yùn)作有更深的理解堪置,知道對(duì)于UI線程得知是一個(gè)相對(duì)的概念,vsync垂直同步的機(jī)制等张惹。View的工作流程是一個(gè)非常復(fù)雜的過(guò)程舀锨,里面的每一個(gè)點(diǎn)都值得深入的分析,這里只是理清了三大流程的調(diào)用過(guò)程宛逗。

  • Activit啟動(dòng)時(shí)View的繪制過(guò)程是從生命周期的onResume開(kāi)始的
  • Activity從onResume開(kāi)始才是對(duì)用戶可見(jiàn)的
  • 在onResume或者onCreate中沒(méi)辦法直接獲得View的寬高坎匿,因?yàn)檫@時(shí)測(cè)量還沒(méi)完成
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市雷激,隨后出現(xiàn)的幾起案子替蔬,更是在濱河造成了極大的恐慌,老刑警劉巖屎暇,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件承桥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡恭垦,警方通過(guò)查閱死者的電腦和手機(jī)快毛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)番挺,“玉大人唠帝,你說(shuō)我怎么就攤上這事⌒兀” “怎么了襟衰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)粪摘。 經(jīng)常有香客問(wèn)我瀑晒,道長(zhǎng),這世上最難降的妖魔是什么徘意? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任苔悦,我火速辦了婚禮,結(jié)果婚禮上椎咧,老公的妹妹穿的比我還像新娘玖详。我一直安慰自己,他們只是感情好勤讽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布蟋座。 她就那樣靜靜地躺著,像睡著了一般脚牍。 火紅的嫁衣襯著肌膚如雪向臀。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天诸狭,我揣著相機(jī)與錄音券膀,去河邊找鬼君纫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛三娩,可吹牛的內(nèi)容都是我干的庵芭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼雀监,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了眨唬?” 一聲冷哼從身側(cè)響起会前,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎匾竿,沒(méi)想到半個(gè)月后瓦宜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岭妖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年临庇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昵慌。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡假夺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出斋攀,到底是詐尸還是另有隱情已卷,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布淳蔼,位于F島的核電站侧蘸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鹉梨。R本人自食惡果不足惜讳癌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望存皂。 院中可真熱鬧晌坤,春花似錦、人聲如沸艰垂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)猜憎。三九已至娩怎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胰柑,已是汗流浹背截亦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工爬泥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人崩瓤。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓袍啡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親却桶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子境输,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 前言 本文的目的有兩個(gè): 給對(duì)自定義View感興趣的人一些入門(mén)的指引 給正在使用自定義View的人一些更深入的解析...
    無(wú)求_95dd閱讀 651評(píng)論 0 1
  • 前言 本文的目的有兩個(gè): 給對(duì)自定義View感興趣的人一些入門(mén)的指引 給正在使用自定義View的人一些更深入的解析...
    BrotherChen閱讀 399評(píng)論 0 0
  • 前言 本文的目的有兩個(gè): 給對(duì)自定義View感興趣的人一些入門(mén)的指引 給正在使用自定義View的人一些更深入的解析...
    王帥Alex閱讀 20,714評(píng)論 34 169
  • 本文來(lái)自網(wǎng)絡(luò),也可能略有改動(dòng)颖系,如有任何不妥可以聯(lián)系刪除嗅剖,原文地址 http://www.reibang.com/...
    M45ter閱讀 406評(píng)論 0 1
  • 寫(xiě)在前面 今天和老爸去醫(yī)院了,老爸最近腰疼嘁扼,幸好沒(méi)什么事信粮,各位也要多多愛(ài)惜自己的身體,等身體出了問(wèn)題就來(lái)不及了趁啸。好...
    xiasuhuei321閱讀 576評(píng)論 2 5