1. 程式人生 > >自定義View基礎(一)——追根溯源,透過原始碼認識ViewRoot,DecorView和performTraversals方法

自定義View基礎(一)——追根溯源,透過原始碼認識ViewRoot,DecorView和performTraversals方法

關於自定義View,可能會常常被我們所熟知,我們知道它的onMeasure(),onLayout()以及onDraw()方法,我們知道要用invalidate()使View進行重繪,呼叫requestLayout()會讓這個View重新測量、佈局。但是,我們有沒有想過,這些方法的內部到底是一個怎樣的流程?它們的內部到底又做了哪些工作?關於這樣的疑問還有很多,接下來,我會慢慢探究。

想要對View的measure,layout,draw這三個過程有一個更深的瞭解,我們首先得知道這三個過程是在哪裡呼叫的,所以,有必要好好介紹一下ViewRoot以及DecorView這兩個不常見的概念

一、關於ViewRoot

1. ViewRoot是什麼?

開啟Android原始碼,搜尋ViewRoot,我們並沒有看到與ViewRoot完全相對應的類,但是我們發現ViewRootImpl這個類,通過命名方式我們也能知道,這是ViewRoot的實現類,也就是我們要看的東西,那麼接下來,我們就看看ViewRootImpl這個類中,到底有哪些需要我們注意的東西呢?

2.ViewRootImpl是什麼?

  The top of a view hierarchy, implementing the needed protocol between View
  and the WindowManager.  This is for the most part an internal implementation
  detail of {@link WindowManagerGlobal}.

這是原始碼中對於ViewRootImpl類的介紹,也就是說,這個類是連線View和WindowManager的紐帶。我們可以認為,正是ViewRootImpl讓View得以正確的顯示出來。在ActivityThread中,也就是說Activity被建立完成,這個時候View,WindowManager的關係在Activity建立的過程中已經被關聯上了,那麼View,WindowManager是怎麼建立關聯的呢?ViewRootImpl這個類到底又做了哪些工作呢?

二、WindowManager,Window,View的理解

1.Window建立的過程

ActivityThread類中performLaunchActivity()方法會創建出Activity的例項,並呼叫onAttach()方法,如下:

ActivityThread.performLaunchActivity()

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    //創建出activity的例項
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    ...
    //呼叫Activity.attach(),對Window以及WindowManager做初始化工作
    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);
    ...
}

分析:performLaunchActivity執行的時候,會創建出對應的activity,之後執行activity的attach()方法,如下:

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) {
            ...
            //建立了一個PhoneWindow物件
            mWindow = new PhoneWindow(this, window);
            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());
            }
            mWindowManager = mWindow.getWindowManager();
            ...
}

分析:在activity的attach方法中,會創建出一個PhoneWindow物件,PhoneWindow是Window的具體實現類,之後,通過setWindowManager方法為mWindow 設定了WindowManager物件,每個activity都會有一個PhoneWindow物件和WindowManager物件。

2.WindowManager建立的過程

Window.setWindowManager()

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
            ...
            mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
            ...
}

分析:得到mWindowManager ,而這個mWindowManager就是和WindowManagerService(WMS)進行通訊,也是WMS識別View具體屬於那個Activity的關鍵,建立時傳入IBinder 型別的mToken。這個mToken是一個IBinder,WMS就是通過這個IBinder來管理Activity裡的View。

3.DecorView建立的過程

接下來,Activity在建立的時候執行onCreate()方法,呼叫setContentView()方法填充佈局,setContentView方法程式碼如下:

Activity.setContentView()

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

分析:setContentView方法中,有getWindow()獲取到Window物件並執行setContentView(),繼續檢視PhoneWindow中的setContentView,程式碼如下:

Activity.setContentView()

public void setContentView(int layoutResID) {
        ...
        if (mContentParent == null) {
            installDecor();
        } 
        ...
}

而installDecor()根據不同的Theme,建立不同的DecorView,DecorView是一個FrameLayout 。至此,PhoneWindow和DecorView兩部分都已經建立好,但兩者之間並沒有任何的聯絡。

4.DecorView與PhoneWindow的關聯

