setContentView()

参考Android 8.0源码

  • Window
      每一个Activity都持有一个Window对象,Window是一个抽象类,提供了绘制窗口的一组通用API。
  • PhoneWindow
      Android为Window提供了唯一的实现类PhoneWindow,也就是说Activity中Window实例是一个PhoneWindow对象,我们通过PhoneWindow具体去绘制窗口。但是PhoneWindow终究是Window,不具备多少View相关的能力。不过PhoneWindow中持有一个Android中非常重要的一个View对象DecorView。DecorView继承FrameLayout,是所有应用窗口(Activity界面)的根View。简而言之,PhoneWindow类是把一个FrameLayout类即DecorView对象进行一定的包装,将它作为应用窗口的根View,并提供一组通用的窗口操作接口。PhoneWindow是Android中最基本的窗口系统,每个Activity均会创建一个PhoneWindow对象,是Activity和整个View系统交互的接口。
      也就是说,每一个Activity持有一个PhoneWindow的对象,而一个PhoneWindow对象持有一个DecorView的实例,Activity中View相关的操作其实大都是通过DecorView来完成的。
  • DecorView
      DecorView继承FrameLayout,对普通的FrameLayout进行功能的扩展,是所有应用窗口(Activity界面)的根View。本来是PhoneWindow的内部类,在我所看的Android 8.0版本源码中,从PhoneWindow类中抽取了出来。主要有以下功能:
    1 . Dispatch ViewRoot分发来的key、touch、trackball等外部事件;
    2 . DecorView有一个直接的子View,我们称之为System Layout,这个View是从系统的Layout.xml中解析出的,它包含当前UI的风格,如是否带title、是否带process bar等,可以称这些属性为Window decorations;
    3 . 作为PhoneWindow与ViewRoot之间的桥梁,ViewRoot通过DecorView设置窗口属性。可以通过View view = getWindow().getDecorView();获取。
    4 . DecorView只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏、标题栏、内容显示栏这三块区域。DecorView里面TitleView表示标题,可以设置requestWindowFeature(Window.FEATURE_NO_TITLE)取消。ContentView表示内容栏,是一个id为content的FrameLayout。我们平常在Activity中使用setContentView()就是设置这个的。

跟踪setContentView()方法的

  当系统(一般是ActivityManagerService)配置好启动一个Activity的相关参数(包含Activity对象和Window对象信息)后,就回回调Activity的onCreate()方法,在其中我们通过设置setContentView()方法类设置该Activity的显示界面。我们通过在Activity中设置setContentView()方法,将写好的布局文件展示在Activity的内容显示区域中。其实就是通过不断地传递,把布局文件对应的资源id一只传递到这个Activity对应的DecorView中,DecorView继承自FrameLayout,当DecorView接收到来自Activity传递过来的布局id后,通过inflater()方法把布局资源id转换为一个View,然后把这个布局View添加在自身中,我们就可以在Activity中看到指定的布局了。

  我们从Activity.java类中的setContentView()开始分析。

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

  getWindow()方法返回的是Window对象,但是Window是一个抽象类,我们使用的是它的实现类PhoneWindow。在PhoneWindow.java中,setContentView()方法如下:

@Override
public void setContentView(int layoutResID) {
    //mContentParent是我们的Activity显示的界面;
    //如果mContentParent为null,就生成DecorView,然后加载生成;
    //如果mContentParent不为null,就说明已经存在DecorView了,就直接加载要设置的布局文件到DecorView作为子View;
    if (mContentParent == null) {
        //生成DecorView;
        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 {
        //将layoutResID的布局添加到mContentParent中;
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;    
}
//构建DecorView,并且初始化标题栏和mContentParent(我们那要显示的内容区域);
private void installDecor() {
    if(mDecor == null){
        //构建DecorView;
        mDecor = generateDecor();       
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    }
    if(mContentParent == null){
        //构建显示内容的区域;
        mContentParent = generateLayout(mDecor);
        //设置Title等;
        //代码省略;
    }
}

  在setContentView()中基本流程如下:
1 . 构建mDecor对象,mDecor就是整个窗口的顶层视图,它主要包含了Title和Content View两个区域,Title区域是我们的标题栏,Content View区域是显示xml布局内容中的区域。
2 . 设置一些关于窗口的属性,初始化标题栏区域和内容显示区域。

protected DecorView generateDecor(int featureId) {
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext().getResources());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    //获取DecorView;
    return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
    //获取窗口的Style属性;
    TypedArray a = getWindowStyle();
    //设置一些窗口属性等,代码省略;
    //布局资源id;
    int layoutResoure;
    int features = getLocalFeatures();
    //代码省略;
    //R.layout.xxx有好几种布局,这里只举了一种布局;
    layoutResource = R.layout.screen_simple;
    //代码省略;
    mDecor.startChanging();
    //加载布局资源到DecorView中;
    mDecor.onResourcesLoaded(mLayoutInflater,layoutResource);
    //获取到显示内容的内容区域,其实就是FrameLayout,ID_ANDROID_CONTENT就是com.android.internal.R.id.content;
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    //代码省略;
    //返回内容显示区域;
    return contentParent;
}
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    //代码省略;
    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 {
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}

  在installDecor()方法中,会加载布局到DecorView中。
在DecorView中,有两个区域,title(系统标题栏)区域和content(我们设置的layout布局显示)区域。
  其中在generateLayout()方法中,有好几种系统内置的layout布局文件,会根据用户设置的一些属性来选择使用哪种布局,比如FEATURE_NO_TITLE时就选择没有title的布局文件。generateLayout()方法只是获取content区域的视图,其实就是一个FrameLayout。我们使用setContentView()方法,这个方法中的参数值代表的布局就是放在content里面的。
  也就是说安卓系统预先设置了一些布局资源,这些布局资源里面有一个留给开发者设置窗口内容的区域,也就是content区域,我们通过setContentView()设置的布局会被添加到content布局中,这个content布局是一个FrameLayout。

  当Activity启东市,通过onCreate()方法让用户设置自己的界面,系统将这个布局界面添加到一个内置的布局界面的content区域中,此时,DecorView就建立起来了。然后调用onStart()方法,并且在调用onResume()方法之前将DecorView添加到WindowManager中,并且设置Activity为可见状态,然后通知ActivityManagerService,该Activity已经变为resume状态了,使系统能够渲染Activity的视图,到这里,Activity视图就显示在手机等设备上面了。

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--------------------last line for now--------------------