android setContentView流程分析

為什么要分析setContentView() 方法的代碼呢?這是xml布局文件如何加載view到內(nèi)存中的流程,如果懂了這個流程的話,一些功能我們就可以實現(xiàn),比如換膚,比如如何改變項目中所有textview的文字顏色或者大小等等,所以我們學(xué)習(xí)很有必要的,既然要學(xué)習(xí)當(dāng)然是從我們繼承的Activity學(xué)起了

class DarrenDemo7Activity :AppCompatActivity(){
  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_darren_demo7)
  }
}

我們是繼承了AppCompatActivity類,首先懂一個小知識compat英文單詞是啥意思呢?


image.png

這圖即然顯示失敗了,翻譯就是兼容的意思,這是從那個版本后我們就不要繼承Activity了,那我們點擊setContentView()方法進去

@Override
  public void setContentView(@LayoutRes int layoutResID) {
      getDelegate().setContentView(layoutResID);
  }

這里又有一個常見的單詞delegate

delegate
n. 代表;委員會成員
v. 授權(quán),把……委托給;選派(某人做某事)

類似代理的意思,這里最終會到Activity類的setContentView()方法中,只是中間做了很多兼容性操作,這個我們就沒必要看了,那我們就直接到Activity類中的setContenView()方法,看它的代碼實現(xiàn).

/**
   * Set the activity content from a layout resource.  The resource will be
   * inflated, adding all top-level views to the activity.
   *
   * @param layoutResID Resource ID to be inflated.
   *
   * @see #setContentView(android.view.View)
   * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
   */
  public void setContentView(@LayoutRes int layoutResID) {
      getWindow().setContentView(layoutResID);
      initWindowDecorActionBar();
  }

讀系統(tǒng)源碼啊,注釋是一個很重要的信息,它會給我們很多啟示,比如這個注釋就說
設(shè)置Activity內(nèi)容從layout資源文件中, 這資源會填充到頂級的Activity中的view上的

我們看到Activity中的setContentView() 方法,它首先會調(diào)用這個:

getWindow().setContentView(layoutResID);

意思是說我們傳遞過來的layout資源文件會設(shè)置到getWindow()這個對象上的setContentView()方法上的,那么我們看看getWindow() 方法返回啥對象?

/**
   * Retrieve the current {@link android.view.Window} for the activity.
   * This can be used to directly access parts of the Window API that
   * are not available through Activity/Screen.
   *
   * @return Window The current window, or null if the activity is not
   *         visual.
   */
  public Window getWindow() {
      return mWindow;
  }

它返回的是一個mWindow變量,看看這個變量在那進行初始化的,在Activity類中全局搜索下: 發(fā)現(xiàn)最終是在Activity中的attach() 方法中初始化了mWindow變量的

@UnsupportedAppUsage
  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, IBinder assistToken) {
      attachBaseContext(context);

      mFragments.attachHost(null /*parent*/);
      //mWindow變量初始化
      mWindow = new PhoneWindow(this, window, activityConfigCallback);
      mWindow.setWindowControllerCallback(mWindowControllerCallback);
      mWindow.setCallback(this);
      mWindow.setOnWindowDismissedCallback(this);
      mWindow.getLayoutInflater().setPrivateFactory(this);
      if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
          mWindow.setSoftInputMode(info.softInputMode);
      }
      if (info.uiOptions != 0) {
          mWindow.setUiOptions(info.uiOptions);
      }

現(xiàn)在Activity中的attch()方法是在那被調(diào)用的呢?這里就要涉及到app的啟動流程了, 我們知道app啟動流程是在ActivityThread類中的main()方法開始的,那我們找到ActivityThread類中的performLaunchActivity()方法,里面會有

/**  Core implementation of activity launch. */
  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
      ActivityInfo aInfo = r.activityInfo;
      if (r.packageInfo == null) {
          r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                  Context.CONTEXT_INCLUDE_CODE);
      }

      ComponentName component = r.intent.getComponent();
      if (component == null) {
          component = r.intent.resolveActivity(
              mInitialApplication.getPackageManager());
          r.intent.setComponent(component);
      }

      if (r.activityInfo.targetActivity != null) {
          component = new ComponentName(r.activityInfo.packageName,
                  r.activityInfo.targetActivity);
      }

      ContextImpl appContext = createBaseContextForActivity(r);
      Activity activity = null;
      try {
          java.lang.ClassLoader cl = appContext.getClassLoader();
//這里是創(chuàng)建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);
          }
      }

然后下面就是創(chuàng)建Activity后調(diào)用了attach() 方法對Window對象進行初始化了.

if (activity != null) {
              CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
              Configuration config = new Configuration(mCompatConfiguration);
              if (r.overrideConfig != null) {
                  config.updateFrom(r.overrideConfig);
              }
              if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                      + r.activityInfo.name + " with config " + config);
              Window window = null;
              if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                  window = r.mPendingRemoveWindow;
                  r.mPendingRemoveWindow = null;
                  r.mPendingRemoveWindowManager = null;
              }

              // Activity resources must be initialized with the same loaders as the
              // application context.
              appContext.getResources().addLoaders(
                      app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

              appContext.setOuterContext(activity);
            //這里調(diào)用了Activity的attach()  方法,那么Activity中的mWindow變量就有值了
              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,
                      r.assistToken);

              if (customIntent != null) {
                  activity.mIntent = customIntent;
              }

在這個方法中我們還可以看到調(diào)用了Activity的onCreate()方法

