溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

深入淺析Android中DecorView與ViewRootImpl的區(qū)別

發(fā)布時間:2020-11-23 17:14:44 來源:億速云 閱讀:267 作者:Leah 欄目:移動開發(fā)

今天就跟大家聊聊有關深入淺析Android中DecorView與ViewRootImpl的區(qū)別,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。

從setContentView說起

一般地,我們在Activity中,會在onCreate()方法中寫下這樣一句:

setContentView(R.layout.main);

顯然,這是為activity設置一個我們定義好的main.xml布局,我們跟蹤一下源碼,看看這個方法是怎樣做的,Activity#setContentView:

public void setContentView(@LayoutRes int layoutResID) {
   getWindow().setContentView(layoutResID); //調用getWindow方法,返回mWindow
   initWindowDecorActionBar();
}
...
public Window getWindow() {  
   return mWindow;
}

從上面看出,里面調用了mWindow的setContentView方法,那么這個“mWindow”是何方神圣呢?嘗試追蹤一下源碼,發(fā)現mWindow是Window類型的,但是它是一個抽象類,setContentView也是抽象方法,所以我們要找到Window類的實現類才行。我們在Activity中查找一下mWindow在哪里被賦值了,可以發(fā)現它在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) {
    ...
    mWindow = new PhoneWindow(this);
    ...
  }

我們只看關鍵部分,這里實例化了PhoneWindow類,由此得知,PhoneWindow是Window的實現類,那么我們在PhoneWindow類里面找到它的setContentView方法,看看它又實現了什么,PhoneWindow#setContentView:

@Override
public void setContentView(int layoutResID) {
  // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
  // decor, when theme attributes and the like are crystalized. Do not check the feature
  // before this happens.
  if (mContentParent == null) { // 1
    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); // 2
  }
  final Callback cb = getCallback();
  if (cb != null && !isDestroyed()) {
    cb.onContentChanged();
  }
}

首先判斷了mContentParent是否為null,如果為空則執(zhí)行installDecor()方法,那么這個mContentParent又是什么呢?我們看一下它的注釋:

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;

它是一個ViewGroup類型,結合②號代碼處,可以得知,這個mContentParent是我們設置的布局(即main.xml)的父布局。注釋還提到了,這個mContentParent是mDecor本身或者是mDecor的一個子元素,這句話什么意思呢?這里先留一個疑問,下面會解釋。

這里先梳理一下以上的內容:Activity通過PhoneWindow的setContentView方法來設置布局,而設置布局之前,會先判斷是否存在mContentParent,而我們設置的布局文件則是mContentParent的子元素。

創(chuàng)建DecorView

接著上面提到的installDecor()方法,我們看看它的源碼,PhoneWindow#installDecor:

private void installDecor() {
  if (mDecor == null) {
    mDecor = generateDecor(); // 1
    mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    mDecor.setIsRootNamespace(true);
    if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
      mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
    }
  }
  if (mContentParent == null) {
    mContentParent = generateLayout(mDecor); // 2
    ...
    } 
  }
}

首先,會執(zhí)行①號代碼,調用PhoneWindow#generateDecor方法:

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

可以看出,這里實例化了DecorView,而DecorView則是PhoneWindow類的一個內部類,繼承于FrameLayout,由此可知它也是一個ViewGroup。

那么,DecroView到底充當了什么樣的角色呢?

其實,DecorView是整個ViewTree的最頂層View,它是一個FrameLayout布局,代表了整個應用的界面。在該布局下面,有標題view和內容view這兩個子元素,而內容view則是上面提到的mContentParent。我們接著看②號代碼,PhoneWindow#generateLayout方法

protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    // 從主題文件中獲取樣式信息
    TypedArray a = getWindowStyle();

    ...

    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
      requestFeature(FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
      // Don't allow an action bar if there is no title.
      requestFeature(FEATURE_ACTION_BAR);
    }

    if(...){
      ...
    }

    // Inflate the window decor.
    // 加載窗口布局
    int layoutResource;
    int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
      layoutResource = R.layout.screen_swipe_dismiss;
    } else if(...){
      ...
    }

    View in = mLayoutInflater.inflate(layoutResource, null);  //加載layoutResource
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); //往DecorView中添加子View,即mContentParent
    mContentRoot = (ViewGroup) in;

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 這里獲取的就是mContentParent
    if (contentParent == null) {
      throw new RuntimeException("Window couldn't find content container view");
    }

    if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
      ProgressBar progress = getCircularProgressBar(false);
      if (progress != null) {
        progress.setIndeterminate(true);
      }
    }

    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
      registerSwipeCallbacks();
    }

    // Remaining setup -- of background and title -- that only applies
    // to top-level windows.
    ...

    return contentParent;
  }

