android setContentView流程分析

为什么要分析setContentView() 方法的代码呢?这是xml布局文件如何加载view到内存中的流程,如果懂了这个流程的话,一些功能我们就可以实现,比如换肤,比如如何改变项目中所有textview的文字颜色或者大小等等,所以我们学习很有必要的,既然要学习当然是从我们继承的Activity学起了

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. 授权,把……委托给;选派(某人做某事)

类似代理的意思,这里最终会到Activity类的setContentView()方法中,只是中间做了很多兼容性操作,这个我们就没必要看了,那我们就直接到Activity类中的setContenView()方法,看它的代码实现.

/**
   * 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();
  }

读系统源码啊,注释是一个很重要的信息,它会给我们很多启示,比如这个注释就说
设置Activity内容从layout资源文件中, 这资源会填充到顶级的Activity中的view上的

我们看到Activity中的setContentView() 方法,它首先会调用这个:

getWindow().setContentView(layoutResID);

意思是说我们传递过来的layout资源文件会设置到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类中全局搜索下: 发现最终是在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);
      }

现在Activity中的attch()方法是在那被调用的呢?这里就要涉及到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();
//这里是创建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);
          }
      }

然后下面就是创建Activity后调用了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);
            //这里调用了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;
              }

在这个方法中我们还可以看到调用了Activity的onCreate()方法

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

所以我们知道attach()方法要早于onCreate()方法调用的.现在我们知道了从Activity中的setContentView()方法到Window类中的setContentView()方法,也就是说我们的layout资源最终是设置给了Window类中的setContentView()上了资源给它了,现在我们看看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 {

发现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,就调用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);
              }
          }
      }
  }

在这里我们不可能所有源码都要弄清楚到底是做什么的,我们只要看跟我们相关的,首先我们看第一个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;

发现DecorView是一个继承了FrameLayout的View对象,那么好现在我们继续看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变量了,那么我们继续跟踪下去,看看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());
   }

在这个方法中,我们没必要知道什么,因为跟我们今天研究的没有关系,只要知道generateDecor()方法返回的是一个DecorView对象就够了
现在继续看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;

看下这个变量的注释:

这是放置窗口内容的视图。
mDecor本身,或者mDecor的子元素,内容存放的地方

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;
  }

发现这个方法太长了,我们也只要看关键的地方就行

// 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);

上面是根据设置不同的属性,加载不同的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类中调用的,就相当于我们在继承的LinearLayout中addView()那么就是把view添加到LinearLayout中,

上面是关键代码,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变量了.
经过上面的流程分析,我们继续看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

根据上面的分析, 我们画图来看看其关系


image.png

上面分析到了我们最终设置的layout布局文件最终是加载到了FrameLayout中去了,在这里我们可以利用studio工具,给我们查看视图:Tools->layout inspector


image.png

我们现在分析到设置后的布局加载到了mContentParent中去了,但是layout资源文件如何解析成view对象添加到mContentParent中,还没涉及到.要继续分析还得回到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;
  }

记得我们就要分析我们设置的layout id资源如何添加到mContentParent这个变量中了,关键代码是这行:

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);
  }

我们知道它又调用了带三个参数的inflater()方法,第三个参数非常重要, 是一个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标签的话,再判断如果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 {
                  //跟我们相关的是从这里开始查看
                    // 开始创建View对象,这是创建我们的根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;
        }
    }

上面部分代码我做了注释,我们从创建view对象开始查看, createViewFromTag()方法就是创建我们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;
      }
  }

这里方法的最重要的代码是这里,因为这里是创建对象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;

从上面的代码我们知道,真正创建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);
  }

最终是调用带二个参数的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);
 }

继续调用:

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

继续调用:

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);
  }

最终创建View的对象是调用这个方法:

/**
   * 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 {
              //在这里我们可以知道是通过反射创建了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);
      }
  }

这个代码虽长,但是总体比较简单,就是通过反射创建了View对象.下面是关键代码:

clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                      mContext.getClassLoader()).asSubclass(View.class);
//获取构造函数
constructor = clazz.getConstructor(mConstructorSignature);
//真正通过反射得到的构造函数创建view对象
final View view = constructor.newInstance(args);

通过这么大段的研究知道了xml布局文件是如何生成view对象的,接着我们继续跟踪代码,还是

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

还是跟踪上面那个方法,经过前面那么多的分析,可能都忘记了这个方法写了啥代码了,再贴下:

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 {
                 //这是根据xml文件创建它的根view对象
                  final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    //定义一个LayoutParams变量
                  ViewGroup.LayoutParams params = null;
                  //判断root不为null才能进入这个if语句
                  if (root != null) {
                      //debug环境下才打印
                      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分类课后研究

下面几个创建的时候都会创建PhoneWindow对象
1:Activity
2:Dialog
3:PopupWindow
4:Toast

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容