if (r.isPersistable()) {
                 mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
             } else {
                 mInstrumentation.callActivityOnCreate(activity, r.state);
             }

所以我們知道attach()方法要早于onCreate()方法調(diào)用的.現(xiàn)在我們知道了從Activity中的setContentView()方法到Window類中的setContentView()方法,也就是說我們的layout資源最終是設(shè)置給了Window類中的setContentView()上了資源給它了,現(xiàn)在我們看看Window類是一個什么類吧

/**
* Abstract base class for a top-level window look and behavior policy.  An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {

發(fā)現(xiàn)Window是一個抽象類,我們知道抽象類是不能被實例化的,那么就找它的子類,我們在Activity類中的attach()方法找到了

@UnsupportedAppUsage
 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, IBinder assistToken) {
     attachBaseContext(context);

     mFragments.attachHost(null /*parent*/);
   //mWindow變量所指向的是PhoneWindow 對象
     mWindow = new PhoneWindow(this, window, activityConfigCallback);

在這里我們就知道了PhoneWindow是Window的子類,那么我們就到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) {
          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;
  }

先看第一個if條件語句, mContentParent變量為null,就調(diào)用installDecor()方法,那看下這個方法做了什么事,

private void installDecor() {
      mForceDecorInstall = false;
      if (mDecor == null) {
          mDecor = generateDecor(-1);
          mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
          mDecor.setIsRootNamespace(true);
          if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
              mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
          }
      } else {
          mDecor.setWindow(this);
      }
      if (mContentParent == null) {
          mContentParent = generateLayout(mDecor);

          // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
          mDecor.makeFrameworkOptionalFitsSystemWindows();

          final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                  R.id.decor_content_parent);

          if (decorContentParent != null) {
              mDecorContentParent = decorContentParent;
              mDecorContentParent.setWindowCallback(getCallback());
              if (mDecorContentParent.getTitle() == null) {
                  mDecorContentParent.setWindowTitle(mTitle);
              }

              final int localFeatures = getLocalFeatures();
              for (int i = 0; i < FEATURE_MAX; i++) {
                  if ((localFeatures & (1 << i)) != 0) {
                      mDecorContentParent.initFeature(i);
                  }
              }

              mDecorContentParent.setUiOptions(mUiOptions);

              if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
                      (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
                  mDecorContentParent.setIcon(mIconRes);
              } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
                      mIconRes == 0 && !mDecorContentParent.hasIcon()) {
                  mDecorContentParent.setIcon(
                          getContext().getPackageManager().getDefaultActivityIcon());
                  mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
              }
              if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
                      (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
                  mDecorContentParent.setLogo(mLogoRes);
              }

              // Invalidate if the panel menu hasn't been created before this.
              // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
              // being called in the middle of onCreate or similar.
              // A pending invalidation will typically be resolved before the posted message
              // would run normally in order to satisfy instance state restoration.
              PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
              if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
                  invalidatePanelMenu(FEATURE_ACTION_BAR);
              }
          } else {
              mTitleView = findViewById(R.id.title);
              if (mTitleView != null) {
                  if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                      final View titleContainer = findViewById(R.id.title_container);
                      if (titleContainer != null) {
                          titleContainer.setVisibility(View.GONE);
                      } else {
                          mTitleView.setVisibility(View.GONE);
                      }
                      mContentParent.setForeground(null);
                  } else {
                      mTitleView.setText(mTitle);
                  }
              }
          }

          if (mDecor.getBackground() == null && mBackgroundFallbackDrawable != null) {
              mDecor.setBackgroundFallback(mBackgroundFallbackDrawable);
          }

          // Only inflate or create a new TransitionManager if the caller hasn't
          // already set a custom one.
          if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
              if (mTransitionManager == null) {
                  final int transitionRes = getWindowStyle().getResourceId(
                          R.styleable.Window_windowContentTransitionManager,
                          0);
                  if (transitionRes != 0) {
                      final TransitionInflater inflater = TransitionInflater.from(getContext());
                      mTransitionManager = inflater.inflateTransitionManager(transitionRes,
                              mContentParent);
                  } else {
                      mTransitionManager = new TransitionManager();
                  }
              }

              mEnterTransition = getTransition(mEnterTransition, null,
                      R.styleable.Window_windowEnterTransition);
              mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
                      R.styleable.Window_windowReturnTransition);
              mExitTransition = getTransition(mExitTransition, null,
                      R.styleable.Window_windowExitTransition);
              mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
                      R.styleable.Window_windowReenterTransition);
              mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
                      R.styleable.Window_windowSharedElementEnterTransition);
              mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
                      USE_DEFAULT_TRANSITION,
                      R.styleable.Window_windowSharedElementReturnTransition);
              mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
                      R.styleable.Window_windowSharedElementExitTransition);
              mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
                      USE_DEFAULT_TRANSITION,
                      R.styleable.Window_windowSharedElementReenterTransition);
              if (mAllowEnterTransitionOverlap == null) {
                  mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
                          R.styleable.Window_windowAllowEnterTransitionOverlap, true);
              }
              if (mAllowReturnTransitionOverlap == null) {
                  mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
                          R.styleable.Window_windowAllowReturnTransitionOverlap, true);
              }
              if (mBackgroundFadeDurationMillis < 0) {
                  mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
                          R.styleable.Window_windowTransitionBackgroundFadeDuration,
                          DEFAULT_BACKGROUND_FADE_DURATION_MS);
              }
              if (mSharedElementsUseOverlay == null) {
                  mSharedElementsUseOverlay = getWindowStyle().getBoolean(
                          R.styleable.Window_windowSharedElementsUseOverlay, true);
              }
          }
      }
  }

