View繪製流程源碼分析

源碼分析第三篇,Activity啓動後的View繪製流程的源碼分析,因爲View繪製牽扯內容太多,所以此篇文章略長,也許許多小夥伴看到部分就會失去耐心,但我相信若能所有看完,或多或少也會有點感悟,若能看完整有那些不足和有誤的地方,請留言告知,感謝!!! (注:如有什麼地方闡述有誤,敬請指正。)html

View繪製基礎概念

說到View繪製首先要先了解幾個概念:java

  • Window:是一個抽象類,具備窗口管理的功能,實現類爲PhoneWindow。Window有3類,應用層Window、子Window、系統Window。應用層Window對應的好比說Activity,而子Window必須附着在父Window上,如Dialog、PopupWindow。系統Window有如Toast、System Alert等。其層級對應區間以下:android

    應用層Window: 1 - 99
      子Window: 1000 - 1999
      系統Window: 2000 - 2999
    複製代碼

    毫無疑問,層級越高的顯示的越靠上。c++

  • PhoneWindow類:PhoneWindow這個類是Framework爲咱們提供的Android窗口的具體實現。咱們平時調用setContentView()方法設置Activity的用戶界面時,實際上就完成了對所關聯的PhoneWindow的 ViewTree(窗口所承載的控件樹)的設置。咱們還能夠經過Activity類的requestWindowFeature()方法來定製Activity關聯PhoneWindow的外觀,這個方法實際上作的是把咱們所請求的窗口外觀特性存儲到了PhoneWindow的mFeatures成員中,在窗口繪製階段生成外觀模板時,會根據mFeatures的值繪製特定外觀。該類繼承於Window類,是Window類的具體實現,即咱們能夠經過該類具體去繪製窗口。而且,該類內部引用一個DecorView對象,該DectorView對象是全部應用窗口(Activity界面)的根View。簡而言之,PhoneWindow類是把一個FrameLayout類即DecorView對象進行必定的包裝,將它做爲應用窗口的根View,並提供一組通用的窗口操做接口。它是Android中的最基本的窗口系統,每一個Activity均會建立一個PhoneWindow對象,是Activity和整個View系統交互的接口。canvas

  • DecorView類:是一個應用窗口的根容器,它本質上是一個FrameLayout。DecorView有惟一一個子View,它是一個垂直LinearLayout,包含兩個子元素,一個是TitleView(ActionBar的容器),另外一個是ContentView(窗口內容的容器)。關於ContentView,它是一個FrameLayout(android.R.id.content),咱們日常用的setContentView就是設置它的子View。windows

  • ViewRootImpl類:ViewRootImpl是實際管理Window中因此View的類,每一個Activity中ViewRootImpl數量取決於調用mWindowManager.addView的調用次數。緩存

注:Activity是由中心控制器ActivityManagerService來管理控制的。和Activity相似,UI層的內容是由另外一個控制器WindowManagerService(WMS)來管理的。bash

setContentView初始化佈局

View的繪製要從Activity建立後,執行setContentView方法開始分析:session

import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
複製代碼

這裏的MainActivity繼承自Activity類,Activity類是AppCompatActivity、FragmentActivity等 Activity的父類,所以直接繼承自Activity更便於分析。app

  1. Activity類:
public void setContentView(@LayoutRes int layoutResID) {
        // getWindow()獲取的是Window
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    
    public Window getWindow() {
        return mWindow;
    }
複製代碼

getWindow()中直接返回mWindow,那麼mWindow是何時被賦值的呢,這就要說到Activity啓動過程啦,Activity啓動最後會調用ActivityThread類中的handleLaunchActivity方法(若不清楚Activity啓動流程的,請參考我上一篇文章),而handleLaunchActivity方法裏又會調用performLaunchActivity方法建立並返回一個Activity,看下performLaunchActivity方法裏部分代碼:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       handleConfigurationChanged(null, null);
       //初始化 WindowManagerService,主要是獲取到 WindowManagerService 代理對象
       WindowManagerGlobal.initialize();
       Activity a = performLaunchActivity(r, customIntent);

       if (a != null) {
           r.createdConfig = new Configuration(mConfiguration);
           // 回調onResume()
           handleResumeActivity(r.token, false, r.isForward,
               !r.activity.mFinished && !r.startsNotResumed);
           ...
       }
       ...
   }

   private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       ...
       ContextImpl appContext = createBaseContextForActivity(r);
       Activity activity = null;
       try {
           java.lang.ClassLoader cl = appContext.getClassLoader();
           // 建立Activity
           activity = mInstrumentation.newActivity(
                   cl, component.getClassName(), r.intent);
           StrictMode.incrementExpectedActivityCount(activity.getClass());
           r.intent.setExtrasClassLoader(cl);
           r.intent.prepareToEnterProcess();
           if (r.state != null) {
               r.state.setClassLoader(cl);
           }
       } catch (Exception e) {
           if (!mInstrumentation.onException(activity, e)) {
               throw new RuntimeException(
                   "Unable to instantiate activity " + component+ ": " + e.toString(), e);
           }
       }

       try {
           // 建立 Application 對象
           Application app = r.packageInfo.makeApplication(false, mInstrumentation);
           ...
           if (activity != null) {
               ...
               // 回調Activity的attach方法
               activity.attach(appContext, this, getInstrumentation(), r.token,
                       r.ident, app, r.intent, r.activityInfo, title, r.parent,
                       r.embeddedID, r.lastNonConfigurationInstances, config,
                       r.referrer, r.voiceInteractor, window, r.configCallback);
               ...
               // 回調Activity的onCreate()方法
               if (r.isPersistable()) {
                   mInstrumentation.callActivityOnCreate(activity, 
                       r.state, r.persistentState);
               } else {
                   mInstrumentation.callActivityOnCreate(activity, r.state);
               }
           ...
           }
       } catch (SuperNotCalledException e) {
           throw e;
       ...
       return activity;
   }
複製代碼

從代碼中能夠看出在performLaunchActivity方法裏不只建立了Activity還調用了attahc方法,而 mWindow就是在attahc方法中賦值的。

