Android setContentView 源码分析

2018-06-20  本文已影响11人  黑色海鸥

基于Android5.1.1(API 22)分析

Activity的setContentView方法解析

Activity的源码中提供了三个重载的setContentView方法,如下:

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

  public void setContentView(View view) {
      getWindow().setContentView(view);
      initWindowDecorActionBar();
  }

  public void setContentView(View view, ViewGroup.LayoutParams params) {
      getWindow().setContentView(view, params);
      initWindowDecorActionBar();
  }

先调用了getWindow()的setContentView方法,然后调用Activity的initWindowDecorActionBar方法

关于窗口Window类的一些关系

Activity, Window, PhoneWindow, DecorView直接的关系

PhoneWindow类的setContentView方法

可以看见Window类的setContentView方法都是抽象的。所以我们直接先看PhoneWindow类的setContentView(int layoutResID)方法源码,如下:

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

      final Callback cb = getCallback();
      if (cb != null && !isDestroyed()) {
          cb.onContentChanged();
      }
  }

窗口PhoneWindow类的installDecor方法

在PhoneWindow中查看installDecor源码如下:

private void installDecor() {
       if (mDecor == null) {
           mDecor = generateDecor();
           mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
           mDecor.setIsRootNamespace(true);
           if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
               mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
           }
       }
       if (mContentParent == null) {
           //根据窗口的风格修饰,选择对应的修饰布局文件,并且将id为content的FrameLayout赋值给mContentParent
           mContentParent = generateLayout(mDecor);
           //......
           //初始化一堆属性值
       }
   }
protected ViewGroup generateLayout(DecorView decor) {
      // Apply data from current theme.

      TypedArray a = getWindowStyle();

      //......
      //依据主题style设置一堆值进行设置

      // Inflate the window decor.

      int layoutResource;
      int features = getLocalFeatures();
      //......
      //根据设定好的features值选择不同的窗口修饰布局文件,得到layoutResource值

      //把选中的窗口修饰布局文件添加到DecorView对象里,并且指定contentParent值
      View in = mLayoutInflater.inflate(layoutResource, null);
      decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
      mContentRoot = (ViewGroup) in;

      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      if (contentParent == null) {
          throw new RuntimeException("Window couldn't find content container view");
      }

      //......
      //继续一堆属性设置,完事返回contentParent
      return contentParent;
  }
番外,还记得我们平时写应用Activity时设置的theme或者feature吗(全屏啥的,NoTitle等)?我们一般是不是通过XML的android:theme属性或者java的requestFeature()方法来设置的呢?譬如:
通过java文件设置:

requestWindowFeature(Window.FEATURE_NO_TITLE);

通过xml文件设置:

android:theme="@android:style/Theme.NoTitleBar"

setContentView还会调用一个Callback接口的成员函数onContentChanged来通知对应的Activity组件视图内容发生了变化。

PhoneWindow类的setContentView方法中最后调运了onContentChanged方法。我们这里看下setContentView这段代码,如下:

public void setContentView(int layoutResID) {
       ......
       final Callback cb = getCallback();
       if (cb != null && !isDestroyed()) {
           cb.onContentChanged();
       }
   }

setContentView源码分析总结

setContentView完以后Activity显示界面初探

简单说明setContentView之后怎么被显示出来的(注意:Activity调运setContentView方法自身不会显示布局的)。

final void handleResumeActivity(IBinder token,
           boolean clearHide, boolean isForward, boolean reallyResume) {
       // If we are getting ready to gc after going to the background, well
       // we are back active so skip it.
       ......
       // TODO Push resumeArgs into the activity for consideration
       ActivityClientRecord r = performResumeActivity(token, clearHide);

       if (r != null) {
           ......
           // If the window hasn't yet been added to the window manager,
           // and this guy didn't finish itself or start another activity,
           // then go ahead and add the window.
           ......
           // If the window has already been added, but during resume
           // we started another activity, then don't yet make the
           // window visible.
           ......
           // The window is now visible if it has been added, we are not
           // simply finishing, and we are not starting another activity.
           if (!r.activity.mFinished && willBeVisible
                   && r.activity.mDecor != null && !r.hideForNow) {
               ......
               if (r.activity.mVisibleFromClient) {
                   r.activity.makeVisible();
               }
           }
           ......
       } else {
           // If an exception was thrown when trying to resume, then
           // just end this activity.
           ......
       }
   }

我们看下Activity的makeVisible方法,如下:

void makeVisible() {
       if (!mWindowAdded) {
           ViewManager wm = getWindowManager();
           wm.addView(mDecor, getWindow().getAttributes());
           mWindowAdded = true;
       }
       mDecor.setVisibility(View.VISIBLE);
   }

总结

总结列表

参考

工匠若水

上一篇 下一篇

猜你喜欢

热点阅读