在這里我們不可能所有源碼都要弄清楚到底是做什么的,我們只要看跟我們相關(guān)的,首先我們看第一個if語句代碼:

if (mDecor == null) {
          mDecor = generateDecor(-1);
          mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
          mDecor.setIsRootNamespace(true);
          if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
              mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
          }
      } else {
          mDecor.setWindow(this);
      }

如果mDecor為null就耀進去做什么邏輯,那么我們看下mDecor這個變量是什么?

// This is the top-level view of the window, containing the window decor.
  private DecorView mDecor;

先看它的注釋:這是窗口的頂層視圖,包含窗口裝飾,而DecorView到底是啥鬼東西呢?點擊進去看看

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
 private static final String TAG = "DecorView";

 private static final boolean DEBUG_MEASURE = false;

 private static final boolean SWEEP_OPEN_MENU = false;

 // The height of a window which has focus in DIP.
 public static final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
 // The height of a window which has not in DIP.
 public static final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;

發(fā)現(xiàn)DecorView是一個繼承了FrameLayout的View對象,那么好現(xiàn)在我們繼續(xù)看PhoneWindow類中的installDecor方法的if語句

if (mDecor == null) {
           mDecor = generateDecor(-1);
           mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
           mDecor.setIsRootNamespace(true);
           if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
               mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
           }
       } else {
           mDecor.setWindow(this);
       }

如果mDecor為null,if語句進來了,然后進入這句代碼

mDecor = generateDecor(-1);

這是把generateDecor()方法返回的對象賦值給mDecor變量了,那么我們繼續(xù)跟蹤下去,看看generateDecor()做了啥事?


protected DecorView generateDecor(int featureId) {
       // System process doesn't have application context and in that case we need to directly use
       // the context we have. Otherwise we want the application context, so we don't cling to the
       // activity.
       Context context;
       if (mUseDecorContext) {
           Context applicationContext = getContext().getApplicationContext();
           if (applicationContext == null) {
               context = getContext();
           } else {
               context = new DecorContext(applicationContext, this);
               if (mTheme != -1) {
                   context.setTheme(mTheme);
               }
           }
       } else {
           context = getContext();
       }
       return new DecorView(context, featureId, this, getAttributes());
   }

在這個方法中,我們沒必要知道什么,因為跟我們今天研究的沒有關(guān)系,只要知道generateDecor()方法返回的是一個DecorView對象就夠了
現(xiàn)在繼續(xù)看installDecor()方法的代碼,看第二個if條件語句.

if (mContentParent == null) {
          mContentParent = generateLayout(mDecor);

          // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
          mDecor.makeFrameworkOptionalFitsSystemWindows();

          final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                  R.id.decor_content_parent);

首先看下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.
  ViewGroup mContentParent;

看下這個變量的注釋:

這是放置窗口內(nèi)容的視圖。
mDecor本身,或者mDecor的子元素,內(nèi)容存放的地方

mContentParent變量是 generateLayout(mDecor);方法賦值的,看下這個方法的源碼