// Activity類的attach方法:
final void attach(Context context, ActivityThread aThread,
           Instrumentation instr, IBinder token, int ident,
           Application application, Intent intent, ActivityInfo info,
           CharSequence title, Activity parent, String id,
           NonConfigurationInstances lastNonConfigurationInstances,
           Configuration config, String referrer, IVoiceInteractor voiceInteractor,
           Window window, ActivityConfigCallback activityConfigCallback) {
       attachBaseContext(context);

       mFragments.attachHost(null /*parent*/);
       // mWindow賦值 PhoneWindow繼承自Window對象,是Window類的具體實現
       mWindow = new PhoneWindow(this, window, activityConfigCallback);
       mWindow.setWindowControllerCallback(this);
       mWindow.setCallback(this);
       mWindow.setOnWindowDismissedCallback(this);
       mWindow.getLayoutInflater().setPrivateFactory(this);
       ...
       // 設置WindowManagerImpl對象
       mWindow.setWindowManager(
               (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
               mToken, mComponent.flattenToString(),
               (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
       if (mParent != null) {
           mWindow.setContainer(mParent.getWindow());
       }
       // 獲取WindowManagerImpl對象
       mWindowManager = mWindow.getWindowManager();
       mCurrentConfig = config;
       mWindow.setColorMode(info.colorMode);
   }
複製代碼

回到setContentView方法,在該方法中getWindow()也調用了setContentView方法,而getWindow()返回的真實類則是PhoneWindow,因此此時調用的是PhoneWindow類中的setContentView方法。

  1. PhoneWindow類setContentView方法:
/**
 * 什麼是Transition?
 *  安卓5.0中Activity和Fragment變換是創建在名叫Transitions的安卓新特性之上的。
 *  這個誕生於4.4的transition框架爲在不一樣的UI狀態之間產生動畫效果提供了很是方便的API。
 *  該框架主要基於兩個概念:場景(scenes)和變換(transitions)。
 *  場景(scenes)定義了當前的UI狀態,
 *  變換(transitions)則定義了在不一樣場景之間動畫變化的過程。
*/

    @Override
    public void setContentView(int layoutResID) {
        // contentParent是mDecor(DecorView)兩部分中的ContentView部分
        if (mContentParent == null) {
            // 初始化DecoView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        // 是否有特徵,須要動態設置,默認沒有(動畫效果)
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            // 本文不是重點先不分析
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 通常會走這裏
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
    
    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // 生成mDecor
            mDecor = generateDecor(-1);
            ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...
        }
    }

    protected DecorView generateDecor(int featureId) {
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext().getResources());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        // new DecorView
        return new DecorView(context, featureId, this, getAttributes());
    }

    protected ViewGroup generateLayout(DecorView decor) {
        ...
        // android.R.id.content
        // 系統內部定義的佈局,contentParent指的是DecorView的ContentView部分
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    
        int layoutResource;
        ...

        mDecor.startChanging();
        // 根據不一樣的Feature設置不一樣的佈局文件
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        ...
        return contentParent;
    }
複製代碼

hasFeature(FEATURE_CONTENT_TRANSITIONS)是用來判斷是否來啓用Transition Api,Transition是什麼代碼上方有所說明。想具體瞭解Transition以及用法,點擊下方連接。

www.jcodecraeer.com/a/anzhuokai…

先初始化了DecoView(根佈局),而後會調用mLayoutInflater.inflate()方法來填充佈局,inflate方法會使用Xml解析器,解析咱們傳入的xml文件,並保存到mContentParent裏。Xml解析的具體源碼就不分析啦,感興趣的小夥伴自行查看吧。到這裏,setContentView()的總體執行流程咱們就分析完了,至此咱們已經完成了Activity的ContentView的建立與設置工做。

onResume界面可見繪製之關聯Window和ViewRootImpl

  1. View的繪製和Activity的啓動息息相關,此時Activity已經走完回調onCreate,而在Activity流程中已經走完performLaunchActivity方法,繼續往下走則會走入handleResumeActivity方法中而後調用 performResumeActivity
// ActivityThread類:
    final void handleResumeActivity(IBinder token,boolean clearHide
        , boolean isForward, boolean reallyResume, int seq, String reason) {
        ...
        // 回調onStart和onResume方法
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {  
            final Activity a = r.activity;  
            boolean willBeVisible = !a.mStartedActivity;  
            ...  
            if (r.window == null && !a.mFinished && willBeVisible) {  
                r.window = r.activity.getWindow();  
                View decor = r.window.getDecorView();  
                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 (a.mVisibleFromClient) {  
                    // 1.主要此變量,下面要用
                    a.mWindowAdded = true;  
                    // 2.Window和DecorView關聯
                    wm.addView(decor, l);  
                }  
            ...  
            if (!r.activity.mFinished && willBeVisible  
                && r.activity.mDecor != null && !r.hideForNow) {  
                ...  
                mNumVisibleActivities++;  
                if (r.activity.mVisibleFromClient) {  
                    // 3.顯示DecorView
                    r.activity.makeVisible();   
                }  
            }  
            ... 
        }
    }
複製代碼

WindowManager是個接口,它的實現類是WindowManagerImpl類,而WindowManagerImpl又把相關邏輯交給了WindowManagerGlobal處理。WindowManagerGlobal是個單例類,它在進程中只存在一個實例,是它內部的addView方法最終建立了咱們的核心類ViewRootImpl。先看上面代碼1處,設置當前Activity成員變量mWindowAdded爲true代表Window已經添加過啦,2處代碼和3處代碼:

// WindowManagerImpl類:
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
// WindowManagerGlobal類:
    // 2處
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...
            // new出實際管理Window中全部View的類ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            // mRoots爲ViewRootImpl的集合,即執行過多少次addView就有多少ViewRootImpl
            mRoots.add(root);
            mParams.add(wparams);
            try {
                // 執行ViewRootImpl的setView--View繪製的起點
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
// Activity類:
    // 3處
    void makeVisible() {
        // 第1處代碼,mWindowAdded已爲true
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        // mDecor設置爲顯示
        mDecor.setVisibility(View.VISIBLE);
    }
複製代碼

這個過程建立一個 ViewRootImpl,並將以前建立的 DecoView 做爲參數傳入,之後 DecoView 的事件都由 ViewRootImpl 來管理了,好比,DecoView 上添加 View,刪除 View。ViewRootImpl 實現了 ViewParent 這個接口,這個接口最多見的一個方法是 requestLayout()。

// ViewRootImpl類:
    public ViewRootImpl(Context context, Display display) {
        ...
        // 從WindowManagerGlobal中獲取一個IWindowSession的實例。它是ViewRootImpl和WMS進行通訊的代理
        mWindowSession = WindowManagerGlobal.getWindowSession();
        ...
        mWindow = new W(this);//建立了一個W本地Binder對象,做用爲將WMS的事件通知到應用程序進程
        ...
        mChoreographer = Choreographer.getInstance();//Choreographer對象,用於統一調度窗口繪圖
        ...
    }
    
// WindowManagerGlobal類:
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    // 獲取WindowManagerService的Binder代理 windowManager
                    IWindowManager windowManager = getWindowManagerService();
                    // 經過Binder代理 windowManager調用openSession函數
                    // 獲取實例sWindowSession:表示活動的客戶端會話。每一個進程一般有一個 Session對象與windowManager交互。
                    // 經過openSession函數來與WMS創建一個通訊會話,後面繼續細說
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
複製代碼

該段代碼中引入兩個概念:mWindowSession和mWindow,分別是IWindowSession和IWindow,具體做用下面細講。

onResume界面可見繪製之IWindowSession和IWindow

  1. 關聯完Window和ViewRootImpl後,ViewRootImpl立馬執行了setView,開始了View繪製的征程。
// ViewRootImpl類:
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ...
                requestLayout();
                ...
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    // 調用IWindowSession的addToDisplay方法,第一個參數是IWindow
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
            }
        }
    }
複製代碼

ViewRootImpl類的setView方法主要作了3個事:

  • 保存傳入的view參數爲mView,這個mView只向PhoneWindow的DecorView
  • 執行了開始繪製的方法requestLayout();
  • 調用IWindowSession的addToDisplay函數,這是一個跨進程的Binder通訊,第一個參數是mWindow,它是W類型,從IWindow.Stub派生的。

從上面代碼可發現,ViewRoot和遠端進程SystemServer的WMS是有交互的,總結一下交互流程:

  • ViewRootImpl初始化時WindowManagerGlobal調用getWindowSession,經IWindowManager調用openSession,獲得IWindowSession對象。
  • setView方法中,調用IWindowSession的addToDisplay函數,把一個IWindow對象做爲參數傳入。

看一下openSession方法:

// WindowManagerService類:
    @Override
    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) {
        ...
        // 返回一個Session對象,它支持Binder通訊,而且屬於Bn端。
        // Bn意味着Binder Native 端,Bp是Binder Proxy端
        // 這兩端會實現相同的接口,但Proxy端只是經過Binder ipc發送一個Binder Transaction,
        // native端是真正作事情,再將結果返回。
        Session session = new Session(this, callback, client, inputContext);
        return session;
    }
    
// Session類:
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        // 調用WMS的addWindow方法
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
    
// WindowManagerService類:
    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        ......
        synchronized(mWindowMap) {
            ...
            // 建立WindowToken
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
            ...
            // 調用attach方法
            win.attach();
        }    
        ...
        return res;
    }
    