ActivityThread在執行完performLaunchActivity()之後,PhoneWindow和DecorView都已經被建立完成,但不存在聯絡,繼續執行ActivityThread中的handleResumeActivity(),檢視原始碼,如下:
ActivityThread.handleResumeActivity()

 final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
                 //獲得當前Activity的PhoneWindow物件
                r.window = r.activity.getWindow();
                 //獲得當前phoneWindow內部類DecorView物件
                View decor = r.window.getDecorView();
                //設定視窗頂層檢視DecorView可見度
                decor.setVisibility(View.INVISIBLE);
                 //得當當前Activity的WindowManagerImpl物件
                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 && !a.mWindowAdded) {
                    //標記根佈局DecorView已經新增到視窗
                    a.mWindowAdded = true;
                    //將根佈局DecorView新增到當前Activity的視窗上面
                    wm.addView(decor, l);
                }
            } 
        ...
}

分析:主要的解釋清參見注釋,接下來wm.addView()方法,wm應該檢視其實現類WindowManagerImpl類的addView()方法,程式碼如下:

WindowManagerImpl.addView()

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

分析:繼續跟蹤程式碼,mGlobal.addView(),原始碼如下:

WindowManagerGlobal.addView()

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;
        ...
        //獲得ViewRootImpl物件root
         root = new ViewRootImpl(view.getContext(), display);
        ...
        // do this last because it fires off messages to start doing things
        try {
            //將傳進來的引數DecorView設定到root中
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
        ...
        }
}

分析:在這裡,我們終於又見到了一個前文熟悉的類ViewRootImpl ,在這裡,他被new了出來,之後呼叫了setView()方法,並把decorView作為引數傳入,繼續跟蹤,看ViewRootImpl 的setView()方法,原始碼如下:

ViewRootImpl .setView()

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
            //將頂層檢視DecorView賦值給全域性的mView
                mView = view;
            ...
            //標記已新增DecorView
             mAdded = true;
            ...
            //請求佈局
            requestLayout();
            ...     
        }
 }

分析:在這裡我們看到了requestLayout()方法,但是要注意的是,這個方法跟我們平時自定義控制元件時候所使用的requestLayout()是不同的方法,避免混淆,繼續檢視原始碼如下:

ViewRootImpl .requestLayout()

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

ViewRootImpl .scheduleTraversals()

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

ViewRootImpl .TraversalRunnable

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

ViewRootImpl .doTraversal()

 void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

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

            performTraversals();

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

分析:峰迴路轉,終於看到了我們要找的方法performTraversals(),淚目。。。。。。。。

5.關於performTraversals()

ViewRootImpl .performTraversals()

private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
        if (host == null || !mAdded)
            return;
        //是否正在遍歷
        mIsInTraversal = true;
        //是否馬上繪製View
        mWillDrawSoon = true;

        ...
        //頂層檢視DecorView所需要視窗的寬度和高度
        int desiredWindowWidth;
        int desiredWindowHeight;
        ...
        //在構造方法中mFirst已經設定為true,表示是否是第一次繪製DecorView
        if (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            //如果視窗的型別是有狀態列的,那麼頂層檢視DecorView所需要視窗的寬度和高度就是除了狀態列
            if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
                    || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                // NOTE -- system code, won't try to do compat mode.
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {//否則頂層檢視DecorView所需要視窗的寬度和高度就是整個螢幕的寬高
                DisplayMetrics packageMetrics =
                    mView.getContext().getResources().getDisplayMetrics();
                desiredWindowWidth = packageMetrics.widthPixels;
                desiredWindowHeight = packageMetrics.heightPixels;
            }
    }
    ...
    //獲得view寬高的測量規格,mWidth和mHeight表示視窗的寬高,lp.widthhe和lp.height表示DecorView根佈局寬和高
     int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
     int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
      // Ask host how big it wants to be
      //執行測量操作
      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //執行佈局操作
     performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
    //執行繪製操作
    performDraw()
}

ViewRootImpl .performMeasure()

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

分析:繼續檢視原始碼performMeasure(),此時,我們就能看到View.measure()的呼叫了,performLayout()以及performDraw()和performMeasure()類似。

三、總結

這裡寫圖片描述

*圖片來自網路

這張圖顯示的是View完整的從無到有的顯示過程,這篇文章最重要的是告訴了我們最頂端的performTraversals從哪裡而來,performTraversals()方法中用到了performMeasure(),performLayout(),performDraw()這三個方法,從中我們也找到了我們想要的結果,找到了onMeasure(),onLayout(),onDraw()的源頭,終於以後不用再用到這些方法的時候囫圇吞棗啦!

如有錯誤,歡迎指正
郵箱:[email protected]