protected ViewGroup generateLayout(DecorView decor) {
      // Apply data from current theme.

      TypedArray a = getWindowStyle();

      if (false) {
          System.out.println("From style:");
          String s = "Attrs:";
          for (int i = 0; i < R.styleable.Window.length; i++) {
              s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                      + a.getString(i);
          }
          System.out.println(s);
      }

      mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
      int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
              & (~getForcedWindowFlags());
      if (mIsFloating) {
          setLayout(WRAP_CONTENT, WRAP_CONTENT);
          setFlags(0, flagsToUpdate);
      } else {
          setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
          getAttributes().setFitInsetsSides(0);
          getAttributes().setFitInsetsTypes(0);
      }

      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 (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
          requestFeature(FEATURE_ACTION_BAR_OVERLAY);
      }

      if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
          requestFeature(FEATURE_ACTION_MODE_OVERLAY);
      }

      if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
          setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
      }

      if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
              false)) {
          setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                  & (~getForcedWindowFlags()));
      }

      if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
              false)) {
          setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
                  & (~getForcedWindowFlags()));
      }

      if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
          setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
      }

      if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
              getContext().getApplicationInfo().targetSdkVersion
                      >= android.os.Build.VERSION_CODES.HONEYCOMB)) {
          setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
      }

      a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
      a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
      if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString()
              + ", major: " + mMinWidthMajor.coerceToString());
      if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
          if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
          a.getValue(R.styleable.Window_windowFixedWidthMajor,
                  mFixedWidthMajor);
      }
      if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
          if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
          a.getValue(R.styleable.Window_windowFixedWidthMinor,
                  mFixedWidthMinor);
      }
      if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
          if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
          a.getValue(R.styleable.Window_windowFixedHeightMajor,
                  mFixedHeightMajor);
      }
      if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
          if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
          a.getValue(R.styleable.Window_windowFixedHeightMinor,
                  mFixedHeightMinor);
      }
      if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
          requestFeature(FEATURE_CONTENT_TRANSITIONS);
      }
      if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
          requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
      }

      mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);

      final Context context = getContext();
      final int targetSdk = context.getApplicationInfo().targetSdkVersion;
      final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
      final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;

      if (!mForcedStatusBarColor) {
          mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
      }
      if (!mForcedNavigationBarColor) {
          mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
          mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
                  0x00000000);
      }
      if (!targetPreQ) {
          mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
                  R.styleable.Window_enforceStatusBarContrast, false);
          mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
                  R.styleable.Window_enforceNavigationBarContrast, true);
      }

      WindowManager.LayoutParams params = getAttributes();

      // Non-floating windows on high end devices must put up decor beneath the system bars and
      // therefore must know about visibility changes of those.
      if (!mIsFloating) {
          if (!targetPreL && a.getBoolean(
                  R.styleable.Window_windowDrawsSystemBarBackgrounds,
                  false)) {
              setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                      FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
          }
          if (mDecor.mForceWindowDrawsBarBackgrounds) {
              params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
          }
      }
      if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
          decor.setSystemUiVisibility(
                  decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
      }
      if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
          decor.setSystemUiVisibility(
                  decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
      }
      if (a.hasValue(R.styleable.Window_windowLayoutInDisplayCutoutMode)) {
          int mode = a.getInt(R.styleable.Window_windowLayoutInDisplayCutoutMode, -1);
          if (mode < LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
                  || mode > LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
              throw new UnsupportedOperationException("Unknown windowLayoutInDisplayCutoutMode: "
                      + a.getString(R.styleable.Window_windowLayoutInDisplayCutoutMode));
          }
          params.layoutInDisplayCutoutMode = mode;
      }

      if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
              >= android.os.Build.VERSION_CODES.HONEYCOMB) {
          if (a.getBoolean(
                  R.styleable.Window_windowCloseOnTouchOutside,
                  false)) {
              setCloseOnTouchOutsideIfNotSet(true);
          }
      }

      if (!hasSoftInputMode()) {
          params.softInputMode = a.getInt(
                  R.styleable.Window_windowSoftInputMode,
                  params.softInputMode);
      }

      if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
              mIsFloating)) {
          /* All dialogs should have the window dimmed */
          if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
              params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
          }
          if (!haveDimAmount()) {
              params.dimAmount = a.getFloat(
                      android.R.styleable.Window_backgroundDimAmount, 0.5f);
          }
      }

      if (params.windowAnimations == 0) {
          params.windowAnimations = a.getResourceId(
                  R.styleable.Window_windowAnimationStyle, 0);
      }

      // The rest are only done if this window is not embedded; otherwise,
      // the values are inherited from our container.
      if (getContainer() == null) {
          if (mBackgroundDrawable == null) {

              if (mFrameResource == 0) {
                  mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
              }

              if (a.hasValue(R.styleable.Window_windowBackground)) {
                  mBackgroundDrawable = a.getDrawable(R.styleable.Window_windowBackground);
              }
          }
          if (a.hasValue(R.styleable.Window_windowBackgroundFallback)) {
              mBackgroundFallbackDrawable =
                      a.getDrawable(R.styleable.Window_windowBackgroundFallback);
          }
          if (mLoadElevation) {
              mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
          }
          mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
          mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
      }

      // Inflate the window decor.

      int layoutResource;
      int features = getLocalFeatures();
      // System.out.println("Features: 0x" + Integer.toHexString(features));
      if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
          if (mIsFloating) {
              TypedValue res = new TypedValue();
              getContext().getTheme().resolveAttribute(
                      R.attr.dialogTitleIconsDecorLayout, res, true);
              layoutResource = res.resourceId;
          } else {
              layoutResource = R.layout.screen_title_icons;
          }
          // XXX Remove this once action bar supports these features.
          removeFeature(FEATURE_ACTION_BAR);
          // System.out.println("Title Icons!");
      } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
              && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
          // Special case for a window with only a progress bar (and title).
          // XXX Need to have a no-title version of embedded windows.
          layoutResource = R.layout.screen_progress;
          // System.out.println("Progress!");
      } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
          // Special case for a window with a custom title.
          // If the window is floating, we need a dialog layout
          if (mIsFloating) {
              TypedValue res = new TypedValue();
              getContext().getTheme().resolveAttribute(
                      R.attr.dialogCustomTitleDecorLayout, res, true);
              layoutResource = res.resourceId;
          } else {
              layoutResource = R.layout.screen_custom_title;
          }
          // XXX Remove this once action bar supports these features.
          removeFeature(FEATURE_ACTION_BAR);
      } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
          // If no other features and not embedded, only need a title.
          // If the window is floating, we need a dialog layout
          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 {
              layoutResource = R.layout.screen_title;
          }
          // System.out.println("Title!");
      } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
          layoutResource = R.layout.screen_simple_overlay_action_mode;
      } else {
          // Embedded, so no decoration is needed.
          layoutResource = R.layout.screen_simple;
          // System.out.println("Simple!");
      }

      mDecor.startChanging();
      mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      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);
          }
      }

      // Remaining setup -- of background and title -- that only applies
      // to top-level windows.
      if (getContainer() == null) {
          mDecor.setWindowBackground(mBackgroundDrawable);

          final Drawable frame;
          if (mFrameResource != 0) {
              frame = getContext().getDrawable(mFrameResource);
          } else {
              frame = null;
          }
          mDecor.setWindowFrame(frame);

          mDecor.setElevation(mElevation);
          mDecor.setClipToOutline(mClipToOutline);

          if (mTitle != null) {
              setTitle(mTitle);
          }

          if (mTitleColor == 0) {
              mTitleColor = mTextColor;
          }
          setTitleColor(mTitleColor);
      }

      mDecor.finishChanging();

      return contentParent;
  }