// WindowToken類:
    void attach() {
        if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
        mSession.windowAddedLocked(mAttrs.packageName);
    }
    
// Session類:
    void windowAddedLocked(String packageName) {
        ...
        if (mSurfaceSession == null) {
            ...
            // 建立SurfaceSession對象
            mSurfaceSession = new SurfaceSession();
            ...
        }
        mNumWindow++;
    }
複製代碼

上面代碼是按照IWindowSession有關的邏輯順序排列的,這裏又出現了一個重要對象mSurfaceSession,不過仍是先講解IWindowSession和IWindow,先來看一張ViewRootImpl和WMS關係圖:

根據這張圖先來總結一下:

  • ViewRootImpl經過IWindowSession和WMS進行跨進程通訊,IWindowSession定義在IWindowSession.aidl文件中
  • ViewRootImpl內部有一個W內部類,它也是一個基於Binder的通訊類,W是IWindow的Bn端,用於請求響應。 咱們來看一下W類內,都有哪些方法:
static class W extends IWindow.Stub {
        private final WeakReference<ViewRootImpl> mViewAncestor;
        private final IWindowSession mWindowSession;

        W(ViewRootImpl viewAncestor) {
            mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
            mWindowSession = viewAncestor.mWindowSession;
        }

        @Override
        public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
                Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
                MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout,
                boolean alwaysConsumeNavBar, int displayId) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchResized(frame, overscanInsets, contentInsets,
                        visibleInsets, stableInsets, outsets, reportDraw, mergedConfiguration,
                        backDropFrame, forceLayout, alwaysConsumeNavBar, displayId);
            }
        }

        @Override
        public void moved(int newX, int newY) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchMoved(newX, newY);
            }
        }

        @Override
        public void dispatchAppVisibility(boolean visible) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchAppVisibility(visible);
            }
        }

        @Override
        public void dispatchGetNewSurface() {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchGetNewSurface();
            }
        }

        @Override
        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
            }
        }

        private static int checkCallingPermission(String permission) {
            try {
                return ActivityManager.getService().checkPermission(
                        permission, Binder.getCallingPid(), Binder.getCallingUid());
            } catch (RemoteException e) {
                return PackageManager.PERMISSION_DENIED;
            }
        }
        ...
        /* Drag/drop */
        @Override
        public void dispatchDragEvent(DragEvent event) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchDragEvent(event);
            }
        }

        @Override
        public void updatePointerIcon(float x, float y) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.updatePointerIcon(x, y);
            }
        }
        ...
    }
複製代碼

能夠看到W內部有一個ViewRootImpl的弱引用,從W內繼承的方法能夠看出,W即IWindow會通知ViewRootImpl一些事件。這裏的事件指的就是按鍵、觸屏等事件。一個按鍵是如何被分發的呢?其大體流程以下:

  • WMS所在的SystemServer進程接收到按鍵事件
  • WMS找到UI位於屏幕位於頂端的進程所對應的IWindow對象,這是一個Bp端對象。
  • 調用這個IWindow對象的dispatchKey,IWindow對象的Bn端位於ViewRootImpl中,ViewRootImpl再根據內部View的位置信息找到真正處理這個事件的View,最後調用dispatchKey方法完成按鍵處理。

到此位置,應該大概能明白IWindowSession和IWindow的用處了吧,再總結一下:

  • IWindowSession:用於和WMS通訊,每一個App進程都會和WMS創建一個IWindowSession會話用於通訊。
  • IWindow:用於回調WMS事件,IWindow是WMS用來進行事件通知的,每當發生一些事件時,WMS就會把這些事件告訴某個IWindow,而後IWindow再回調回ViewRootImpl中的某個View,來響應這些事件。

onResume界面可見繪製之同步屏障--VSYNC同步

還有Surface和SurfaceSession沒有正式介紹呢,不過在此以前先來繼續介紹requestLayout():

// ViewRootImpl類:
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            // 檢查是否在非UI線程更新UI
            checkThread();
            mLayoutRequested = true;
            // 遍歷
            scheduleTraversals();
        }
    }
    
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            // 若是不是UI線程則拋出異常
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }   
    
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // postSyncBarrier方法,被稱爲同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    } 
    
複製代碼

MessageQueue#postSyncBarrier方法,被稱爲同步屏障,在這裏主要爲了避免影響主線程UI的繪製。同步屏障能夠理解爲:在MessageQueue中添加一個特殊的msg,將這個msg做爲一個標記,在這個標記被移除以前,當前MessageQueue隊列中排在它後面的其它(非async:即同步) 的message不會被handler處理。所以此處的代碼繼續向下執行postCallback,咱們來看看這個方法裏幹了些什麼:

// Choreographer類:Choreographer就是負責獲取Vsync同步信號並控制App線程(主線程)完成圖像繪製的類。
    public void postCallback(int callbackType, Runnable action, Object token) {
        // 傳遞的參數 delayMillis 爲 0
        postCallbackDelayed(callbackType, action, token, 0);
    }
    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        ...
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }
    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        ...
        synchronized (mLock) {
            // 從開機到如今的毫秒數(手機睡眠的時間不包括在內)
            final long now = SystemClock.uptimeMillis();
            // 從上面方法可知,delayMillis == 0
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) { //  true
                // 執行該方法,從方法名能夠看出此方法中處理跟幀相關的邏輯
                scheduleFrameLocked(now);
            } else {
                // 異步回調延遲執行
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }
    
    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            // 是否容許動畫和繪製的垂直同步,默認是爲true
            if (USE_VSYNC) {
                ...
                // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                // 從上面註釋中可知,若是運行在Looper線程,即主線程,View繪製走到這基本就是主線程
                if (isRunningOnLooperThreadLocked()) { // true
                    // 執行該方法
                    scheduleVsyncLocked();
                } else {
                    // 切換到主線程,調度vsync
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                // 若是沒有VSYNC的同步,則發送消息刷新畫面
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                if (DEBUG) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }
    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }
// DisplayEventReceiver類:
    public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            ...
        } else {
            nativeScheduleVsync(mReceiverPtr);
        }
    }
    
複製代碼

如今跟隨源碼到了mDisplayEventReceiver.scheduleVsync();方法,而mDisplayEventReceiver是何時初始化的呢,經過源碼能夠看到是在Choreographer類初始化時被new出來的,那Choreographer類是何時初始化的,再經過源碼能夠看到是ViewRootImpl類初始化時被new出來的。

public ViewRootImpl(Context context, Display display) {
        ...
        mChoreographer = Choreographer.getInstance();
        ...
    }
// Choreographer類:Choreographer就是負責獲取Vsync同步信號並控制App線程(主線程)完成圖像繪製的類。
    //每一個線程一個Choreographer實例
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            return new Choreographer(looper);
        }
    };
    private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        //建立handle對象,用於處理消息,其looper爲當前的線程的消息隊列
        mHandler = new FrameHandler(looper);
        //建立VSYNC的信號接受對象 USE_VSYNC默認爲true
        mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
        //初始化上一次frame渲染的時間點
        mLastFrameTimeNanos = Long.MIN_VALUE;
        //計算幀率,也就是一幀所需的渲染時間,getRefreshRate是刷新率,通常是60
        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
        //建立消息處理隊列
        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
        for (int i = 0; i <= CALLBACK_LAST; i++) {
            mCallbackQueues[i] = new CallbackQueue();
        }
    }
