Activity中布局资源layoutResId在setContentView加载过程分析

本文详细分析了Android中Activity的setContentView方法在加载布局资源时的执行过程,从寻找setContentView的真正实现,到mWindow的实例化,再到PhoneWindow中的setContentView实现。内容包括布局解析、installDecor方法、generateLayout方法的探讨,以及布局资源screen_title.xml的解析。通过对整个流程的剖析,加深了对Android视图加载机制的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

记得刚开始学习安卓那会,感觉安卓真的很简单,用xml写一个布局,然后再写一个activity,接着调用一下在onCreate中调用下setContentView(resId)一个页面就可以看到了,现在回想也才知道Android的牛逼,它降低了开发者的门槛 ,但是一旦你跨过门槛,越往里走就发现越神奇,下面就分析下setContentView(resId)中到底做了什么,也是为了后面分析view的Measure过程做一个铺垫。

分析

1、寻找setContentView真正实现的地方

一般我们在新建的activity中传入布局的id就可以加载这个页面了,当然setContentView有几个重载的方法,就不细说了。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

上面讲到了activity的onCreate方法,很明显onCreate方法中我们常用的就是调用setContentView来给activity给定一个显示的布局id,就是我们常继承的Activity中的setContentView(resId)的源码

      /**
     * 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.
     * 准备导入的布局资源的id
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        //ActionBar的初始化,平时很少用ActionBar暂时忽略
        initActionBar();
    }

2、mWindow实例化

getWindow返回的是mWindow,mWindow是窗口抽象类Window的一个实例,而setContentView(resId)是window中一个抽象方法,所以我们需要找到mWindow实例化的地方,然后看mWindow实现类的setContentView方法做了一些什么工作。

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) {
        attachBaseContext(context);
    ...
         mWindow = PolicyManager.makeNewWindow(this);
    ...

最后在activity的attach方法中找到了mWindow实例化的地方,接着需要找PolicyManager.makeNewWindow返回的对象,也就是Window实现子类,PolicyManager的代码不多,所以这里就直接贴出来了看看。

/**
 * 隐藏的api,难怪sdk中找不到。T—T
 * {@hide}
 */
public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";
    private static final IPolicy sPolicy;
    static {
        // Pull in the actual implementation of the policy at run-time
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
        } catch (InstantiationException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        }
    }
    // Cannot instantiate this class
    private PolicyManager() {}

    //mWindow其实也是在这里实例化的
    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }
    public static LayoutInflater makeNewLayoutInflater(Context context) {
        return sPolicy.makeNewLayoutInflater(context);
    }
    public static WindowManagerPolicy makeNewWindowManager() {
        return sPolicy.makeNewWindowManager();
    }
    public static FallbackEventHandler makeNewFallbackEventHandler(Context context) {
        return sPolicy.makeNewFallbackEventHandler(context);
    }
}

PolicyManager的makeNewWindow找到了,可惜这里还不是真正实例化mWindow的地方,从上面的代码可以看出,通过反射实例化com.android.internal.policy.impl.Policy这个类,IPolicy 目测应该是一个接口,这个sPolicy实例化是通过反射构造方法来创建的,而且在static代码块中,可见这个类是在被虚拟机加载的时候就实例化了sPolicy。所以我们需要继续寻找。最后功夫不负有心人,总算是找到了Policy 这个类,这个类的实现了IPolicy 接口,代码也不算多,那么就直接贴出来吧。

public class Policy implements IPolicy {
    private static final String TAG = "PhonePolicy";

    private static final String[] preload_classes = {
        "com.android.internal.policy.impl.PhoneLayoutInflater",
        "com.android.internal.policy.impl.PhoneWindow",
        "com.android.internal.policy.impl.PhoneWindow$1",
        "com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback",
        "com.android.internal.policy.impl.PhoneWindow$DecorView",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
    };

    static {
        // For performance reasons, preload some policy specific classes when
        // the policy gets loaded.
        for (String s : preload_classes) {
            try {
                Class.forName(s);
            } catch (ClassNotFoundException ex) {
                Log.e(TAG, "Could not preload class for phone policy: " + s);
            }
        }
    }
    //终于算是找到你了Activity中的mWindow也就是在这里被实例化的
    public PhoneWindow makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }

    public PhoneLayoutInflater makeNewLayoutInflater(Context context) {
        return new PhoneLayoutInflater(context);
    }

    public PhoneWindowManager makeNewWindowManager() {
        return new PhoneWindowManager();
    }
}