發(fā)現(xiàn)這個方法太長了,我們也只要看關(guān)鍵的地方就行

// Inflate the window decor.

     int layoutResource;
     int features = getLocalFeatures();
     // System.out.println("Features: 0x" + Integer.toHexString(features));
     if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
         if (mIsFloating) {
             TypedValue res = new TypedValue();
             getContext().getTheme().resolveAttribute(
                     R.attr.dialogTitleIconsDecorLayout, res, true);
             layoutResource = res.resourceId;
         } else {
             layoutResource = R.layout.screen_title_icons;
         }
         // XXX Remove this once action bar supports these features.
         removeFeature(FEATURE_ACTION_BAR);
         // System.out.println("Title Icons!");
     } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
             && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
         // Special case for a window with only a progress bar (and title).
         // XXX Need to have a no-title version of embedded windows.
         layoutResource = R.layout.screen_progress;
         // System.out.println("Progress!");
     } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
         // Special case for a window with a custom title.
         // If the window is floating, we need a dialog layout
         if (mIsFloating) {
             TypedValue res = new TypedValue();
             getContext().getTheme().resolveAttribute(
                     R.attr.dialogCustomTitleDecorLayout, res, true);
             layoutResource = res.resourceId;
         } else {
             layoutResource = R.layout.screen_custom_title;
         }
         // XXX Remove this once action bar supports these features.
         removeFeature(FEATURE_ACTION_BAR);
     } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
         // If no other features and not embedded, only need a title.
         // If the window is floating, we need a dialog layout
         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 {
             layoutResource = R.layout.screen_title;
         }
         // System.out.println("Title!");
     } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
         layoutResource = R.layout.screen_simple_overlay_action_mode;
     } else {
         // Embedded, so no decoration is needed.
         layoutResource = R.layout.screen_simple;
         // System.out.println("Simple!");
     }

     mDecor.startChanging();
     mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

     ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

上面是根據(jù)設(shè)置不同的屬性,加載不同的layout文件,我們就找個最簡單布局看看,看最后一個else語句的代碼

else {
         // Embedded, so no decoration is needed.
         layoutResource = R.layout.screen_simple;
         // System.out.println("Simple!");
     }

我們看看這個布局screen_simple是啥?

<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/layout/screen_simple.xml
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/

This is an optimized layout for a screen, with the minimum set of features
enabled.
-->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:fitsSystemWindows="true"
  android:orientation="vertical">
  <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:id="@android:id/content"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:foregroundInsidePadding="false"
       android:foregroundGravity="fill_horizontal|top"
       android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

然后再看下這行代碼

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

這句代碼是將screen_simple的layout id中的view添加到mDecor中,而mDecor是DecorView,DecorView是一個FrameLayout,
看下onResourcesLoaded方法:

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
      if (mBackdropFrameRenderer != null) {
          loadBackgroundDrawablesIfNeeded();
          mBackdropFrameRenderer.onResourcesLoaded(
                  this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                  mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                  getCurrentColor(mNavigationColorViewState));
      }

      mDecorCaptionView = createDecorCaptionView(inflater);
      final View root = inflater.inflate(layoutResource, null);
      if (mDecorCaptionView != null) {
          if (mDecorCaptionView.getParent() == null) {
              addView(mDecorCaptionView,
                      new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
          }
          mDecorCaptionView.addView(root,
                  new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
      } else {

          // Put it below the color views.
          addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
      }
      mContentRoot = (ViewGroup) root;
      initializeElevation();
  }

上面中的root變量就是screen_simple資源解析后得到的,我們看到其中的addView(), 這是addView()是在DecorView類中調(diào)用的,就相當(dāng)于我們在繼承的LinearLayout中addView()那么就是把view添加到LinearLayout中,

上面是關(guān)鍵代碼,generateLayout()方法返回的是contentParent變量, 而這個變量是在這賦值的

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

而變量ID_ANDROID_CONTENT是:

/**
   * The ID that the main layout in the XML layout file should have.
   */
  public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

我們從注釋中就知道了我們通過setContentView(layout的id)的布局文件最終是賦值給mContentParent變量了.
經(jīng)過上面的流程分析,我們繼續(xù)看PhoneWIndow類中的setContentView()方法,這個方法還有一個重要的知識點:

if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
          final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                  getContext());
          transitionTo(newScene);
      } else {
          mLayoutInflater.inflate(layoutResID, mContentParent);
      }

就是else代碼塊中的

mLayoutInflater.inflate(layoutResID, mContentParent);

這是將我們的layout資源通過解析后生成的view對象放到mContentParent的View中,所以這個view一定是一個ViewGroup,因為只有這樣才能存放子view

根據(jù)上面的分析, 我們畫圖來看看其關(guān)系


image.png

上面分析到了我們最終設(shè)置的layout布局文件最終是加載到了FrameLayout中去了,在這里我們可以利用studio工具,給我們查看視圖:Tools->layout inspector


image.png

我們現(xiàn)在分析到設(shè)置后的布局加載到了mContentParent中去了,但是layout資源文件如何解析成view對象添加到mContentParent中,還沒涉及到.要繼續(xù)分析還得回到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) {
          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;
  }

記得我們就要分析我們設(shè)置的layout id資源如何添加到mContentParent這個變量中了,關(guān)鍵代碼是這行:

