Android setContentView源码解析

从入门应用Hello World 说起,我们简单的用setContentView设置了一个布局,然后启动我们的应用,手机界面就显示出Hello World的布局,这个背后到底发生了什么,这就是我今天所研究的对象。

1.概述

首先弄清楚几个跟View有关的概念

  • 窗口(Window):这是一个纯语义的说法,即程序员所看到的屏幕上的某个独立的界面,比如一个带有Title Bar 的Activity 界面、一个对话框、一个Menu 菜单等,这些都称之为窗口。这里所说的窗口管理一般也都泛指所有这些窗口,在Android 的英文相关文章中则直接使用Window 这个单词。而从WmS 的角度来讲,窗口是接收用户消息的最小单元,WmS 内部用特定的类表示一个窗口,以实现对窗口的管理。WmS 接收到用户消息后,首先要判断这个消息属于哪个窗口,然后通过IPC 调用把这个消息传递给客户端的ViewRoot 类。

  • Window 类:该类在android.view 包中,是一个abstract 类,该类是对包含有可视界面的窗口的一种包装。所谓的可视界面就是指各种View 或者ViewGroup,一般可以通过res/layout 目录下的xml 文件描述。

  • ViewRoot 类:该类在android.view 包中,客户端申请创建窗口时需要一个客户端代理,用以和WmS 进行交互,这个就是ViewRoot 的功能,每个客户端的窗口都会对应一个ViewRoot 类。

  • W 类:该类是ViewRoot 类的一个内部类,继承于Binder,用于向WmS 提供一个IPC 接口,从而让WmS 控制窗口客户端的行为。

描述一个窗口之所以使用这么多类的原因在于,窗口的概念存在于客户端和服务端(WmS)之中,客户端所理解的窗口和服务端理解的窗口是不同的,因此,在客户端和服务端会用不同的类来描述窗口。同时,无论是在客户端还是服务端,对窗口都有不同层面的抽象,比如在客户端,用户能看到的窗口一般是View 或者ViewGroup 组成的窗口,而与Activity 对应的窗口却是一个DecorView 类,而具备常规Phone 操作接口的窗口却又是一个PhoneWindow 类。


关系图盗用老罗的图片描述下


至于一个手机窗口的具体组成,网上也有图描述的很清楚了



一般来说DecoView包含一个LinearLayout,(这里的一般指的是R.Layout.screen_title后续会有介绍)这个LinearLayout又包含标题和内容区域两个FramLayout,SDK中tools文件夹下hierarchyviewer bat 查看ViewTree可以证实这一点



同样可以在代码中证实这一点

获取ContentView: findViewById(android.R.id.content) 

获取ContentView上一级:ContentView.getParent

获取DecorView:上面的对象再getParent     或者直接 getWindow().getDecorView()

理论说了这么多,talking is cheap, show me the code!


2.setContentView源码解析

Acvitivity.setContentView

/**
 * 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.
 */
public void setContentView(int layoutResID) {
    getWindow().setContentView(layoutResID);
}
  
public Window getWindow() {
    return mWindow;
}
 

getWindow()到底是个什么鬼,打印一下发现是  com.android.internal.policy.impl.PhoneWindow

接下来看 PhoneWindow.setContentView

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();         // 1、生成DecorView和mContentParent
    } else {
        mContentParent.removeAllViews();
    }
    mLayoutInflater.inflate(layoutResID, mContentParent);// 2、将layoutResId的布局添加到mContentParent中
    final Callback cb = getCallback();<span style="font-family: Arial, Helvetica, sans-serif;">// 3、窗口回调函数</span>
    if (cb != null) {
        cb.onContentChanged();
    }
}
  

我们可以看到,setContentView的基本流程简单概括就是如下几步:

1、构建mDecor对象和mContentParent。mDecor就是整个窗口的顶层视图,它一般包含了Title和ContentView两个区域 ,Title区域就是我们的标题栏,ContentView区域就是显示我们xml布局内容中的区域。

2、设置一些关于窗口的属性,初始化标题栏区域和内容显示区域;

3、调用Window的回调方法。

下面详细解读上面三个部分

2.1 构建mDecor对象和mContentParent

重点分析PhoneWindow.installDecor函数

  // 构建mDecor对象,并且初始化标题栏和Content Parent(我们要显示的内容区域)
    private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor();          // 1、构建DecorView
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);              // 2、获取ContentView容器,即显示内容的区域
 
        mTitleView = (TextView)findViewById(com.android.internal.R.id.title); //3、设置Title等
        if (mTitleView != null) {
            if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                View titleContainer = findViewById(com.android.internal.R.id.title_container);
                if (titleContainer != null) {
                    titleContainer.setVisibility(View.GONE);
                } else {
                    mTitleView.setVisibility(View.GONE);
                }
                if (mContentParent instanceof FrameLayout) {
                    ((FrameLayout)mContentParent).setForeground(null);
                }
            } else {
                mTitleView.setText(mTitle);
            }
        }
    }
}
 
    protected DecorView generateDecor() {
    return new DecorView(getContext(), -1);    // 构建mDecor对象
}

接着看PhoneWindow.generateLayout,这个函数有点长,也是本文分析的重点,稍微精简一下

// 返回用于显示我们设置的页面内容的ViewGroup容器
     protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        // 1、获取窗口的Style属性
        TypedArray a = getWindowStyle();
 
        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }
        // 窗口是否是浮动的
        mIsFloating = a.getBoolean(com.android.internal.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);
        }
        // 设置是否不显示title区域
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
     
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值