Activity Window DecorView ViewRoot 的一些知識點

職責(zé)簡介

Activity

Activity并不負(fù)責(zé)視圖控制,它只是控制生命周期和處理事件。真正控制視圖的是Window。一個Activity包含了一個Window,Window才是真正代表一個窗口。Activity就像一個控制器,統(tǒng)籌視圖的添加與顯示,以及通過其他回調(diào)方法,來與Window、以及View進行交互。

Window

Window是視圖的承載器,內(nèi)部持有一個 DecorView,而這個DecorView才是 view 的根布局。Window是一個抽象類,實際在Activity中持有的是其子類PhoneWindow。

PhoneWindow的類結(jié)構(gòu).png

PhoneWindow中有個內(nèi)部類DecorView,通過創(chuàng)建DecorView來加載Activity中設(shè)置的布局R.layout.activity_main。
Window 通過WindowManager將DecorView加載其中,并將DecorView交給ViewRoot,進行視圖繪制以及其他交互。

DecorView

DecorView是FrameLayout的子類,它可以被認(rèn)為是Android視圖樹的根節(jié)點視圖。DecorView作為頂級View,它內(nèi)部包含一個豎直方向的LinearLayout,一般情況下在這個LinearLayout里面有上下三個部分,上面是個ViewStub,延遲加載的視圖(應(yīng)該是設(shè)置ActionBar,根據(jù)Theme設(shè)置),中間的是標(biāo)題欄(根據(jù)Theme設(shè)置,有的布局沒有),下面的是內(nèi)容欄@android:id/content。具體情況和Android版本及主體有關(guān),以其中一個布局為例,如下所示:

C:\Users\android\AppData\Local\Android\Sdk\platforms\android-27\data\res\layout\screen_title.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />

    <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">

        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </FrameLayout>

   <!--內(nèi)容欄,熟悉的字眼@android:id/content-->
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />

</LinearLayout>

在Activity中通過setContentView所設(shè)置的布局文件其實就是被加到內(nèi)容欄(就是上面的id為content的FrameLayout)之中的,成為其唯一子View。

ViewRoot

ViewRoot可能比較陌生,但是其作用非常重大。所有View的繪制以及事件分發(fā)等交互都是通過它來執(zhí)行或傳遞的。ViewRoot對應(yīng)ViewRootImpl類,它是連接WindowManagerService和DecorView的紐帶,View的三大流程 measure,layout,draw 均通過ViewRoot來完成。

ViewRoot并不屬于View樹的一份子。從源碼實現(xiàn)上來看,它既非View的子類,也非View的父類,但是,它實現(xiàn)了ViewParent接口,這讓它可以作為View的名義上的父視圖。

Window、Activity、DecorView以及ViewRoot四者之間的關(guān)系如下圖所示

Activity_Window_DecorView_ViewRoot.png

DecorView的創(chuàng)建

這部分內(nèi)容主要講DecorView是怎么一層層嵌套在Actvity,PhoneWindow中的,以及DecorView如何加載內(nèi)部布局。

從Activity的onCreate方法開始。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    //調(diào)用Activity的setContentView方法
    setContentView(R.layout.activity_main)
}

Activity的setContentView方法

public void setContentView(@LayoutRes int layoutResID) {
    //1. 交給window來設(shè)置視圖
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
public Window getWindow() {
    return mWindow;
}

getWindow方法返回的是一個PhoneWindow對象。接下來我們看一下PhoneWindow類中的一些成員變量和方法。

PhoneWindow類中的兩個成員變量

//當(dāng)前窗口的頂層視圖
private DecorView mDecor;
//@android:id/content所對應(yīng)的FrameLayout
private ViewGroup mContentParent;

PhoneWindow的setContentView方法精簡版

 @Override
 public void setContentView(int layoutResID) {
     if (mContentParent == null) {
         //注釋1處,如果mContentParent 為null,調(diào)用installDecor方法
         installDecor();
     } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
         //mContentParent 不為null,移除掉所有的子View
         mContentParent.removeAllViews();
     }
     //...
     //注釋2處,為mContentParent添加子View,layoutResID即Activity中設(shè)置的布局文件
     mLayoutInflater.inflate(layoutResID, mContentParent);
     //...
     //Callback是Window類中的一個接口,用戶可以通過接口中的回調(diào)方法,執(zhí)行一些操作,
     //比如事件分發(fā)。Activity是實現(xiàn)了這個接口的。
     final Callback cb = getCallback();
     if (cb != null && !isDestroyed()) {
         cb.onContentChanged();
     }
}