// DisplayEventReceiver類:子類FrameDisplayEventReceiver
    public DisplayEventReceiver(Looper looper, int vsyncSource) {
        ...
        mMessageQueue = looper.getQueue();
        // //初始化native的消息隊列
        // 接受數量多少等於looper中消息的多少
        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
                vsyncSource);

        mCloseGuard.open("dispose");
    }
    
    // JNI--nativeInit方法(該方法定義在android_view_DisplayEventReceiver.cpp中)
    static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
            jobject messageQueueObj) {
        sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
        ...
        sp<NativeDisplayEventReceiver> receiver = new NativeDisplayEventReceiver(env,
                receiverWeak, messageQueue);
        status_t status = receiver->initialize();
        ...
        receiver->incStrong(gDisplayEventReceiverClassInfo.clazz); 
        // 把c++中NativeDisplayEventReceiver對象地址返回給Java層
        // 經過這種方式將Java層的對象與Native層的對象關聯在了一塊兒。
        return reinterpret_cast<jlong>(receiver.get());
    }
複製代碼

如今讓咱們回到mDisplayEventReceiver.scheduleVsync();方法中:

// DisplayEventReceiver類:
    public void scheduleVsync() {
        // 從上面分析可知,此時mReceiverPtr保存着
        // c++中NativeDisplayEventReceiver對象的地址
        // 從名字就知道此對象爲:原生顯示接收器(做用:請求VSYNC的同步)
        if (mReceiverPtr == 0) {
            ...
        } else {
            // 該方法也爲native方法
            // 傳入NativeDisplayEventReceiver對象的地址,請求VSYNC的同步
            nativeScheduleVsync(mReceiverPtr);
        }
    }
複製代碼

VSYNC的同步:其做用主要是讓顯卡的運算和顯示器刷新率一致以穩定輸出的畫面質量。VSYNC的同步具體如何用,具體如何,不是本文的重點,感興趣的小夥伴自行搜索吧。 咱們是執行mChoreographer.postCallback方法進入的JNI,所以其最終會回調到參數mTraversalRunnable(TraversalRunnable)的類方法內。想具體瞭解VSYNC的小夥伴不妨看看: dandanlove.com/2018/04/13/…

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            // 移除同步障礙
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();
            ...
        }
    }

複製代碼

從源碼中能夠看到TraversalRunnable類內,執行了方法doTraversal();而doTraversal裏先是移除了同步障礙,緊接着執行了performTraversals方法。

繪製流程三部曲

  1. 重量級方法來啦,performTraversals方法中,執行了咱們所熟知的Measure、Layout、Draw的方法:
private void performTraversals() {
        final View host = mView;// 這就是DecorView
        ...
        boolean newSurface = false;
        ...
        // 27源碼1901行
        relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
        ...
        ......
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    // 獲取根View的MeasureSpec的方法
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    ...
                     // 27源碼2167行
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    ...
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;

                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }

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

                    layoutRequested = true;
                }
            }
        }
        if (didLayout) {
            // 27源碼2212行
            performLayout(lp, mWidth, mHeight);
            ...
        }
        ......
       
        // 決定是否讓newSurface爲true,致使後邊是否讓performDraw沒法被調用,而是從新scheduleTraversals
        if (!hadSurface) {
            if (mSurface.isValid()) {
                // If we are creating a new surface, then we need to
                // completely redraw it.  Also, when we get to the
                // point of drawing it we will hold off and schedule
                // a new traversal instead.  This is so we can tell the
                // window manager about all of the windows being displayed
                // before actually drawing them, so it can display then
                // all at once.
                newSurface = true;
                        .....
            }
        }
        ......
        if (!cancelDraw && !newSurface) {
            // 27源碼2359行
            performDraw();
        } else {
            if (isViewVisible) {
                // 再執行一次 scheduleTraversals,也就是會再執行一次performTraversals
                scheduleTraversals();
            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

        mIsInTraversal = false;
    }

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

        case ViewGroup.LayoutParams.MATCH_PARENT:
            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;
    }
    
    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
        ...
        int relayoutResult = mWindowSession.relayout(
                mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f),
                viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame,
                mPendingMergedConfiguration, mSurface);// 將mSurface參數傳入

        ...
        return relayoutResult;
    }

複製代碼

performTraversals()方法先調用了relayoutWindow()方法,而relayoutWindow()方法中調用了IWindowSession的relayout()方法並傳入了mSurface,暫切記住這個方法,咱們放到最後再說。

接下來須要分3個模塊講解:

繪製流程三部曲之Measure

這裏有個重要的類MeasureSpec:在Measure流程中,系統會將View的LayoutParams根據父容器所施加的規則轉換成對應的MeasureSpec,而後在onMeasure方法中根據這個MeasureSpec來肯定View的測量寬高。它的高兩位用來表示模式SpecMode,低30位用來表示大小SpecSize。 SpecMode共有如下三種類型:

  1. UNSPECIFIED:父容器不做限制,子View想多大就多大,通常用於系統內部,如:ScrollView。
  2. EXACTLY:精確模式,父容器徹底決定子View的大小,當寬或高設爲肯定值時:即width=20dp,height=30dp,或者爲match_parent。
  3. AT_MOST:最大模式,大小不能大於SpecSize,也就是子View的大小有上限,對應於LayoutParams中的warp_content。看一看其部分源碼:
public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /**
          * UNSPECIFIED 模式:
          * 父View不對子View有任何限制,子View須要多大就多大
          */ 
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
          * EXACTYLY 模式:
          * 父View已經測量出子Viwe所須要的精確大小,這時候View的最終大小
          * 就是SpecSize所指定的值。對應於match_parent和精確數值這兩種模式
          */ 
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
          * AT_MOST 模式:
          * 子View的最終大小是父View指定的SpecSize值,而且子View的大小不能大於這個值,
          * 即對應wrap_content這種模式
          */ 
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        //將size和mode打包成一個32位的int型數值
        //高2位表示SpecMode,測量模式,低30位表示SpecSize,某種測量模式下的規格大小
        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        //將32位的MeasureSpec解包,返回SpecMode,測量模式
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

        //將32位的MeasureSpec解包,返回SpecSize,某種測量模式下的規格大小
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
        //...
    }
複製代碼

能夠看出,該類的思路是至關清晰的,對於每個View,包括DecorView,都持有一個MeasureSpec,而該MeasureSpec則保存了該View的尺寸規格。在View的測量流程中,經過makeMeasureSpec來將size和mode打包成一個32位的int型數值,在其餘流程經過getMode或getSize獲得模式和寬高。

咱們從新看回上面ViewRootImpl#getRootMeasureSpec方法的實現:根據不一樣的模式來設置MeasureSpec,若是是LayoutParams.MATCH_PARENT模式,則是窗口的大小,WRAP_CONTENT模式則是大小不肯定,可是不能超過窗口的大小等等。對於DecorView來講,它已是頂層view了,沒有父容器,所以DecorView的 MeasureSpec使用的是屏幕窗口的大小windowSize和DecorView的LayoutParams來確認MeasureSpec的。那麼到目前爲止,就已經得到了一份DecorView的MeasureSpec,它表明着根View的規格、尺寸,在接下來的measure流程中,就是根據已得到的根View的MeasureSpec來逐層測量各個子View。

接下來分析測量的邏輯:

// ViewRootImpl類
    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);
        }
    }
// View 類
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        // 回調onMeasure,在自定義View時也常常會重寫此方法
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
    }
    // 若不重寫此方法,系統會設置一個默認的大小給子View
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    // 不管是EXACTLY仍是AT_MOST,都按照測量結果進行設置。
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
    
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        // 保存到成員變量mMeasuredWidth和mMeasuredHeight中
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
複製代碼

