本系列主要是探讨 View 的绘制过程及部分相关的实现机制的源码分析

setContentView 分析

相关关系

Activity 中有 Window 成员 实例化为 PhoneWindow PhoneWindow 是抽象 Window 类的实现类

Window 提供了绘制窗口的通用 API PhoneWindow 中包含了 DecorView 对象 是所有窗口 (Activity 界面) 的根 View

具体的构如下

具体的可以通过 hierarchyviewer 工具分析一下

PhoneWindow 的 setContentView 分析

Window 类的 setContentView 方法 而 Window 的 setContentView 方法是抽象的 所以查看 PhoneWindow 的 setContentView()

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

@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 下的所有 View
// 又因为这个的存在 我们可以多次使用setContentView()
mContentParent.removeAllViews();
}
// 判断是否使用了 Activity 的过度动画
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
// 设置动画场景
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 将资源文件通过 LayoutInflater 对象装换为 View 树
// 在 PhoneWindow 的构造函数中 mLayoutInflater = LayoutInflater.from(context);
mLayoutInflater.inflate(layoutResID, mContentParent);
}

  //View中
  /**
   * Ask that a new dispatch of {@link <span class="hljs-comment">#onApplyWindowInsets(WindowInsets)} be performed.</span>
   */
  // public void <span class="hljs-function"><span class="hljs-title">requestApplyInsets</span></span>() {
  //     requestFitSystemWindows();
  // }
  mContentParent.requestApplyInsets();
  final Callback cb = getCallback();
  <span class="hljs-keyword">if</span> (cb != null &amp;&amp; !isDestroyed()) {
      cb.onContentChanged();
  }

}

@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}

  <span class="hljs-keyword">if</span> (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
      view.setLayoutParams(params);
      final Scene newScene = new Scene(mContentParent, view);
      transitionTo(newScene);
  } <span class="hljs-keyword">else</span> {
    //已经为View 直接使用View的addView方法追加到当前mContentParent中
      mContentParent.addView(view, params);
  }
  mContentParent.requestApplyInsets();
  final Callback cb = getCallback();
  //调用CallBack接口的onContentChange来通知Activity组件视图发生了变化
  <span class="hljs-keyword">if</span> (cb != null &amp;&amp; !isDestroyed()) {
      cb.onContentChanged();
  }

}

复制代码
  1. installDecor 方法
  //截取部分主要分析代码
  private void installDecor() {
      if (mDecor == null) {
          //如果mDecor为空则创建一个DecorView实例
          // protected DecorView generateDecor() {
          //   return new DecorView(getContext(), -1);
          // }
          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.
       //根据当前style修饰相应样式
   TypedArray a = getWindowStyle();

   ...
   //一堆<span class="hljs-keyword">if</span>判断

   // 增加窗口修饰

   int layoutResource;
   int features = getLocalFeatures();

   ...
   //根据features选择不同的窗帘修饰布局文件得到
   //把选中的窗口修饰布局文件添加到DecorView中, 指定contentParent的值
   View <span class="hljs-keyword">in</span> = mLayoutInflater.inflate(layoutResource, null);
   decor.addView(<span class="hljs-keyword">in</span>, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
   mContentRoot = (ViewGroup) <span class="hljs-keyword">in</span>;

   ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
   <span class="hljs-keyword">if</span> (contentParent == null) {
       throw new RuntimeException(<span class="hljs-string">"Window couldn't find content container view"</span>);
   }

   ...
   <span class="hljs-built_in">return</span> contentParent;

}

复制代码

该方法的主要功能为 根据窗口的 style 为该窗口选择不同的窗口根布局文件 将 mDecor 作为根视图将窗口布局添加, 获取 id 为 content 的 FrameLayout 返回给 mContentParent 对象 实质为阐释 mDecor 和 mContentParent 对象

  1. (扩展) 关于设置 Activity 属性需要在 setContentView 方法之前调用的问题

在设置 Activity 属性的时候 比如 requestWindowFeature(Window.FEATURE_NO_TITLE) 需要在 setContentView 方法之前调用

  public boolean requestFeature(int featureId) {
      if (mContentParent != null) {
          throw new AndroidRuntimeException("requestFeature() must be called before adding content");
      }
      ...
  }
复制代码
  1. onContentChanged 方法

在 PhoneWindow 中没有重写 getCallback 相关方法 而在 Window 类下

  /**
   * Return the current Callback interface for this window.
   */
  public final Callback getCallback() {
      return mCallback;
  }
复制代码

mCallback 相关的赋值方法

  /**
   * Set the Callback interface for this window, used to intercept key
   * events and other dynamic operations in the window.
   *
   * @param callback The desired Callback interface.
   */
  public void setCallback(Callback callback) {
      mCallback = callback;
  }
复制代码

setCallback 方法在 Activity 中被使用

  final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        ...
        mWindow.setCallback(this);
        ...
  }
复制代码

说明 Activity 实现了 Window 的 CallBack 接口 然后在 Activity 中找到 onContentChanged 方法

  public void onContentChanged() {
  }
复制代码

对空方法. 说明在 Activity 的布局改动时 (setContentView 或者 addContentView 方法执行完毕后会调用改方法) 所以各种 View 的 findViewById 方法什么的可以放在这里

  1. setContentView 源码总结
  • 创建一个 DecorView 的对象 mDector 该 mDector 将作为整个应用窗口的根视图
  • 根据根据 Feature 等 style theme 创建不同的窗口修饰布局文件 并且通过 findViewById 获取 Activity 布局文件该存放的地方
  • 将 Activity 的布局文件添加至 id 为 content 的 FrameLayout 内
  • 执行到当前页面还没有显示出来
  1. Activity 页面显示

我们都知道 Activity 的实际开始于 ActivityThread 的 main 方法 当该方法调运完之后会调用该类的 performLaunchActivity 方法来创建要启动的 Activity 组件 这个过程中还会为该 Activity 组件创建窗口对象和视图对象 当组件创建完成后用过调用该类的 handleResumeActivity 方法将其激活

  final void handleResumeActivity(IBinder token,
             boolean clearHide, boolean isForward, boolean reallyResume) {
               ...
             if (!r.activity.mFinished && willBeVisible
                     && r.activity.mDecor != null && !r.hideForNow) {
                 ...
                 if (r.activity.mVisibleFromClient) {
                     r.activity.makeVisible();
                     //这里这里 通过调用Activity的makeVisible方法来显示我们通过setContentView创建的mDecor
                 }
                 ...
             }
         } else {
           ...
         }
     }
复制代码
  //Activity的makeVisible方法
  void makeVisible() {
       if (!mWindowAdded) {
           ViewManager wm = getWindowManager();
           wm.addView(mDecor, getWindow().getAttributes());
           mWindowAdded = true;
       }
       mDecor.setVisibility(View.VISIBLE);
   }
复制代码

至此通过 setContentView 方法设置的页面才最后显示出来

  • Android

    开放手机联盟(一个由 30 多家科技公司和手机公司组成的团体)已开发出 Android,Android 是第一个完整、开放、免费的手机平台。

    293 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...