我們看一下注釋1處的installDecor方法

PhoneWindow的installDecor方法精簡版

private void installDecor() {
    if (mDecor == null) {
        //注釋1處
        mDecor = generateDecor();
        //...
    }
    if (mContentParent == null) {
        //注釋2處
        mContentParent = generateLayout(mDecor);
        //...
    }
}

在上面方法的注釋1處,如果mDecor == null,會調(diào)用generateDecor方法。

接下來看一下PhoneWindow的generateDecor()方法

protected DecorView generateDecor() {
    return new DecorView(getContext(), -1);
}

很簡單,創(chuàng)建了一個DecorView。要注意的是我們傳入的第二個參數(shù)是 -1 。接下來看一下注釋2處的generateLayout方法。

PhoneWindow的generateLayout方法

protected ViewGroup generateLayout(DecorView decor) {
        // 從當(dāng)前主題中應(yīng)用樣式信息
        TypedArray a = getWindowStyle();
        //...

        //初始化一些主體樣式
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } 
        //...

       // 注釋1處
       int layoutResource;
       int features = getLocalFeatures();
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
                //...
                layoutResource = R.layout.screen_title_icons;
            }
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                //假如我們使用的布局文件是R.layout.screen_title;
                layoutResource = R.layout.screen_title;
            }
        }
    //...
    //注釋2處
    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;
   //注釋3處,ID_ANDROID_CONTENT就是@android:id/content,這個常量是在Window類中定義的
   ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
   //...
   //返回contentParent
   return contentParent;
}
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

generateLayout方法注釋1處,先從主題中獲取樣式,然后根據(jù)樣式,加載對應(yīng)的布局文件,比如我們加載的是R.layout.screen_title。注釋2處將布局文件視圖加載到DecorView中。注釋3處,將id為@android:id/content的FrameLayout賦值給contentParent返回。

這時候我們回到PhoneWindow的setContentView方法的注釋2處

//注釋2處,為mContentParent添加子View,layoutResID即Activity中設(shè)置的布局文件
mLayoutInflater.inflate(layoutResID, mContentParent);

此處的代碼就是將我們設(shè)置的布局文件加載到id為@android:id/content的FrameLayout中去。

以上就是DecorView的創(chuàng)建過程。

DecorView的顯示

以上僅僅是將DecorView建立起來。通過setContentView()設(shè)置的界面,為什么在onResume()之后才對用戶可見呢?這就要從ActivityThread開始說起了。

ActivityThread的handleLaunchActivity方法精簡版

 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
     //...
    Activity a = performLaunchActivity(r, customIntent);
    //...
    if (a != null) {
        handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
    }
    //...

}

接下來看一下ActivityThread的performLaunchActivity方法精簡版,這個方法內(nèi)部會執(zhí)行Activity的attach方法和onCreate方法

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
     //...
     //創(chuàng)建Activity實例
     Activity activity = null;
     try {
          java.lang.ClassLoader cl = appContext.getClassLoader();
          activity = mInstrumentation.newActivity(
                  cl, component.getClassName(), r.intent);
          //...
      } 
      try {
           //...
          if (activity != null) {
             //...
            //注釋1處,activity調(diào)用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);
             //...
            //注釋2處,這里會使Activity的onCreate方法被調(diào)用
            mInstrumentation.callActivityOnCreate(activity, r.state);
           //...
           mActivities.put(r.token, r);
        } 

    return activity;
}

在注釋1處,activity調(diào)用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, ActivityConfigCallback activityConfigCallback) {
      
        //...
        //創(chuàng)建Window對象
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
       //...
      //添加callback對象
      mWindow.setCallback(this);
    }

ActivityThread的performLaunchActivity方法注釋2處,這里會使Activity的onCreate方法被調(diào)用。onCreate方法里面執(zhí)行了setContentView方法。

接下來看一下ActivityThread的handleResumeActivity方法精簡版

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
  //...  
  //注釋1處,在這個方法里activity調(diào)用了onResume方法
  r = performResumeActivity(token, clearHide, reason);
  if (r != null) {
       final Activity a = r.activity;
       if (r.window == null && !a.mFinished && willBeVisible) {
                //為r.window賦值
                r.window = r.activity.getWindow();
                //為decor 賦值
                View decor = r.window.getDecorView();
                //設(shè)置decor不可見 
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                //...  
                if (a.mVisibleFromClient) {//默認(rèn)是true
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        //注釋2處,被添加進WindowManager了,但是這個時候,還是不可見的
                        wm.addView(decor, l);
                    } 
                }
               //...  
            }
        if (!r.activity.mFinished && willBeVisible
                && r.activity.mDecor != null && !r.hideForNow) {
            //...  
            r.activity.mVisibleFromServer = true;
            mNumVisibleActivities++;
            if (r.activity.mVisibleFromClient) {
               //注釋3處,在這里,執(zhí)行了重要的操作,使得DecorView可見。
                r.activity.makeVisible();
            }
        }
   } 
}