从上面的makeNewWindow方法中可以很清楚的看到,我们activity的mWindow也就是PhoneWindow的实例,所以我们也就回到了我们最初目的,打开PhoneWindow看看它的setContentView(resId)方法都做了些什么。

3、分析setContentView的实现

同样PhoneWindow的源码在sdk中没有找到,这里就不贴了,为啥不贴,因为源码太多了。我在网上找了一个源码的链接地址,如果需要翻墙推荐一个nydus的翻墙工具,实惠好用

PhoneWindow:
https://android.googlesource.com/platform/frameworks/base/+/696cba573e651b0e4f
18a4718627c8ccecb3bda0/policy/src/com/android/internal/policy/impl/PhoneWindow.java

下面就将PhoneWindow的setContentView的代码贴出来

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
        //如果mContentParent 为null就调用installDecor初始化
            installDecor();
        } else {
        //否则就情况所有的子View
            mContentParent.removeAllViews();
        }
        //然后将layoutResID加载到mContentParent中
        mLayoutInflater.inflate(layoutResID, mContentParent);
        //如果PhoneWindow没有被销毁,且回调cb不为null,那么就调用cb.onContentChanged()
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

setContentView中调用的相关方法

   /**
     * 将layoutResId资源加载到View树中
     * 
     * @param 需要加载的layoutResId (e.g.,
     *        <code>R.layout.main_page</code>)
     * @param 需要加载布局的父布局
     * @return 返回根View,如果参数root不为空,那么root就是根View,否则导入的resource
     * 解析的View就是根View
     */
    public View inflate(int resource, ViewGroup root) {
        return inflate(resource, root, root != null);
    }

这里需要重点分析一下installDecor方法

private void installDecor() {
        //mDecor是Window中的根View
        if (mDecor == null) {
           //mDecor是DecorView的实例,而DecorView是PhoneWindow中的一个内部类,
           //private final class DecorView extends **FrameLayout** implements 
           //RootViewSurfaceTaker {...},这里很明显DecorView是FrameLayout的子类
           //generateDecor方法很直接new DecorView(getContext(), -1),即构建一个DecorView
            mDecor = generateDecor();     
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) {

            mContentParent = generateLayout(mDecor);

            //这里特意留下了这句代码,就是要讲下这个findViewById()方法,这个方法是Window中
            //继承过来的,它的实现是getDecorView().findViewById(id);也就是说通过
            //mDecor来获取相关的子控件            
            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
           ...
           }
    }

看到这里上面也就剩下 mContentParent = generateLayout(mDecor);需要深入了解了,所以这里就深入了解下generateLayout方法。一般我们新建一个activity默认

protected ViewGroup generateLayout(DecorView decor) {
        // 根据主题和相关的设定配置layoutResource,有兴趣的同学自己可以阅读PhoneWindow在此省略
        ...
        mDecor.startChanging();
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        // window中的常量ID_ANDROID_CONTENT = com.android.internal.R.id.content;
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
        ...
        mDecor.finishChanging();
        return contentParent;
    }

为了更进一步理解,使用了新建activity自动生成的一种布局screen_title.xml,开始是在sdk下面找到然后解压出来的,后面发现找不到编码格式,只能google了一把,找到了相关资源。

screen_title.xml

https://android.googlesource.com/platform/frameworks/base/+/c80f952/core/res/res/l
ayout/screen_title.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content" />
     <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <!--这里也就是mTitleView-->
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <!--这里也就是mContentParent-->
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

光看代码可能有点伤,所以我特意准备了3张图

1.mTitleView

mTitleView

2.mContentParent

mContentParent

3.view树

view树

总结

最后来总结下这个过程
Activity层面:
MainActivity(setContentView(resId);)—>Activity(getWindow().setContentView(layoutResID);)

PhoneWindow中的setContentView方法首先判断mContentParent是否为null,如果不为null则通过LayoutInflater来将resId解析后添加到mContentParent中,当然如果为null则通过installDecor方法初始化mContentParent,通过mDecor加载系统预定的主题布局文件,根据主题的不同,判断是否需要标题栏,actionbar,加载的进度条等等,然后通过固定的id(@android:id/content)初始化mContentParent。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值