Activity加载view6.0源码分析---setContentView

时间:2022-04-24
本文章向大家介绍Activity加载view6.0源码分析---setContentView,主要内容包括Chapter One:认识Activity的布局、Chapter Two:activity加载view布局----始于setContentView、Chapter Three,总结说明、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

本篇博文介绍三个方面的知识

chapter One:认识Activity的布局

chapter Two:启动activity时的布局--从setContentView说起

chapter Three:总结说明

View这个东西,是构成页面Activity最基本的元素,所以可想而知他到底有多重要。在研究activity组件的view加载之前,先整体认

识下activity的布局,有助于更好的去理解setContentView方法

Chapter One:认识Activity的布局

对于研究布局这种东西,必须要掌握一些视图工具,在这里推荐一个sdk查看视图的工具sdktoolshierarchyviewer,随意找一个界

面去查看activity的view视图

在这个activity界面中我把导航栏给隐藏了,所以不存在导航栏,根据这张图的话大致可以看到一个activity的布局,再结合对

setContentView的研究,可以总结出activity的布局图如下:

从这张activity的布局图可以看到:一个activity对应一个应用窗口mWindow,应用窗口mWindow包括activity的顶级view是

mDecorView,mDecorView包括状态栏statusbar和导航栏navigationbar以及要加载activity布局的view-----------------------

mDecorContentParent,该view又包括一个标题栏titlebar和activity的内容布局contentparent。在contentParent中就是该

activity的view树。

  1. mWindow:Window对象,Window是一个抽象类,是activity的顶层外观和行为的代理。会往windowmanage中添加该类的一个实例作为顶层view。window提供基本的UI代理,比如背景啊,标题区域啊,按键处理啊等等,Window只有一个实现类PhoneWindow,所以mWindow对象实际是PhoneWindow对象。当启动一个activity的过程中会初始化一个属于Phonewindow的window对象。Phonewindow对象的创建在activity的attach方法中
  2. mDecor:DecorView对象,继承自framelayout,是window窗口的 顶级view,包含window的装饰。类的定义位于PhoneWindow.java中
  3. mDecorContentParent:DecorContentParent对象,实现类是ActionbarOverlayLayout,属于activity布局的最外层view,包括标题栏和activity的内容布局
  4. mContentParent:activity的内容布局,继承自ViewGroup,用来加载存放activity的view树,如果没有标题栏,那么mContentparent的大小回合mDecorContentParent相等,以此类推
  5. 状态栏:statusbar,对应的id为statusBarBackground,在PhoneWindow中会加载,当window属性发生改变时会刷新导航栏。但不论是导航栏和状态栏,从这个id也可以看出,PhoneWindow只是加载他们的background,即相当于只加载一个view的占位,先告诉应用窗口,系统窗口要求将状态栏和导航栏布局在这里,你不要占用,但此时不会加载导航栏和状态栏的view,只是绘制背景而已
  6. 导航栏:navigationbar,对应的id为navigationBarBackground,在PhoneWindow中会加载,当window属性发生改变时会刷新状态栏
  7. 标题栏:titlebar,对于导航栏,状态栏和标题栏的存在与否,与window的属性特征有关,在加载view时所以会去判断window的属性特征,进而决定是否要加载这三者

对activity的布局大致有个了解之后,就开始去分析activity启动后加载view的流程

Chapter Two:activity加载view布局----始于setContentView

         对于activity的布局的加载大致分为两部分,一部分是加载view,另一部分是将view绑定到应用窗口Window。其中这两个步

骤中将view绑定到window是在启动activity时完成的操作,是将mDecor绑定到window。然后再往mDecor中添加各种view。对于

activity的启动过程留待以后进行分析,现在分析加载view---始于Activity.java的setContentView方法,看一下加载view的流程。

可以看到代码流程很简单,从Activity.java的setContentView方法进入,到PhonewWindow.java的setContentView方法进行一系

列处理,接下来进入代码进行分析

1,Activity.java的setContentView方法,代码路径androidframeworksbasecorejavaandroidapp

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

源码中对该方法的解释是,从一个layout文件中取出view设置成activity的content,该资源文件会被填充,并遍历文件中的所有

view添加到activity。意思就是填充一个资源文件,加载view。做了两件事儿

  • 一是getWindow获取到Window对象,然后去调用Window的setContentView方法。
  • 二是initWindowDecorActionbar(),创建actionbar对象,填充mDecor下的actionbarView,并把view加载上去(博主猜测是在Window的setContentView方法中只是填充一个actionbar的占位,然后initWindowDecorActionbar()完成view的加载)

重点研究第一步:getWindow().setContentView方法。

首先一个问题,为什么我要说getWindow.setContentView调用的是PhoneWindow中的setContentView方法??

解疑:查看getWindow方法

 public Window getWindow() {
        return mWindow;
    }