mLayoutInflater.inflate(layoutResID, mContentParent);

那我們就看看LayoutInflater類的inflate()方法,

 /**
   * Inflate a new view hierarchy from the specified xml resource. Throws
   * {@link InflateException} if there is an error.
   *
   * @param resource ID for an XML layout resource to load (e.g.,
   *        <code>R.layout.main_page</code>)
   * @param root Optional view to be the parent of the generated hierarchy.
   * @return The root View of the inflated hierarchy. If root was supplied,
   *         this is the root View; otherwise it is the root of the inflated
   *         XML file.
   */
  public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
      return inflate(resource, root, root != null);
  }

我們知道它又調(diào)用了帶三個參數(shù)的inflater()方法,第三個參數(shù)非常重要, 是一個boolean值,z這里傳入的是true,再次點擊進入

/**
   * Inflate a new view hierarchy from the specified xml resource. Throws
   * {@link InflateException} if there is an error.
   *
   * @param resource ID for an XML layout resource to load (e.g.,
   *        <code>R.layout.main_page</code>)
   * @param root Optional view to be the parent of the generated hierarchy (if
   *        <em>attachToRoot</em> is true), or else simply an object that
   *        provides a set of LayoutParams values for root of the returned
   *        hierarchy (if <em>attachToRoot</em> is false.)
   * @param attachToRoot Whether the inflated hierarchy should be attached to
   *        the root parameter? If false, root is only used to create the
   *        correct subclass of LayoutParams for the root view in the XML.
   * @return The root View of the inflated hierarchy. If root was supplied and
   *         attachToRoot is true, this is root; otherwise it is the root of
   *         the inflated XML file.
   */
  public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
      final Resources res = getContext().getResources();
      if (DEBUG) {
          Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
      }

      View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
      if (view != null) {
          return view;
      }
      XmlResourceParser parser = res.getLayout(resource);
      try {
//真正去解析xml文件生成View對象
          return inflate(parser, root, attachToRoot);
      } finally {
          parser.close();
      }
  }

從上面的方法中,最重要的還是inflater()方法, 這個方法是將xml文件解析成View的過程

 /**
     * Inflate a new view hierarchy from the specified XML node. Throws
     * {@link InflateException} if there is an error.
     * <p>
     * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
     * reasons, view inflation relies heavily on pre-processing of XML files
     * that is done at build time. Therefore, it is not currently possible to
     * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
     *
     * @param parser XML dom node containing the description of the view
     *        hierarchy.
     * @param root Optional view to be the parent of the generated hierarchy (if
     *        <em>attachToRoot</em> is true), or else simply an object that
     *        provides a set of LayoutParams values for root of the returned
     *        hierarchy (if <em>attachToRoot</em> is false.)
     * @param attachToRoot Whether the inflated hierarchy should be attached to
     *        the root parameter? If false, root is only used to create the
     *        correct subclass of LayoutParams for the root view in the XML.
     * @return The root View of the inflated hierarchy. If root was supplied and
     *         attachToRoot is true, this is root; otherwise it is the root of
     *         the inflated XML file.
     */
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            //開始追蹤
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
          //上下文賦值
            final Context inflaterContext = mContext;
            //獲取到activity_main.xml文件中的屬性
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
          //把root賦值給result變量
            View result = root;

            try {
                advanceToRootNode(parser);
                //獲取到屬性的名稱
                final String name = parser.getName();
              //如果是debug模式下打印日記
                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }
                //如果是merge標(biāo)簽的話,再判斷如果root==null或者attachToRoot為false的情況下          就報錯
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                  //跟我們相關(guān)的是從這里開始查看
                    // 開始創(chuàng)建View對象,這是創(chuàng)建我們的根view對象
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(
                        getParserStateDescription(inflaterContext, attrs)
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

上面部分代碼我做了注釋,我們從創(chuàng)建view對象開始查看, createViewFromTag()方法就是創(chuàng)建我們xml布局文件的根View對象的,我們看下這個方法的源碼:

/**
   * Creates a view from a tag name using the supplied attribute set.
   * <p>
   * <strong>Note:</strong> Default visibility so the BridgeInflater can
   * override it.
   *
   * @param parent the parent view, used to inflate layout params
   * @param name the name of the XML tag used to define the view
   * @param context the inflation context for the view, typically the
   *                {@code parent} or base layout inflater context
   * @param attrs the attribute set for the XML tag used to define the view
   * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
   *                        attribute (if set) for the view being inflated,
   *                        {@code false} otherwise
   */
  @UnsupportedAppUsage
  View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
          boolean ignoreThemeAttr) {
      if (name.equals("view")) {
          name = attrs.getAttributeValue(null, "class");
      }

      // Apply a theme wrapper, if allowed and one is specified.
      if (!ignoreThemeAttr) {
          final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
          final int themeResId = ta.getResourceId(0, 0);
          if (themeResId != 0) {
              context = new ContextThemeWrapper(context, themeResId);
          }
          ta.recycle();
      }

      try {
          View view = tryCreateView(parent, name, context, attrs);

          if (view == null) {
              final Object lastContext = mConstructorArgs[0];
              mConstructorArgs[0] = context;
              try {
                  if (-1 == name.indexOf('.')) {
                      view = onCreateView(context, parent, name, attrs);
                  } else {
                      view = createView(context, name, null, attrs);
                  }
              } finally {
                  mConstructorArgs[0] = lastContext;
              }
          }

          return view;
      } catch (InflateException e) {
          throw e;

      } catch (ClassNotFoundException e) {
          final InflateException ie = new InflateException(
                  getParserStateDescription(context, attrs)
                  + ": Error inflating class " + name, e);
          ie.setStackTrace(EMPTY_STACK_TRACE);
          throw ie;

      } catch (Exception e) {
          final InflateException ie = new InflateException(
                  getParserStateDescription(context, attrs)
                  + ": Error inflating class " + name, e);
          ie.setStackTrace(EMPTY_STACK_TRACE);
          throw ie;
      }
  }

