概述
这回我们来分析一下 Activity布局加载的setContentView()的整个流程。这一流程将细化为3个部分来分析:setContentView系统布局加载流程、LayoutInflater初始化分析、LayoutInflater布局加载流程。
一、setContentView系统布局加载流程
我们来看看 Activity的 setContentView()方法:
// Activity.java
// mWindow 实例
mWindow =new PhoneWindow(this,window, activityConfigCallback);
// ......
public Window getWindow() {
return mWindow;
}
public void setContentView(@LayoutRes int layoutResID) {
//注释 1 mWindow是 PhoneWindow。我们到 PhoneWindow去看 setContentView() 方法
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
// PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//注释 2, 如果 mContentParent等于空,创建一个 DecorView(mContentParent 就是 ContentView)
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
// 注释 3, 将我们页面的布局记载道 id为 R.id.content(mContentParent) 的布局里
mLayoutInflater.inflate(layoutResID, mContentParent);
}
上面我们看到,Activity的 setContentView方法调用了变量 mWindow的方法,而mWindow的实现类是PhoneWindow,看上面注释。
我们看下上面注释2、3,PhoneWindow的 setContentView方法先会判断布局变量mContentParent 是否等于空。如果等于空,在上面注释 2的地方初始化 DecorView。然后在注释 3的地方将我们在activity_main_layout自己定义的布局加载到 mContentParent布局里,而这个 mContentParent布局就是这个 id为 R.id.content 的Layout,这个下面会看到。现在我们来看看注释 2处 DecorView创建的过程:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//注释 4, 如果 mDecor等于null,创建一个 DecorView
mDecor = generateDecor(-1);
......
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//注释 5 创建一个id为 R.id.content 的布局(也就是contentView),这就是我们 activity_main_layout的父布局
mContentParent = generateLayout(mDecor);
}
}
protected DecorView generateDecor ( int featureId){
// 创建一个 DecorView
return new DecorView(context, featureId, this, getAttributes());
}
上面注释 4处创建了一个 DecorView 对象赋给了变量 mDecor。然后注释 5是 mContentParent 的初始化过程,下面看一下:
protected ViewGroup generateLayout (DecorView decor){
//注释 6, int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
// 获取到 id为 R.id.content的Layout
ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
// .......
if (
//注释 7, 一些列 if else判断
) {......} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
//注释 8, 在 DecorView里再添加一层 id为layoutResource 的系统布局
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//注释 9, 返回 content的Layout,赋值给上面的 mContentParent变量
return contentParent;
}
// DecorView.java
void onResourcesLoaded (LayoutInflater inflater,int layoutResource){
......
// 创建系统布局
final View root = inflater.inflate(layoutResource, null);
......
// Put it below the color views.
// 在 DecorView里再添加一层 id为layoutResource 的系统布局
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
......
}
上面注释 6的地方开始创建这个 id为R.id.content的布局,并最终返回给上面的mContentParent 变量。然后注释 7这个地方有一系列判断,最终会选择一个系统布局,在注释 8的地方把这个系统布局加载到我们的 DecorView里。而且需要注意的是这个系统布局里会有一个叫 R.id.content的 View。
- 小结
总结一下,Activity的setContentView是通过 其持有的 PhoneWindow来加载布局的。PhoneWindow会先创建一个 DecorView加载进来,而DecorView在创建时又会加载一个系统布局添加给 DecorView,而这个系统布局又包含了一个叫做 R.id.content的布局,此布局最终会加载并赋值给 mContentParent 变量,最后我们自己在 activity里定义的activity_main_layout会加载进 mContentParent。
下面是一个简单的层级图:
setContentView.png
二、LayoutInflater初始化分析
下面到 LayoutInflater布局加载分析。分析布局加载之前我们先看一下 LayoutInflater的初始化过程。
1.View view = View.inflate(this,id_view, viewParent);
2.View view = LayoutInflater.from(this).inflate(id_view, viewParent);
3.View view = LayoutInflater.from(this).inflate(id_view, viewParent, false);
我们常用的布局加载有上面几种用法,其实最终都会走到第3种方式。第3种方式最后一个 boolean型参数表示是否将创建的布局添加到父布局。下面我们先看看 LayoutInflater.from(this)这个方法,LayoutInflater是怎么初始化的:
//LayoutInflater .java
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
// 这里点进去是抽象方法,找到 context的实现类 ContextImpl的 getSystemService方法
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// ......
return LayoutInflater;
}
上面是调用了 context的 getSystemService方法,我们找到实现类 ContextImpl看一下:
// ContextImpl.java
public Object getSystemService (String name){
// ......
// 在 SystemServiceRegistry.java里拿
return SystemServiceRegistry.getSystemService(this, name);
}
// SystemServiceRegistry.java
public final class SystemServiceRegistry {
// 单例,用于缓存系统服务
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new ArrayMap<String, ServiceFetcher<?>>();
static {
// 注册系统布局加载 Service
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}
});
// 这个静态块里注册了 N多个系统服务
// 省略......
}
public static Object getSystemService(ContextImpl ctx, String name) {
// 从缓存内中获取系统服务
final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
final Object ret = fetcher.getService(ctx);
// ......
return ret;
}
}
上面一直找,找到了 SystemServiceRegistry类里面。原来 LayoutInflater在SystemServiceRegistry 初始化块里面会被注册成一个系统服务,而且以单例的形式保存在 ArrayMap列表里。
三、LayoutInflater布局加载流程
下面我们来分析LayoutInflater加载布局的流程,看它的inflate方法:
public View inflate ( @LayoutRes int resource, @Nullable ViewGroup root){
return inflate(resource, root, root != null);
}
public View inflate ( @LayoutRes int resource, @Nullable ViewGroup root,
boolean attachToRoot){
// 获取用于资源加载的 Resources对象
final Resources res = getContext().getResources();
//注释 10, 用来根据布局文件的xml预编译生成dex,然后通过反射来生成对应的View,
//从而减少XmlPullParser解析Xml的时间。
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
// 获取 XmlResourceParser解析器,用于解析布局
XmlResourceParser parser = res.getLayout(resource);
try {
//注释 11, 开始加载布局并返回
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
上面注释 10,首先根据布局文件的xml预编译生成dex,然后通过反射来生成对应的View。如果没有,则往下用 XmlResourceParser 解析加载。这个预编译的开关默认是false,只有在内部测试时打开,这里不再讲。我们继续往下看注释 11处inflate的重载方法:
public View inflate (XmlPullParser parser, @Nullable ViewGroup root,boolean attachToRoot){
synchronized (mConstructorArgs) {
// 获取属性集
final AttributeSet attrs = Xml.asAttributeSet(parser);
View result = root;
try {
// parser 移动到布局根节点处,获取根标签名称
advanceToRootNode(parser);
final String name = parser.getName();
//注释12, 判断 根标签是不是 “merge”
if (TAG_MERGE.equals(name)) {
// 是 merge标签,那就遍历布局并创建。通过递归解析加载布局
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//注释 13, 不是 merge,则创建根标签 View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
//省略......
//注释 14, 遍历布局文件,生成所有自View。通过递归解析加载布局
rInflateChildren(parser, temp, attrs, true);
//注释 15, 如果需要的话,把布局添加到父布局
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 返回 View
if (root == null || !attachToRoot) {
result = temp;
}
}
// 省略 ......
return result;
}
}
上面在创建布局的时候,会现在注释 12处判断布局的根节点标签是不是“merge”,如果是就会调用rInflate方法遍历标签下的所有布局,并递归地解析和加载布局。
如果根标签不是“merge”,就会到注释13处开始创建布局,并在注释 14处同样采用递归的方式解析和加载子布局。然后在注释15的地方判断创建的布局是否加载到父布局中。也就是我们在调用布局加载参数时可以传入一个boolean参数,决定是否加入父布局。
下面再继续看一下上面注释 13处,View是怎么创建的:
View createViewFromTag (View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr){
// ......
try {
// 注释16,尝试从 Factory中获取View
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
try {
//注释17 如果类名里存在'.',则说明布局里用的是全类名,说明这是自定义 View
// 否则 不带'.',说明用的不是全类名,是系统提供的View(默认前缀 android.widget)
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
// ......
}
public final View createView (@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs){
// ......
// 先从缓存中拿构造函数
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
try {
if (constructor == null) {
//注释 18 缓存中没有,那就通过反射创建构造函数,并把构造函数加入缓存
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
// ......
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
}
// .......
try {
// 用构造函数通过反射创建 View并返回
final View view = constructor.newInstance(args);
// ......
return view;
} finally {
}
}
}
上面注释 16的地方,会首先尝试从Factory中获取View的对象,这个Factory可以人为地传入,这样我们就可以拦截到 View的创建过程。再往下看注释 18 的地方,到了View最终创建的地方。View的创建是通过反射的方式,并将构造器保存在缓存中,下次创建时直接在缓存中拿。
我们再看看上面注释 16的地方 Factory的传入,我们在 activity的onCreate方法里可以以下面的方式传入Factory,这样就可以拦截到 View的创建:
LayoutInflater layoutInflater = LayoutInflater.from(this);
LayoutInflaterCompat.setFactory2(layoutInflater, new LayoutInflater.Factory2() {
@Nullable
@Override
public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
// 可以创建View并返回
return null;
}
@Nullable
@Override
public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
// 可以创建View并返回
return null;
}
});
好了布局的加载流程基本就这样了。稍微总结一下吧:
-
Activity的 setContentView方法不只是负责用 LayoutInflater 加载我们定义的布局,还加载了多层系统布局,比如 DecorView、ContentView等。
-
LayoutInflater 是系统服务。
-
rInflate和rInflateChildren方法都是通过递归解析并加载xml中的子布局。
-
我们在使用 LayoutInflater 加载布局的时候,可以通过参数 attachToRoot选择是否将该布局加载到某个父布局。
-
我们可以在 Activity的 onCreate方法里,在setContentView执行前,传入 Factory对象,这样可以拦截 View的创建。
网友评论