在注釋3處,當(dāng)我們執(zhí)行了Activity.makeVisible()方法之后,界面才對我們是可見的。

Activity的makeVisible方法

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        //1. ViewManager添加View
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    //DecorView設(shè)置可見性
    mDecor.setVisibility(View.VISIBLE);
}

到此DecorView便可見,顯示在屏幕中。但是在這其中,ViewManager添加View起到了重要的作用,因為其內(nèi)部創(chuàng)建了一個ViewRootImpl對象,負(fù)責(zé)繪制顯示各個子View。

ViewManager是一個接口,是WindowManager的父接口,WindowManager對應(yīng)的實現(xiàn)類是WindowManagerImpl。

WindowManagerImpl類的addView方法。

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

內(nèi)部調(diào)用mGlobal的addView方法。mGlobal是一個WindowManagerGlobal對象。

WindowManagerGlobal的addView方法

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    //...
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        //...
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
       //將DecorView交給ViewRootImpl  
       root.setView(view, wparams, panelParentView);
    }
}

看到其中實例化了ViewRootImpl對象,然后調(diào)用其setView()方法。其中setView()方法經(jīng)過一些列折騰,最終調(diào)用了performTraversals()方法,然后依照下圖流程層層調(diào)用,完成繪制,最終界面才顯示出來。

ViewRootImpl類的setView方法精簡版

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        //...
       //調(diào)用requestLayout方法
        requestLayout();
       //... 
       //設(shè)置ViewRootImpl為DecorView的父控件
       view.assignParent(this);
    }
}

ViewRootImpl類的requestLayout方法精簡版

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
       //注釋1處,
        checkThread();
        mLayoutRequested = true;
        //注釋2處,調(diào)用scheduleTraversals方法
        scheduleTraversals();
    }
}

注釋1處ViewRootImpl類的checkThread方法

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

這個異常信息不知道你們見沒見過,反正我是見過,哈哈。

ViewRootImpl類的scheduleTraversals方法精簡版

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //...
        //1. 注意mTraversalRunnable是一個TraversalRunnable對象
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        //...
    }
}

TraversalRunnable是ViewRootImpl的一個內(nèi)部類

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        //1.調(diào)用ViewRootImpl類的doTraversal方法
        doTraversal();
    }
}

ViewRootImpl類的doTraversal方法精簡版

void doTraversal() {
    if (mTraversalScheduled) {
      //...
      //調(diào)用performTraversals方法
      performTraversals();
      //...
    }
}

ViewRootImpl類的performTraversals方法精簡版

private void performTraversals() {

  //...
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  //...
  performLayout(lp, mWidth, mHeight);
  //...
  performDraw();
  //...

}

performTravels.png

其實ViewRootImpl的作用不止如此,還有許多功能,如事件分發(fā)等等。

觸摸事件的傳遞順序是從硬件到ViewRootImpl的。

從ViewRootImpl開始觸摸事件傳遞順序如下圖所示:


觸摸事件傳遞順序.png

注意:最后一個步驟

DecorView的superDispatchTouchEvent方法

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

因為DecorView是繼承自FrameLayout的。所以從現(xiàn)在開始ViewGroup開始處理事件分發(fā)。

總結(jié)

以上通過源碼形式介紹了Window、Activity、DecorView以及ViewRoot之間的錯綜關(guān)系,以及如何創(chuàng)建并顯示DecorView。通過以上了解可以知道,Activity就像個控制器,不負(fù)責(zé)視圖部分。Window像個承載器,裝著內(nèi)部視圖。DecorView就是個頂層視圖,是所有View的最外層布局。ViewRoot像個連接器,負(fù)責(zé)溝通,通過硬件的感知來通知視圖,進行用戶之間的交互。

參考鏈接

  1. 簡析Window、Activity、DecorView以及ViewRoot之間的錯綜關(guān)系
  2. Android View源碼解讀:淺談DecorView與ViewRootImpl
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容