根據源碼的追蹤,最終將測量的結果保存在本身的mMeasuredWidth和mMeasuredHeight成員變量中。ViewGroup的測量流程和此一致,只是其在onMeasure時須要測量子View。咱們看一看FrameLayout的onMeasure:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 獲取子View的個數
        int count = getChildCount();
        // 判斷當前佈局的寬高是不是match_parent模式或者指定一個精確的大
        // 若是是則置measureMatchParent爲false.
        // 由於若是是,則當前父控件則寬高大小已經肯定,不受子View的限制
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        // 清空須要測量match_parent寬或高的子視圖的集合
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
        // 遍歷子View,獲取修正本身的最大寬高
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                // 對child進行測量,方法裏child會調用本身的child.measure()方法測量本身
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                // 尋找子View中寬高的最大者,由於若是FrameLayout是wrap_content屬性
                // 那麼它的大小取決於子View中的最大者
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                // 若是FrameLayout是wrap_content模式,那麼往mMatchParentChildren中
                // 添加寬或者高爲match_parent的子View,
                // 由於該子View的最終測量大小會影響到FrameLayout的最終測量大小影響
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        // Account for padding too
        // 子View中的最大的寬高和自身的padding值都會影響到最終的大小
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Check against our minimum height and width
        // 根據有無背景來算出最大的寬高,getSuggestedMinimumHeight/Width下面有說明
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }
        // 從新給本身(FrameLayout)設置測量結果(保存測量結果)
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
        
        // 讀取須要再測量的子視圖數量(設置爲match_parent的子View)
        count = mMatchParentChildren.size();
        // 此處判斷必須大於1,若不大於1,則不會發生子View改變
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
                
                final int childWidthMeasureSpec;
                /**
                 * 若是子View的寬度是match_parent屬性,那麼對當前FrameLayout的MeasureSpec修改:
                 * 把widthMeasureSpec的寬度規格修改成:總寬度 - padding - margin,這樣作的意思是:
                 * 對於子Viw來講,若是要match_parent,那麼它能夠覆蓋的範圍是FrameLayout的測量寬度
                 * 減去padding和margin後剩下的空間。
                 *
                 * 如下兩點的結論,能夠查看getChildMeasureSpec()方法:
                 *
                 * 若是子View的寬度是一個肯定的值,好比50dp,那麼FrameLayout的widthMeasureSpec的寬度規格修改成:
                 * SpecSize爲子View的寬度,即50dp,SpecMode爲EXACTLY模式
                 * 
                 * 若是子View的寬度是wrap_content屬性,那麼FrameLayout的widthMeasureSpec的寬度規格修改成:
                 * SpecSize爲子View的寬度減去padding減去margin,SpecMode爲AT_MOST模式
                */
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }
                
                final int childHeightMeasureSpec;
                // 同理對高度進行相同的處理
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
                // 這部分的子View須要從新進行measure過程
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }
    
    // getSuggestedMinimumHeight同理
    protected int getSuggestedMinimumWidth() {
        //若是沒有給View設置背景,那麼就返回View自己的最小寬度mMinWidth
        //若是給View設置了背景,那麼就取View自己最小寬度mMinWidth和背景的最小寬度的最大值
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
複製代碼

總結一下這段代碼:首先,FrameLayout根據它的MeasureSpec來對每個子View進行測量,即調用measureChildWithMargin方法;對於每個測量完成的子View,會尋找其中最大的寬高,那麼FrameLayout的測量寬高會受到這個子View的最大寬高的影響(wrap_content模式),接着調用setMeasureDimension方法,把FrameLayout的測量寬高保存。最後則是特殊狀況的處理,即當FrameLayout爲wrap_content屬性時,若是其子View是match_parent屬性的話,則要從新設置FrameLayout的測量規格,而後從新對該部分View測量。咱們看看它是如何測量child的,代碼以下:

// ViewGroup類
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        // 子View的LayoutParams,你在xml的layout_width和layout_height,
        // layout_xxx的值最後都會封裝到這個個LayoutParams。
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
複製代碼

由源碼可知,裏面調用了getChildMeasureSpec方法,把父容器的MeasureSpec以及自身的layoutParams屬性傳遞進去來獲取子View的MeasureSpec,這也印證了「子View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定」這個結論。如今咱們看下這個getChildMeasureSpec的實現:

// ViewGroup類
    // spec爲父View的MeasureSpec
    // padding爲父View在相應方向的已用尺寸加上父View的padding和子View的margin
    // childDimension爲子View的LayoutParams的值
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        // size表示子View可用空間:父容器尺寸減去padding
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                // 表示子View的LayoutParams指定了具體大小值(xx dp)
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 子View想和父View同樣大
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 子View想本身決定其尺寸,但不能比父View大
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
複製代碼

這裏根據ViewGroup的SpecMode不一樣,給child設置了不一樣的模式和大小,以保證child能正確完成測量的過程。如今咱們返回measureChildWithMargins方法,接着就會執行child.measure方法,此時若是child仍是ViewGroup,則依舊會走一遍上面舉的Fragment的onMeasure流程,直到child爲單純的View爲止。這就回到了更上面講的View的measure方法啦。

咱們來個關於MeasureSpec的例子總結:(純屬我的理解,如有誤之處,敬請指正)

  1. 先明確一個概念,如今有三種佈局,分別爲:DecorView、FrameLayout、若干childView
  2. MeasureSpec的最初來源是DecorView,使用屏幕窗口的大小windowSize和DecorView的LayoutParams來確認firstWidthMeasureSpec和firstHeightMeasureSpec的,所以DecorView的大小一開始就是確認的
  3. DecorView根佈局,FrameLayout爲DecorView的子View,若干childView爲FrameLayout子View
  4. 每一個子View在measure本身前都會先根據父佈局給的widthMeasureSpec和heightMeasureSpec以及padding和margin、本身的寬高來計算出適合本身的widthMeasureSpec和heightMeasureSpec
  5. 若FrameLayout的寬高都爲match_parent,則表明FrameLayout的大小和DecorView同樣大,則DecorView和FrameLayout的widthMeasureSpec和heightMeasureSpec同樣。
  6. 若FrameLayout的寬高都爲wrap_content或者有同樣爲wrap_content,則FrameLayout的大小就不必定和DecorView同樣,根據源碼ViewGroup的getChildMeasureSpec方法可知,wrap_content狀況的佈局寬高大小等於父佈局寬高(DecorView)-自身padding,因此此時二者的widthMeasureSpec和heightMeasureSpec是不同的,至少mode不一致。假設此時沒有設置padding以及FrameLayout寬爲match_paren高爲wrap_content,所以此時FrameLayout的寬高會和屏幕大小一致,但heightMeasureSpec模式會爲AT_MOST。
  7. 根據第5條的分析(即同上面狀況一致),此時的FrameLayout的大小不肯定,而此時須要先測量FrameLayout的子View(若干childView),而且此時將使用size大小和屏幕大小一致但mode模式爲AT_MOST的widthMeasureSpec和heightMeasureSpec傳入childView中。
  8. childView中根據第6步傳入的widthMeasureSpec和heightMeasureSpec生成的本身的widthMeasureSpec和heightMeasureSpec,此時測量出來的各個childView的大小。
  9. FrameLayout在childView測量完成後須要根據若干childView中的最大寬高和自身的padding值以及有無背景圖引發的大小來設置自身最終的寬高。
  10. 若在衆多childView中存在寬高爲match_parent的View時,則須要對這部分View從新測量,由於上一次測量是使用size大小和屏幕大小一致但mode模式不一致的widthMeasureSpec和heightMeasureSpec,而如今最終的FrameLayout的大小已經肯定,所以FrameLayout的widthMeasureSpec和heightMeasureSpec已經發生改變(mode發生改變),所以須要遍歷這部分childView(這部分子View大小可能會因FrameLayout的高固定而改變本身的大小),因爲第8步已經知道了父佈局的精確大小,因此此時只須要根據每一個childView的容許的最大寬或高和MeasureSpec.EXACTLY造成適合每一個childView的MeasureSpec值,而後從新measure這部分childView。
  11. 若干childView在測量本身前也須要先結合參數中給出的widthMeasureSpec和heightMeasureSpec,以及padding、margin、本身的寬高計算出適合本身的MeasureSpec值,而後傳出measure中,最後在onMeasure中進一步測量。
  12. 就是因爲會出現第9步的情形,所以View有時會屢次onMeasure。
// 該佈局中FrameLayout有兩個TextView,都帶有match_parent的屬性
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    tools:context=".MainActivity">
    
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:text="Hello World!Hello World" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Hello World!" />
</FrameLayout>

// TextView類:
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;

        ......
        if (heightMode == MeasureSpec.EXACTLY) {
            // Parent has told us how big to be. So be it.
            height = heightSize;
            mDesiredHeightAtMeasure = -1;
        } else {
            // 獲取所需高度
            int desired = getDesiredHeight();
            height = desired;
            ...
        }
        ...

        setMeasuredDimension(width, height);
    }