返回的是activity的mWindow对象,对于mWindow对象的创建也是在Activity.java中的attach方法中

    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对象

           mWindow = new PhoneWindow(this);
                //设置mWindow回调为该activity,这个在接下来setContentView的分析中会触发回调
                 mWindow.setCallback(this);


}

同时,进入到Window.java中也可以看到这一点:

/**
 * 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类的说明是:

Window是一个抽象类,是最顶层的窗口的外观和行为的代理,window的实例应该被作为最顶层的UI添加到WindowManage

中。Window提供了基本的ui,比如背景,标题区域,默认的按键处理过程等等。Window只有一个唯一的实现类PhoneWindow,

当需要Window对象时需要去初始化PhoneWindow。

至此,对于Activity中的mWindow对象大致有了一个清晰的认识了:他是个PhoneWindow对象,Window.java中方法的实现在

PhoneWindow.java中

2,PhoneWindow.java中的setContentView方法,代码路径androidframeworksbasecorejavacomandroidinternalpolicy

 @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) {
            //实例化DecorView对象和mContentParent对象
                   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()) {
            //通知activity内容已经改变,触发onContentChanged方法
             cb.onContentChanged();
        }
    }

先来总结一下代码的流

在新启动一个activity时mContentParent还未绑定id,此时mContentParent为null。从代码流程图中可以看出setContentView做了三件事 installDecor实例化DecorView对象和mContentParent对象 填充layout文件 通知activity布局已经改变 为什么说是通知activity布局已经改变呢?这是因为在Activity.java的attach方法中mWindow对象设置了callback为this,所以在getCallback时获取到的cb为当前与该window对应的activity。 3,PhoneWindow.java中的installDecor方法分析-----实例化DecorView对象和mContentParent对象

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
                    ...............
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            
                }
            }

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

。。。。。。。。
。。。。。。。。 }
    }

在创建一个activity时mDecor和mContentParent均为null

  • 借助generateDecor方法实例化mDecor,即获取到activity的最顶级的view
  • 借助generateLayout方法实例化mContentParent对象,并且根据window的不同Feature来选择对应的布局文件(总之,generatelayout其实就是根据当前的window的特征属性feature来加载内容布局,并获取到当前布局的最外层view。也就是说
  • generatelayout本质就是根据activity的theme主题来找到对应的xml布局并且获取到id为content的ViewGroup赋给mContentParent)
  • 获取到mDecorParent对象,并且根据getLocalFeature获取到的Feature来设置(这也就说明了在自定义Activity时为什么要将
  • getWindow.requestFeature方法卸载setContentView方法之前)
  • 对title进行隐藏或者是设置内容的操作
  • 如果需要切入切出动画,那么就获取到各种动画资源

接下里对installDecor中某些代码做一些分析

mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

用于对焦点的传递设置:只有当子view不想获取焦点时mDecor才会去获取焦点

mDecor.postOnAnimation(mInvalidatePanelMenuRunnable)

去开启初始化menu菜单的线程

在这里说明一句,为什么requestFeature要写在setContentView前面,这是因为在调用setContentView时会获取到window的各种

feature进行一些判断设置。

4,PhoneWindow.java中的generateLayout方法研究

第一步:首先是获取到window的布局style

     TypedArray a = getWindowStyle();

第二步,获取到各种属性并进行requestFeature的设置

第三步,通过获取到的window的布局去获取window的各种属性,并根据window的各种属性去选择不同的layout的文件,比如标题栏

是否隐藏,window是悬浮窗还是全屏,等等问题。当然因为在3.0和4.0以及5.0对于menukey的支持不同,所以会有一个与版本相关

的 一个判断。至于这个版本之间有什么不同可以参考总结说明中列出来的文件。

其实generatelayout就做了一件事,那就是根据window的各种属性去获取不同的xml文件。

Chapter Three,总结说明

  • setContentView执行流程中主要涉及到3个类PhoneWindow.java,Activity.java和Window.java
  • Window和windowmanager中的各种feature和flag的style对应的各种含义以及动画style在androidandroidframeworksbasecoreresresvaluesattrs.xml文件中有注释说明
  • 在menu键的设置中涉及到了版本问题,包括3.0,4.0和5.0分别有对应的不同处理,参考androidandroidframeworksbasecorejavaandroidosBuild.java可以看到注释有说明各版本有什么不同
  • 至于为什么说mDecor是最外层view,是因为在generateLayout方法中mDecor将填充该xml文件的view--mContentRoot添加了进来。

Activity在启动加载布局共有两个操作

  • 创建DecorView的布局:setContentView的流程基本是用来创建DecorView的布局
  • 将布局添加到window窗口:在Activity的启动过程中,会将应用窗口添加到WindowManager中进行统一管理,以及绑定DecorView
  • 对于状态栏和导航栏,是在每次window属性发生变化时会去更新,但是只是设置了一个背景色,只是占位用,没有加载这些view