這里方法的最重要的代碼是這里,因為這里是創(chuàng)建對象View的邏輯:

if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
              //判斷屬性名字是否帶. 比如androidx.appcompat.widget.AppCompatTextView和
            //TextView
              //不帶.的就是很早我們的View,比如Button,TextView
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(context, parent, name, attrs);
                } else {
                    view = createView(context, name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        return view;

從上面的代碼我們知道,真正創(chuàng)建View的對象是這個方法createView(),我們再看看這個方法:

/**
   * Version of {@link #onCreateView(View, String, AttributeSet)} that also
   * takes the inflation context.  The default
   * implementation simply calls {@link #onCreateView(View, String, AttributeSet)}.
   *
   * @param viewContext The Context to be used as a constructor parameter for the View
   * @param parent The future parent of the returned view.  <em>Note that
   * this may be null.</em>
   * @param name The fully qualified class name of the View to be create.
   * @param attrs An AttributeSet of attributes to apply to the View.
   *
   * @return View The View created.
   */
  @Nullable
  public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
          @NonNull String name, @Nullable AttributeSet attrs)
          throws ClassNotFoundException {
      return onCreateView(parent, name, attrs);
  }

最終是調(diào)用帶二個參數(shù)的onCreateView()

/**
  * Version of {@link #onCreateView(String, AttributeSet)} that also
  * takes the future parent of the view being constructed.  The default
  * implementation simply calls {@link #onCreateView(String, AttributeSet)}.
  *
  * @param parent The future parent of the returned view.  <em>Note that
  * this may be null.</em>
  * @param name The fully qualified class name of the View to be create.
  * @param attrs An AttributeSet of attributes to apply to the View.
  *
  * @return View The View created.
  */
 protected View onCreateView(View parent, String name, AttributeSet attrs)
         throws ClassNotFoundException {
     return onCreateView(name, attrs);
 }

繼續(xù)調(diào)用:

protected View onCreateView(String name, AttributeSet attrs)
          throws ClassNotFoundException {
      return createView(name, "android.view.", attrs);
  }

繼續(xù)調(diào)用:

public final View createView(String name, String prefix, AttributeSet attrs)
          throws ClassNotFoundException, InflateException {
      Context context = (Context) mConstructorArgs[0];
      if (context == null) {
          context = mContext;
      }
      return createView(context, name, prefix, attrs);
  }

最終創(chuàng)建View的對象是調(diào)用這個方法:

/**
   * Low-level function for instantiating a view by name. This attempts to
   * instantiate a view class of the given <var>name</var> found in this
   * LayoutInflater's ClassLoader.
   *
   * <p>
   * There are two things that can happen in an error case: either the
   * exception describing the error will be thrown, or a null will be
   * returned. You must deal with both possibilities -- the former will happen
   * the first time createView() is called for a class of a particular name,
   * the latter every time there-after for that class name.
   *
   * @param viewContext The context used as the context parameter of the View constructor
   * @param name The full name of the class to be instantiated.
   * @param attrs The XML attributes supplied for this instance.
   *
   * @return View The newly instantiated view, or null.
   */
  @Nullable
  public final View createView(@NonNull Context viewContext, @NonNull String name,
          @Nullable String prefix, @Nullable AttributeSet attrs)
          throws ClassNotFoundException, InflateException {
      //判斷viewContext對象是否為null
      Objects.requireNonNull(viewContext);
    //判斷name 是否為null
      Objects.requireNonNull(name);
      //使用Map集合存儲name屬性,
      Constructor<? extends View> constructor = sConstructorMap.get(name);
      if (constructor != null && !verifyClassLoader(constructor)) {
          constructor = null;
          sConstructorMap.remove(name);
      }
      Class<? extends View> clazz = null;

      try {
          //開始追蹤
          Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

          if (constructor == null) {
              // Class not found in the cache, see if it's real, and try to add it
              clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                      mContext.getClassLoader()).asSubclass(View.class);

              if (mFilter != null && clazz != null) {
                  boolean allowed = mFilter.onLoadClass(clazz);
                  if (!allowed) {
                      failNotAllowed(name, prefix, viewContext, attrs);
                  }
              }
              constructor = clazz.getConstructor(mConstructorSignature);
              constructor.setAccessible(true);
              sConstructorMap.put(name, constructor);
          } else {
              // If we have a filter, apply it to cached constructor
              if (mFilter != null) {
                  // Have we seen this name before?
                  Boolean allowedState = mFilterMap.get(name);
                  if (allowedState == null) {
                      // New class -- remember whether it is allowed
                      clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                              mContext.getClassLoader()).asSubclass(View.class);

                      boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                      mFilterMap.put(name, allowed);
                      if (!allowed) {
                          failNotAllowed(name, prefix, viewContext, attrs);
                      }
                  } else if (allowedState.equals(Boolean.FALSE)) {
                      failNotAllowed(name, prefix, viewContext, attrs);
                  }
              }
          }

          Object lastContext = mConstructorArgs[0];
          mConstructorArgs[0] = viewContext;
          Object[] args = mConstructorArgs;
          args[1] = attrs;

          try {
              //在這里我們可以知道是通過反射創(chuàng)建了View對象
              final View view = constructor.newInstance(args);
              if (view instanceof ViewStub) {
                  // Use the same context when inflating ViewStub later.
                  final ViewStub viewStub = (ViewStub) view;
                  viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
              }
              return view;
          } finally {
              mConstructorArgs[0] = lastContext;
          }
      } catch (NoSuchMethodException e) {
          final InflateException ie = new InflateException(
                  getParserStateDescription(viewContext, attrs)
                  + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
          ie.setStackTrace(EMPTY_STACK_TRACE);
          throw ie;

      } catch (ClassCastException e) {
          // If loaded class is not a View subclass
          final InflateException ie = new InflateException(
                  getParserStateDescription(viewContext, attrs)
                  + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
          ie.setStackTrace(EMPTY_STACK_TRACE);
          throw ie;
      } catch (ClassNotFoundException e) {
          // If loadClass fails, we should propagate the exception.
          throw e;
      } catch (Exception e) {
          final InflateException ie = new InflateException(
                  getParserStateDescription(viewContext, attrs) + ": Error inflating class "
                          + (clazz == null ? "<unknown>" : clazz.getName()), e);
          ie.setStackTrace(EMPTY_STACK_TRACE);
          throw ie;
      } finally {
          Trace.traceEnd(Trace.TRACE_TAG_VIEW);
      }
  }

這個代碼雖長,但是總體比較簡單,就是通過反射創(chuàng)建了View對象.下面是關(guān)鍵代碼:

clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                      mContext.getClassLoader()).asSubclass(View.class);
//獲取構(gòu)造函數(shù)
constructor = clazz.getConstructor(mConstructorSignature);
//真正通過反射得到的構(gòu)造函數(shù)創(chuàng)建view對象
final View view = constructor.newInstance(args);