複製代碼

上述佈局繪製的效果圖,注:兩個TextView高是同樣的(不信能夠本身試試) 說明:該狀況符合上述說的第5步,而通過第6和第7步測量後,heightMeasureSpec的mode爲AT_MOST,兩個TextView的高度根據源碼(感興趣)來看是不一致的,雖然二者的measure以前的heightMeasureSpec一致,可是最終測量出來的高度不一樣,可將第一個TextView去掉可顯示出,第二個TextView第一次測量後的高度,而FrameLayout肯定高度後heightMeasureSpec的mode爲EXACTLY,從代碼中可看出height被賦值爲FrameLayout高度-padding值,因爲無padding,則和FrameLayout高度一致。

疑問:不過上述代碼中爲何要判斷match_parent的子View必須大於1,才從新測量子View? 將第一個TextView寬和高都設置爲wrap_content,果然第二個TextView高度會變爲一行高度。

繪製流程三部曲之Layout

接下來看一看View的onLayout佈局,layout的主要做用:根據子視圖的大小以及佈局參數將View樹放到合適的位置上。此時回到ViewRootImpl類的performLayout方法:

// ViewRootImpl類
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        ...
        try {
            // 先調用mView的layout方法
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            mInLayout = false;
            // 請求從新佈局的集合(繪製流程通常爲空)
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
                // 獲取到須要進行layout的View的個數
                // requestLayout() was called during layout.
                // If no layout-request flags are set on the requesting views, there is no problem.
                // If some requests are still pending, then we need to clear those flags and do
                // a full request/measure/layout pass to handle this situation.
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,false);
                if (validLayoutRequesters != null) {
                    
                    mHandlingLayoutInLayoutRequest = true;

                    // Process fresh layout requests, then measure and layout
                    int numValidRequests = validLayoutRequesters.size();
                    for (int i = 0; i < numValidRequests; ++i) {
                        final View view = validLayoutRequesters.get(i);
                        // 調用它們的requestLayout方法,
                        view.requestLayout();
                    }
                    // 再次進行測量
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    // 從新layout
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    mHandlingLayoutInLayoutRequest = false;

                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;
                        // 再次檢查是否仍有須要layout的View,若是有,就到下一幀再繼續
                        getRunQueue().post(new Runnable() {
                            @Override
                            public void run() {
                                int numValidRequests = finalRequesters.size();
                                for (int i = 0; i < numValidRequests; ++i) {
                                    final View view = finalRequesters.get(i);
                                    Log.w("View", "requestLayout() improperly called by " + view +
                                            " during second layout pass: posting in next frame");
                                    view.requestLayout();
                                }
                            }
                        });
                    }
                }
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }
    
    boolean requestLayoutDuringLayout(final View view) {
        if (view.mParent == null || view.mAttachInfo == null) {
            // Would not normally trigger another layout, so just let it pass through as usual
            return true;
        }
        if (!mLayoutRequesters.contains(view)) {
            mLayoutRequesters.add(view);
        }
        ...
    }
    
// View類:
    @CallSuper
    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }
        ...
    }
複製代碼

performLayout方法中首先調用了View的layout方法佈局控件,然後出現了一個集合mLayoutRequesters,從源碼中可知mLayoutRequesters集合數據是在requestLayoutDuringLayout方法中添加的,而requestLayoutDuringLayout方法確是被View中的requestLayout方法調用。View類中的requestLayout方法和invalidate方法主要用於自定義View。

  1. requestLayout方法會致使View的onMeasure、onLayout、onDraw方法被調用;
  2. invalidate方法則只會致使View的onDraw方法被調用

所以,如今這種狀況代碼不會走到if (numViewsRequestingLayout > 0) 判斷內,因此只須要看View的layout方法便可。

// View類:
    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;
        // 判斷view和和其父view的佈局模式狀況,若二者不一樣步,則進行子view的size大小的修改
        // 即有兩種狀況會進入到該if條件:
        // 一是子view有特殊的光學邊界,而父view沒有,此時optical爲true,
        // 另外一種是父view有一個特殊的光學邊界,而子view沒有,此時optical爲false
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            // 回調onLayout()方法
            onLayout(changed, l, t, r, b);
            ...
        }
        ...
    }
    
    public static boolean isLayoutModeOptical(Object o) {
        return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
    }
// ViewGroup類:
    private int mLayoutMode = LAYOUT_MODE_UNDEFINED;
    
    boolean isLayoutModeOptical() {
        return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;
    }
複製代碼

mLayoutMode的值默認是LAYOUT_MODE_UNDEFINED,也就是說:isLayoutModeOptical(mParent)返回false,因此會調用setFrame方法,並把四個位置信息傳遞進去,這個方法用於肯定View的四個頂點的位置,即初始化mLeft,mRight,mTop,mBottom這四個值,當初始化完畢後,ViewGroup的佈局流程也就完成了。 接下來layout方法裏會回調onLayout()方法,該方法在ViewGroup中調用,用於肯定子View的位置,即在該方法內部,子View會調用自身的layout方法來進一步完成自身的佈局流程。因爲上面Measure是對FrameLayout進行分析,則如今Layout也使用FrameLayout分析:

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        //把父容器的位置參數傳遞進去
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();
        // 如下四個值會影響到子View的佈局參數
        // parentLeft由父容器的padding和Foreground決定
        final int parentLeft = getPaddingLeftWithForeground();
        // parentRight由父容器的width和padding和Foreground決定
        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                // 獲取子View的測量寬高
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
                //當子View設置了水平方向的layout_gravity屬性時,根據不一樣的屬性設置不一樣的childLeft
                //childLeft表示子View的 左上角座標X值
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                /* 水平居中,因爲子View要在水平中間的位置顯示,所以,要先計算出如下:
                 * (parentRight - parentLeft -width)/2 此時得出的是父容器減去子View寬度後的
                 * 剩餘空間的一半,那麼再加上parentLeft後,就是子View初始左上角橫座標(此時正好位於中間位置),
                 * 假如子View還受到margin約束,因爲leftMargin使子View右偏而rightMargin使子View左偏,因此最後
                 * 是 +leftMargin -rightMargin .
                 */
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    //水平居右,子View左上角橫座標等於 parentRight 減去子View的測量寬度 減去 margin
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    //若是沒設置水平方向的layout_gravity,那麼它默認是水平居左
                    //水平居左,子View的左上角橫座標等於 parentLeft 加上子View的magin值
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }
                //當子View設置了豎直方向的layout_gravity時,根據不一樣的屬性設置同的childTop
                //childTop表示子View的 左上角座標的Y值
                //分析方法同上
                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }
                //對子元素進行佈局,左上角座標爲(childLeft,childTop),右下角座標爲(childLeft+width,childTop+height)
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }
複製代碼