由以上代碼可以看出,該方法還是做了相當多的工作的,首先根據設置的主題樣式來設置DecorView的風格,比如說有沒有titlebar之類的,接著為DecorView添加子View,而這里的子View則是上面提到的mContentParent,如果上面設置了FEATURE_NO_ACTIONBAR,那么DecorView就只有mContentParent一個子View,這也解釋了上面的疑問:mContentParent是DecorView本身或者是DecorView的一個子元素。
用一幅圖來表示DecorView的結構如下:

深入淺析Android中DecorView與ViewRootImpl的區(qū)別

小結:DecorView是頂級View,內部有titlebar和contentParent兩個子元素,contentParent的id是content,而我們設置的main.xml布局則是contentParent里面的一個子元素。

在DecorView創(chuàng)建完畢后,讓我們回到PhoneWindow#setContentView方法,直接看②號代碼: mLayoutInflater.inflate(layoutResID, mContentParent);這里加載了我們設置的main.xml布局文件,并且設置mContentParent為main.xml的父布局,至于它怎么加載的,這里就不展開來說了。

到目前為止,通過setContentView方法,創(chuàng)建了DecorView和加載了我們提供的布局,但是這時,我們的View還是不可見的,因為我們僅僅是加載了布局,并沒有對View進行任何的測量、布局、繪制工作。在View進行測量流程之前,還要進行一個步驟,那就是把DecorView添加至window中,然后經過一系列過程觸發(fā)ViewRootImpl#performTraversals方法,在該方法內部會正式開始測量、布局、繪制這三大流程。至于該一系列過程是怎樣的,因為涉及到了很多機制,這里簡單說明一下:

將DecorView添加至Window

每一個Activity組件都有一個關聯的Window對象,用來描述一個應用程序窗口。每一個應用程序窗口內部又包含有一個View對象,用來描述應用程序窗口的視圖。上文分析了創(chuàng)建DecorView的過程,現在則要把DecorView添加到Window對象中。而要了解這個過程,我們首先要簡單先了解一下Activity的創(chuàng)建過程:
首先,在ActivityThread#handleLaunchActivity中啟動Activity,在這里面會調用到Activity#onCreate方法,從而完成上面所述的DecorView創(chuàng)建動作,當onCreate()方法執(zhí)行完畢,在handleLaunchActivity方法會繼續(xù)調用到ActivityThread#handleResumeActivity方法,我們看看這個方法的源碼:

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { 
  //...
  ActivityClientRecord r = performResumeActivity(token, clearHide); // 這里會調用到onResume()方法

  if (r != null) {
    final Activity a = r.activity;

    //...
    if (r.window == null && !a.mFinished && willBeVisible) {
      r.window = r.activity.getWindow(); // 獲得window對象
      View decor = r.window.getDecorView(); // 獲得DecorView對象
      decor.setVisibility(View.INVISIBLE);
      ViewManager wm = a.getWindowManager(); // 獲得windowManager對象
      WindowManager.LayoutParams l = r.window.getAttributes();
      a.mDecor = decor;
      l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
      l.softInputMode |= forwardBit;
      if (a.mVisibleFromClient) {
        a.mWindowAdded = true;
        wm.addView(decor, l); // 調用addView方法
      }
      //...
    }
  }
}

在該方法內部,獲取該activity所關聯的window對象,DecorView對象,以及windowManager對象,而WindowManager是抽象類,它的實現類是WindowManagerImpl,所以后面調用的是WindowManagerImpl#addView方法,我們看看源碼:

public final class WindowManagerImpl implements WindowManager {  
  private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
  ...
  @Override
  public void addView(View view, ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params, mDisplay, mParentWindow);
  }
}

接著調用了mGlobal的成員函數,而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); // 1

      view.setLayoutParams(wparams);

      mViews.add(view);
      mRoots.add(root);
      mParams.add(wparams);
    }

    // do this last because it fires off messages to start doing things
    try {
      root.setView(view, wparams, panelParentView); // 2
    } catch (RuntimeException e) {
      // BadTokenException or InvalidDisplayException, clean up.
      synchronized (mLock) {
        final int index = findViewLocked(view, false);
        if (index >= 0) {
          removeViewLocked(index, true);
        }
      }
      throw e;
    }
  }


先看①號代碼處,實例化了ViewRootImpl類,接著,在②號代碼處,調用ViewRootImpl#setView方法,并把DecorView作為參數傳遞進去,在這個方法內部,會通過跨進程的方式向WMS(WindowManagerService)發(fā)起一個調用,從而將DecorView最終添加到Window上,在這個過程中,ViewRootImpl、DecorView和WMS會彼此關聯

看完上述內容,你們對深入淺析Android中DecorView與ViewRootImpl的區(qū)別有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI