LayoutInflater 是我们在 Android 开发中经常用到的一个类
为什么要说是经常用到的呢?因为我们写的 xml 布局配置文件都是通过 LayoutInflater 来 inflate 成具体的 View 对象的,刚接触 Android 开发的同学可能会疑惑,我没有用过这个东西啊,它是什么鬼?那是因为我们在 Activity 中加载布局是通过 setContentView() 函数完成的
public void setContentView(int layoutResID) {
// 这里调用 getWindow() 拿到 window 对象
// 然后调用 mWindow.setContentView() 函数
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Window.java 是一个抽象类,继承并实现它的是 PhoneWindow.java , mWindow 是在 Activity 的 attach() 函数中被初始化的,这个函数是在 ActivityThread 中在创建 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) {
// 设置 baseContext
attachBaseContext(context);
// 重点在这里,new 了一个 PhoneWindow 对象
mWindow = new PhoneWindow(this);
// 这里先不说,后面会讲到
mWindow.getLayoutInflater().setPrivateFactory(this);
// 此后省略部分无关代码
}
接下来,我们再进一步深入到 PhoneWindow 中看看构造函数都做了什么
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
PhoneWindow 的构造函数除了调用 super() 外就只是初始化了一个 LayoutInflater 对象,mWindow.getLayoutInflater() 返回的就是 mLayoutInflater 对象,那么 mLayoutInflater 是到底如何被创建的?我们继续跟代码
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
这里的 context 是 Activity 对象,以下是 Activity 的 getSystemService() 函数代码
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
最终调用了 super.getSystemService(name)
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
// 实例化 mInflater
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
这里是调用了 baseContext 的 getSystemService() 函数,获取一个实例,然后调用 cloneInContext() 复制一个新的实例
getBaseContext() 是 ContextWrapper 的函数, Activity 继承 ContextThemeWrapper, ContextThemeWrapper 继承了 ContextWrapper,它是在 Activity 的 attach() 函数中通过 attachBaseContext() 进行的赋值,那我们就有必要知道这里的 baseContext 到底是什么,getSystemService() 做了些什么
我们在 ActivityThread 中找到对应的代码如下
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity = null;
try { // 通过反射 new 一个 Activity 对象
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
// ...
} catch (Exception e) {
// ...
}
Context appContext = createBaseContextForActivity(r, activity);
// ...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);
// ... 各种状态设置以及异常处理
return activity;
}
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
// ...
// 通过 ContextImpl 为 Activity 创建一个 Context 对象
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
// ...
return baseContext;
}
距离真相又近了一步
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, int displayId, Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
// 直接返回新的 ContextImpl 对象
return new ContextImpl(null, mainThread, packageInfo, null, null, false,
null, overrideConfiguration, displayId);
}
ContextImpl.createActivityContext() 只是 new 了一个 ContextImpl 对象,构造函数我们不再分析,直接看 ContextImpl 的 getSystemService() 函数
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
接着看 SystemServiceRegistry 的部分代码
static {
// ... 省略大量代码
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
// ... 省略大量代码
}
/**
* Gets a system service from a given context.
*/
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
我们最终的结论是在 Activity 中通过 getSystemService(LAYOUT_INFLATER_SERVICE) 得到的是一个 PhoneLayoutInflater 对象,他的代码十分简单
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
public PhoneLayoutInflater(Context context) {
super(context);
}
protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
}
/** Override onCreateView to instantiate names that correspond to the
widgets known to the Widget factory. If we don't find a match,
call through to our super class.
*/
@Override
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
}
PhoneLayoutInflater 是用来解析系统控件的,比如 TextView 、Button 、ImageView 等,通过 name 与 prefix 拼接得到类全名,如果解析失败,就调用 super
前面扯了一大堆没用的,我们回过头来看 mWindow.setContentView() 里面做了什么事
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
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);
}
}
在 Activity 第一次被创建的时候 mContentParent 一定是空的,所以走 installDecor() 函数,我们简单的认为此 Activity 没有 transition 动画,所以会调用 mLayoutInflater.inflate(layoutResID, mContentParent) 去加载布局文件
在分析 LayoutInflater.inflate() 函数之前,我们出于好奇,看一下 installDecor() 是干什么的
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
// 省略部分代码
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// 省略代码...
}
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
// DecorView 是继承自 FrameLayout 的一个内部 View
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public DecorView(Context context, int featureId) {
super(context);
// ...
}
// ...
}
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
// ... 根据主题设置各种属性
a.recycle();
int layoutResource;
// 这里主要是根据 theme 不同选择不同的容器布局
if (xxx) {
layoutResource = R.layout.xxx;
} else { // 不止两个条件,此处简写
layoutResource = R.layout.xxx;
}
// 实例化容器布局
View in = mLayoutInflater.inflate(layoutResource, null);
// 添加到 decor 中,并且是充满 decor
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
// 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");
}
// ..
return contentParent;
}
通过上面的代码我们可以看到,在 Activity 中通过 setContentView() 设置的布局,实际上最后是被添加到了 mContentParent 中,通过查看源码发现,mContentParent 其实也是一个 FrameLayout
别的不多扯,下面进入正题,分析 PhoneLayoutInflater.java 前面已经贴过了它的源码,它的构造函数只是调用了 super
protected LayoutInflater(Context context) {
mContext = context;
}
protected LayoutInflater(LayoutInflater original, Context newContext) {
mContext = newContext;
mFactory = original.mFactory;
mFactory2 = original.mFactory2;
mPrivateFactory = original.mPrivateFactory;
setFilter(original.mFilter);
}
也很简单,一个参数的就是存储一下 context 对象,两个参数的,还把 original 的三个回调以及过滤器赋值给自己
Filter
public interface Filter {
// 作用是是否允实例化 clazz
boolean onLoadClass(Class clazz);
}
Factory
public interface Factory {
// 用来解析 xml 中自定义的 tag ,建议自定义 tag 加入自己的包名以区别于系统的
public View onCreateView(String name, Context context, AttributeSet attrs);
}
Factory2
public interface Factory2 extends Factory {
// 多了一个父布局 parent
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
和 setFactory 相关的函数一共有三个
public void setFactory(LayoutInflater.Factory factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = factory;
} else {
mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}
}
public void setFactory2(Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
/**
* @hide for use by framework
*/
public void setPrivateFactory(Factory2 factory) {
if (mPrivateFactory == null) {
mPrivateFactory = factory;
} else {
mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
}
}
通过上面的代码,我们可以知道,Factory 和 Factory2 两个只能设置其中一个,setPrivateFactory() 是隐藏 api ,setFactory() 或者 setFactory2() 函数要求在调用之前,最好用 cloneInContext() 获取一个新的实例,而且原来的 Factory 依然存在,会和自己的 Factory 合并
FactoryMerger
private static class FactoryMerger implements Factory2 {
private final Factory mF1, mF2;
private final Factory2 mF12, mF22;
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
mF1 = f1;
mF2 = f2;
mF12 = f12;
mF22 = f22;
}
public View onCreateView(String name, Context context, AttributeSet attrs) {
View v = mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF2.onCreateView(name, context, attrs);
}
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
: mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
: mF2.onCreateView(name, context, attrs);
}
}
FactoryMerger 实现了 Factory2 接口,构造函数有四个参数,前两个是新的 Factory ,后两个参数是旧的 Factory ,回调中先调用新的 Factory ,如果返回为空,则调用旧的 Factory
主角 inflate() 函数登场,相关函数一共四个,但最终都是调用的同一个
// 解析 xml 资源 如果 root 不为空,resource 生成的 view 会被添加到 root 中
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
// 解析一个 xml 文件
public View inflate(XmlPullParser parser, ViewGroup root) {
return inflate(parser, root, root != null);
}
// 解析 xml 资源 第三个参数用来指定 view 是否应该 add 到 root 里
// 如果 root 为空 则地三个参数没有意义
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// 通过 Resources 获取一个 XmlResourceParser
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
// 上面三个函数最终都会调用这个函数,他是重点
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
// 重点,下面单独贴
}
从上面的代码,我们知道,inflate() 函数有四个重载函数,最后都只会调用一个函数,下面是代码
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
// mConstructorArgs 是长度为 2 的 Object 数组
// 第 0 个是 context 对象 第 1 个 是 AttributeSet 对象
mConstructorArgs[0] = inflaterContext;
View result = root;
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 创建 view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// 生成 LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
// 解析 temp 的子 view
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
// 如果 root 不为空并且 attachToRoot 为 true
// 则把 xml 解析完成的 view 添加到 root 中
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
// 如果 view 被添加到了 root 则返回 root
// 否则返回 xml 解析出来的 view
return result;
}
}
其实 LayoutInflater 是使用的 pull 解析方式来解析的 xml ,关于 xml 解析方式,可以去网上搜,不止 pull 一种解析方式
可以看出,如果 xml 的根标签是 merge 那么 root 不能为空并且 attachToRoot 必须为 true ,否则会抛异常,后面调用了 createViewFromTag() ,就是根据 tag 节点创建 View
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
// 如果是 view 标签就获取 class 的值
name = attrs.getAttributeValue(null, "class");
}
if (!ignoreThemeAttr) {
// 如果设置了主题
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
// 则生成一个带有主题的 context
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
if (name.equals(TAG_1995)) {
// 被 blink 包住的 view 会一闪一闪的,可以看 BlinkLayout 源码
return new BlinkLayout(context, attrs);
}
View view;
if (mFactory2 != null) {
// 如果 mFactory2 不为空则调用 mFactory2.onCreateView()
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
// 如果经过上面的步骤 view 还是空,并且 mPrivateFactory 不为空
// 则回调 mPrivateFactory.onCreateView()
// 我们前面分析了,Activity 的 LayoutInflater 是设置了 PrivateFactory 的
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('.')) { // 如果 tag 中没有 .
// 如果子类没有重写 onCreateView() 则默认拼接 android.view. 作为类名
// PhoneLayoutInflater 重写了 onCreateView() 上面提到过
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
createViewFromTag() 中调用了 createView() 函数,第二个参数代表要拼接到 name 前的前缀
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
if (constructor == null) {
// 使用 ClassLoader 加载类
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 (mFilter != null) {
// 获取之前保存过的状态
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
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[] args = mConstructorArgs;
args[1] = attrs;
// 实例化 view
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) { // ViewStub 懒加载
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
}
通过上面的步骤,只是解析完成了 xml 的根 view ,接下来要循环解析 xml 中的 子 view
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (finishInflate) {
parent.onFinishInflate();
}
}
上面的代码就是根据不同的 tag 做不同的解析,如果不是 requestFocus 、tag 、include 或者 merge 就调用 createViewFromTag() 然后循环 rInflateChildren(),直到解析出所有 View ,限于篇幅原因,LayoutInflater 对各种 tag 的解析,留做后续讲解