onLayout方法內部直接調用了layoutChildren方法,而layoutChildren則是具體的實現。首先獲取父容器的padding值,而後遍歷其每個子View,根據子View的layout_gravity屬性、子View的測量寬高、父容器的padding值、來肯定子View的佈局參數,而後調用child.layout方法,把佈局流程從父容器傳遞到子元素。若是子View是一個ViewGroup,那麼就會重複以上步驟,若是是一個View,那麼會直接調用View#layout方法。

繪製流程三部曲之Draw

如今就剩下最後的Draw啦,咱們繼續回到ViewRootImpl中看performDraw方法:

// ViewRootImpl類:
    private void performDraw() {
        ...
        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        ...
        if (mReportNextDraw) {
            ...
            try {
                mWindowSession.finishDrawing(mWindow);
            } catch (RemoteException e) {
            }
        }
    }
複製代碼

裏面又調用了ViewRootImpl#draw方法,並傳遞了fullRedrawNeeded參數,而該參數由mFullRedrawNeeded成員變量獲取,它的做用是判斷是否須要從新繪製所有視圖,若是是第一次繪製視圖,那麼顯然應該繪製因此的視圖,若是因爲某些緣由,致使了視圖重繪,那麼就沒有必要繪製全部視圖。

// ViewRootImpl類:
    private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        ...
        if (!sFirstDrawComplete) {
            synchronized (sFirstDrawHandlers) {
                sFirstDrawComplete = true;
                final int count = sFirstDrawHandlers.size();
                for (int i = 0; i< count; i++) {
                    mHandler.post(sFirstDrawHandlers.get(i));
                }
            }
        }
        // 滑動到指定區域
        scrollToRectOrFocus(null, false);
        // 分發OnScrollChanged事件
        if (mAttachInfo.mViewScrollChanged) {
            mAttachInfo.mViewScrollChanged = false;
            mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
        }

        boolean animating = mScroller != null && mScroller.computeScrollOffset();
        final int curScrollY;
        if (animating) {
            curScrollY = mScroller.getCurrY();
        } else {
            curScrollY = mScrollY;
        }
        // RootView滑動回調
        if (mCurScrollY != curScrollY) {
            mCurScrollY = curScrollY;
            fullRedrawNeeded = true;
            if (mView instanceof RootViewSurfaceTaker) {
                ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
            }
        }

        final float appScale = mAttachInfo.mApplicationScale;
        final boolean scalingRequired = mAttachInfo.mScalingRequired;

        int resizeAlpha = 0;
        // 獲取須要繪製的區域
        final Rect dirty = mDirty;
        if (mSurfaceHolder != null) {
            dirty.setEmpty();
            if (animating && mScroller != null) {
                mScroller.abortAnimation();
            }
            return;
        }

        //若是fullRedrawNeeded爲真,則把dirty區域置爲整個屏幕,表示整個視圖都須要繪製
        //第一次繪製流程,須要繪製全部視圖
        if (fullRedrawNeeded) {
            mAttachInfo.mIgnoreDirtyState = true;
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        }
        ...
        // 分發onDraw
        mAttachInfo.mTreeObserver.dispatchOnDraw();

        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                //
            } else {
                //
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }
        ...
    }
複製代碼

首先滑動到指定區域,而後獲取了mDirty值,該值保存了須要重繪的區域的信息,接着根據fullRedrawNeeded來判斷是否須要重置dirty區域,最後調用了ViewRootImpl.drawSoftware方法,並把相關參數傳遞進去,包括dirty區域。

// ViewRootImpl類:
    final Surface mSurface = new Surface(); // 成員變量
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo
        , int xoff, int yoff,boolean scalingRequired, Rect dirty) {

        // Draw with software renderer.
        final Canvas canvas;
        try {
            final int left = dirty.left;
            final int top = dirty.top;
            final int right = dirty.right;
            final int bottom = dirty.bottom;
            // 從mSurface中鎖定一塊Canvas區域,由dirty區域決定
            canvas = mSurface.lockCanvas(dirty);

            // The dirty rectangle can be modified by Surface.lockCanvas()
            //noinspection ConstantConditions
            if (left != dirty.left || top != dirty.top || right != dirty.right
                    || bottom != dirty.bottom) {
                attachInfo.mIgnoreDirtyState = true;
            }

            // TODO: Do this in native
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return false;
        } catch (IllegalArgumentException e) {
            ...
            mLayoutRequested = true;    // ask wm for a new surface next time.
            return false;
        }

        try {
            ...
            try {
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;
                // 正式開始繪製
                mView.draw(canvas);

                drawAccessibilityFocusedDrawableIfNeeded(canvas);
            } finally {
                ...
            }
        } finally {
            try {
                // 解鎖Canvas,屏幕上立刻就回出現繪製的畫面長相
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                ...
            }
            ...
        }
        return true;
    }
複製代碼

首先是實例化了Canvas對象,而後從mSurface鎖定一塊Canvas的區域,由dirty區域決定,接着對canvas進行一系列的屬性賦值,而後調用了mView.draw(canvas)方法繪製,最後解鎖顯示畫面。咱們在上面「onResume界面可見繪製之同步屏障--VSYNC同步」部分提到了Surface和SurfaceSession兩個概念。而從ViewRootImpl.drawSoftware方法中也看到了Surface的身影。 如今來總結一下目前出現的和Surface有關的身影:

  • 在ViewRootImpl初始化(構造時),會建立一個Surface,上面代碼可見,直接在成員變量new出來的。
  • ViewRootImpl經過IWindowSession和WMS交互,而WMS調用的一個attahc方法會構造一個SurfaceSession,忘記的小夥伴請回看「onResume界面可見繪製之IWindowSession和IWindow」部分。
  • ViewRootImpl在performTransval的處理過程當中會調用IWindowSession的relayout方法,回看「繪製流程三部曲」部分。
  • ViewRootImpl.drawSoftware方法中調用Surface的lockCanvas方法,獲得一塊畫布
  • ViewRootImpl.drawSoftware方法中調用Surface的unlockCanvasAndPost方法,釋放這塊畫布

如今經過跟Surface有關的身影來講一下Surface,先來一張精簡流程圖:

圖中出現了幾個新類,主要看一下SurfaceComposerClient如何獲得的,上面說到SurfaceSession類的初始化,回看「onResume界面可見繪製之IWindowSession和IWindow」

// SurfaceSession類:
    public SurfaceSession() {
        // 會調用native本地方法
        mNativeClient = nativeCreate();
    }
    
// android_view_SurfaceSession.cpp文件
    static jlong nativeCreate(JNIEnv* env, jclass clazz) {
    // 初始化SurfaceComposerClient,該對象會和SurfaceFlinger交互
    SurfaceComposerClient* client = new SurfaceComposerClient();
    client->incStrong((void*)nativeCreate);
    return reinterpret_cast<jlong>(client);
}
複製代碼

經過精簡流程圖和上述代碼,咱們能夠發現一下幾點:

  1. SurfaceComposerClient是在初始化SurfaceSession時才顯示初始化的
  2. 在「onResume界面可見繪製之IWindowSession和IWindow」裏咱們講解ViewRootImpl類中setView方法時,能夠看到requestLayout()在mWindowSession.addToDisplay()以前調用,而且上面也說明了SurfaceSession初始化時機是在mWindowSession.addToDisplay中
  3. 全部三部曲的邏輯都是由requestLayout()而來
  4. 精簡流程圖上顯示須要SurfaceComposerClient,纔會有後面的mSurface.lockCanvas和mSurface.unlockCanvasAndPost

