小白都看得懂的布局加载流程
Posted 懂你的大海
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小白都看得懂的布局加载流程相关的知识,希望对你有一定的参考价值。
本篇文章的起点是从 Activity
的 setContentView
方法说起。我先放一张加载布局的时序图,布局加载涉及的类相对比较少一些。
1. Activity#setContentView()
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里 getWindow()
返回的是 Window
类型的,它是一个抽象类,变量名为 mWindow
,之前看过我写的 Activity 启动流程
的应该知道,它是在 Activity
的 attach
方法中初始化的,可以看下面的代码:
Activity#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,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
}
创建出实现类 PhoneWindow
,其实 Window
也只有一个实现类就是 PhoneWindow
. 接下来,就到了 PhoneWindow
的setContentView
方法中了,继续跟进。
2. PhoneWindow#setContentView
@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) {
installDecor();//------------------->2
} 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);//------------------->2
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
注释1处是对 DecorView
进行初始化,这个不是我们本篇文档的重点,我们看下注释2 是用 LayoutInflater
对我们的资源id 进行加载:
3.LayoutInflater#inflate
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
两个参数的会调用三个参数的方法,其中 第三个参数的话肯定就是 true
了,因为我们上面穿了 mContentParent
,这个值会在 installDecor
方法中进行初始化的。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: "" + res.getResourceName(resource) + "" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
XMLResourceParser
负责加载解析我们 xml
,最后交给 inflate
方法,我曾经在 解决布局加载那些奇奇怪怪的事 中对这部分代码做过详细的解释,在里面的 createViewFromTag
方法中,对 View
进行反射,看下代码:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
...
View view;
if (mFactory2 != null) {//------------->1
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {//------------>2
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {//----------->3
view = onCreateView(parent, name, attrs);
} else {//----------------->4
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
...
}
注释1:判断是否设置了 mFactory2
; 注释2:判断是否设置了 mFactory
; 如果我们设置了 Factory
,就会调用相应的 onCreateView
方法,利用它我们可以做很多事情,像统计布局控件的绘制速度,全局替换某个控件等,这个我在后面文章会介绍它的使用。 注释3:判断我们在xml
中的控件是否自定义控件,如果是自定义控件的话,一般我们都是 xxx.MyView
一定会包含 .
,如果含有的话就是自定义控件,否则就是进入注释4,系统控件,例如:TextView
这样,进入 createView
方法:
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
...
}
可以看到我们对 View
最终是通过反射创建出来的,并且对View
进行了缓存.
sConstructorMap.put(name, constructor);
今日分享就到这里了,有问题评论区见~
以上是关于小白都看得懂的布局加载流程的主要内容,如果未能解决你的问题,请参考以下文章