通過這么大段的研究知道了xml布局文件是如何生成view對象的,接著我們繼續(xù)跟蹤代碼,還是

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)

還是跟蹤上面那個方法,經(jīng)過前面那么多的分析,可能都忘記了這個方法寫了啥代碼了,再貼下:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
      synchronized (mConstructorArgs) {
          Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

          final Context inflaterContext = mContext;
          final AttributeSet attrs = Xml.asAttributeSet(parser);
          Context lastContext = (Context) mConstructorArgs[0];
          mConstructorArgs[0] = inflaterContext;
          View result = root;

          try {
              advanceToRootNode(parser);
              final String name = parser.getName();

              if (DEBUG) {
                  System.out.println("**************************");
                  System.out.println("Creating root view: "
                          + name);
                  System.out.println("**************************");
              }

              if (TAG_MERGE.equals(name)) {
                  if (root == null || !attachToRoot) {
                      throw new InflateException("<merge /> can be used only with a valid "
                              + "ViewGroup root and attachToRoot=true");
                  }

                  rInflate(parser, root, inflaterContext, attrs, false);
              } else {
                 //這是根據(jù)xml文件創(chuàng)建它的根view對象
                  final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    //定義一個LayoutParams變量
                  ViewGroup.LayoutParams params = null;
                  //判斷root不為null才能進入這個if語句
                  if (root != null) {
                      //debug環(huán)境下才打印
                      if (DEBUG) {
                          System.out.println("Creating params from root: " +
                                  root);
                      }
                      // Create layout params that match root, if supplied
                      params = root.generateLayoutParams(attrs);
                      if (!attachToRoot) {
                          // Set the layout params for temp if we are not
                          // attaching. (If we are, we use addView, below)
                          temp.setLayoutParams(params);
                      }
                  }

                  if (DEBUG) {
                      System.out.println("-----> start inflating children");
                  }

                  // Inflate all children under temp against its context.
                  rInflateChildren(parser, temp, attrs, true);

                  if (DEBUG) {
                      System.out.println("-----> done inflating children");
                  }

                  // We are supposed to attach all the views we found (int temp)
                  // to root. Do that now.
                  if (root != null && attachToRoot) {
                      root.addView(temp, params);
                  }

                  // Decide whether to return the root that was passed in or the
                  // top view found in xml.
                  if (root == null || !attachToRoot) {
                      result = temp;
                  }
              }

          } catch (XmlPullParserException e) {
              final InflateException ie = new InflateException(e.getMessage(), e);
              ie.setStackTrace(EMPTY_STACK_TRACE);
              throw ie;
          } catch (Exception e) {
              final InflateException ie = new InflateException(
                      getParserStateDescription(inflaterContext, attrs)
                      + ": " + e.getMessage(), e);
              ie.setStackTrace(EMPTY_STACK_TRACE);
              throw ie;
          } finally {
              // Don't retain static reference on context.
              mConstructorArgs[0] = lastContext;
              mConstructorArgs[1] = null;

              Trace.traceEnd(Trace.TRACE_TAG_VIEW);
          }

          return result;
      }
  }

PhoneWindow分類課后研究

下面幾個創(chuàng)建的時候都會創(chuàng)建PhoneWindow對象
1:Activity
2:Dialog
3:PopupWindow
4:Toast

?著作權(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)容