那麼是什麼讓在走三部曲邏輯前,使得SurfaceSession初始化的呢?

其實,這個問題的答案就在requestLayout()裏,咱們上面「onResume界面可見繪製之同步屏障--VSYNC同步」講到同步障礙:mHandler.getLooper().getQueue().postSyncBarrier(),就是由於這個,其發送了一個同步障礙消息後,會阻止消息隊列中其它消息的執行,可是並不會中止代碼的執行,此時代碼會先越過requestLayout()繼續向下走,此時就會初始化SurfaceSession...,等Looper輪詢到同步障礙消息後繼續走,直到回調mTraversalRunnable裏的doTraversal()方法。這就相似於handler.post裏代碼和普通主線程代碼:

// 主線程中:
    Handler handler = new Handler();
    handler.post(new Runnable() {
        @Override
        public void run() {
            Log.e("handler.post","handler.post:111111111111111111111111111111");
        }
    });
    for(int i = 0; i <10000; i++){
        Log.e("handler.post","handler.post:"+i);
    }
    
//打印結果爲:
    ......
    handler.post:9998
    handler.post:9999
    04-18 21:29:26.521 6249-6249/com.android.sourcecodeanalysis E/handler.post: handler.post:111111111111111111111111111111
複製代碼

所以來簡單總結一下Surface:整個Activity的繪圖流程就是從mSurface中lock一塊Canvas,而後交給mView去自由發揮畫畫才能,最後unlockCanvasAndPost釋放這塊Canvas。 因爲Surface這個系統很是複雜,包含Layer(顯示層)、FrameBuffer(幀緩衝)、PageFlipping(畫面交換:FrontBuffer前一幀緩衝畫面和BackBuffer後一幀緩衝畫面)、SurfaceFlinger(圖像混合:多個顯示層混合一塊兒顯示畫面)以及Linux共享內存等知識點,這裏就再也不深究,有興趣的小夥伴,能夠參考「深刻理解Android卷1(鄧凡平)」一書第8章Surface系統,去進一步研究(切記閱讀源碼需謹慎,c/c++自我感受很不錯的小夥伴能夠嘗試深刻一下)。

接着看咱們最後的draw的邏輯:

// View類:
 public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */

    // Step 1, draw the background, if needed
    int saveCount;

    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);
        
        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
        
        // we're done... return; } ... } 複製代碼

源碼中已經給出了draw的步驟:

  1. 對View的背景進行繪製
  2. 保存當前的圖層信息(可跳過)
  3. 繪製View的內容
  4. 對View的子View進行繪製(若是有子View)
  5. 繪製View的褪色的邊緣,相似於陰影效果(可跳過)
  6. 繪製View的裝飾(例如:滾動條) 源碼中提示,其中第2步和第5步是能夠跳過的,是常見的狀況。
// View類:
    // 繪製背景
    private void drawBackground(Canvas canvas) {
        // mBackground是該View的背景參數,好比背景顏色
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }
        // 根據View四個佈局參數來肯定背景的邊界
        setBackgroundBounds();
        ...
        // 獲取當前View的mScrollX和mScrollY值
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        // 考慮到了view的偏移參數:scrollX和scrollY
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            // 若是scrollX和scrollY有值,則對canvas的座標進行偏移,再繪製背景
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }
    
    protected void onDraw(Canvas canvas) {
        // 該方法是一個空實現,由於不一樣的View有着不一樣的內容,須要本身去實現
        // 即在自定義View中重寫該方法來實現。
    }
    // View中無子佈局,全部dispatchDraw爲空實現,該方法主要針對ViewGroup有子View狀況
    protected void dispatchDraw(Canvas canvas) {

    }
複製代碼

若是該View是一個ViewGroup,則其繪製調用dispatchDraw(canvas);

// ViewGroup類:
protected void dispatchDraw(Canvas canvas) {
    boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;
    
    // 遍歷了因此子View
    for (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            final View transientChild = mTransientViews.get(transientIndex);
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            transientIndex++;
            if (transientIndex >= transientCount) {
                transientIndex = -1;
            }
        }
        int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
        final View child = (preorderedList == null)
                ? children[childIndex] : preorderedList.get(childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    ......
}
複製代碼

能夠看到dispatchDraw方法中會遍歷全部子View,而且調用drawChild方法:

// ViewGroup類:
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        // 這裏調用了View的draw方法,但這個方法並非上面所說的,由於參數不一樣
        return child.draw(canvas, this, drawingTime);
    }
    
// View類:
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ......
        if (!drawingWithDrawingCache) {
            if (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            } else {
                // Fast path for layouts with no backgrounds
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else {
                    draw(canvas);
                }
            }
        } else if (cache != null) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            if (layerType == LAYER_TYPE_NONE) {
                // no layer paint, use temporary paint to draw bitmap
                Paint cachePaint = parent.mCachePaint;
                if (cachePaint == null) {
                    cachePaint = new Paint();
                    cachePaint.setDither(false);
                    parent.mCachePaint = cachePaint;
                }
                cachePaint.setAlpha((int) (alpha * 255));
                canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
            } else {
                // use layer paint to draw the bitmap, merging the two alphas, but also restore
                int layerPaintAlpha = mLayerPaint.getAlpha();
                mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                mLayerPaint.setAlpha(layerPaintAlpha);
            }
        }
    }
複製代碼

首先判斷是否已經有緩存,即以前是否已經繪製過一次了,若是沒有,則會調用draw(canvas)方法,開始正常的繪製,即上面所說的六個步驟,不然利用緩存來顯示。ViewGroup繪製過程:dispatchDraw遍歷繪製子View,若子View依舊爲ViewGroup則接着dispatchDraw遍歷繪製,直到不是ViewGroup爲止。 最後就只剩下了前景繪製(onDrawForeground):所謂的前景繪製,就是指View除了背景、內容、子View的其他部分,例如滾動條等.

// View類:
    public void onDrawForeground(Canvas canvas) {
        onDrawScrollIndicators(canvas);
        onDrawScrollBars(canvas);

        final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
        if (foreground != null) {
            if (mForegroundInfo.mBoundsChanged) {
                mForegroundInfo.mBoundsChanged = false;
                final Rect selfBounds = mForegroundInfo.mSelfBounds;
                final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

                if (mForegroundInfo.mInsidePadding) {
                    selfBounds.set(0, 0, getWidth(), getHeight());
                } else {
                    selfBounds.set(getPaddingLeft(), getPaddingTop(),
                            getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
                }

                final int ld = getLayoutDirection();
                Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                        foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
                foreground.setBounds(overlayBounds);
            }

            foreground.draw(canvas);
        }
    }
複製代碼

和通常的繪製流程很是類似,都是先設定繪製區域,而後利用canvas進行繪製。

至此,View總體繪製流程完成。

說下感覺,此篇文章前先後後準備加寫,用了5天空閒時間,最大的收穫並非只是知道了View的繪製流程,而是理解了自定義View何時須要調用什麼方法和爲何這樣調用的原理,而且之前有些看不懂的地方,忽然感受豁然開朗。雖然過程很辛苦,而且熬了幾個晚上,可是感受都是值得的。也許沒有幾我的能看完整篇,但仍是但願看的小夥伴能在此文章中收穫一點,一點點也是好的。最後告誡本身一句:深刻源碼需謹慎,能力不是很足時,不要太過深刻,不然會迷失在裏面。

參考連接:

www.jianshu.com/p/a13e3a325…

www.jianshu.com/p/3299c3de0…

www.jianshu.com/p/dc6eeeb73…

blog.csdn.net/freekiteyu/…

深刻理解Android卷1(鄧凡平)

...

若以爲文章不錯或者不足之處,歡迎點贊留言,謝謝

相關文章
相關標籤/搜索