-
概述
在Activity的setContentView流程中,我们会看到LayoutInflater的影子,我们也经常会使用LayoutInflater的inflate方法来加载xml布局,那么它是如何把xml转成View的呢?我们通过源码来看看它是如何实现的。
-
LayoutInflater的创建
LayoutInflater通过其静态方法from获取:
public static LayoutInflater from(@UiContext Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; }
from需要一个Context参数,在上下文中传入,所以这个context就是Activity本身,因为它间接继承了Context,Activity中实现了getSystemService方法,其中又会调用ContextThemeWrapper的getSystemService方法:
@Override public Object getSystemService(String name) { if (LAYOUT_INFLATER_SERVICE.equals(name)) { if (mInflater == null) { //会走这 mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this); } return mInflater; } return getBaseContext().getSystemService(name); }
可见,最终调用的是getBaseContext方法获取的Context:
protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; } /** * @return the base context as set by the constructor or setBaseContext */ public Context getBaseContext() { return mBase; }
而attachBaseContext是在Activity的attach方法中调用的:
final void attach(Context context, ...) { attachBaseContext(context); ... }
通过《Activity启动流程》中的分析可知,在创建Activity的流程中,ActivityThread的performLaunchActivity方法中,Activity对象调用了attach方法:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... ContextImpl appContext = createBaseContextForActivity(r); Activity activity = null; try { java.lang.ClassLoader cl = appContext.getClassLoader(); activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent); ... }catch(){...} try{ if (activity != null) { ... appContext.setOuterContext(activity); activity.attach(appContext, ...); ... } ... }catch(){...} ... } private ContextImpl createBaseContextForActivity(ActivityClientRecord r) { final int displayId = ActivityClient.getInstance().getDisplayId(r.token); ContextImpl appContext = ContextImpl.createActivityContext( this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
ContextImpl的createActivityContext方法如下:
static ContextImpl createActivityContext(ActivityThread mainThread, ...) { ... ContextImpl context = new ContextImpl(...); ... }
可以看到,attach方法最终传入的就是ContextImpl。回到流程中,来看ContextImpl的getSystemService方法:
@Override public Object getSystemService(String name) { ... return SystemServiceRegistry.getSystemService(this, name); }
继续看SystemServiceRegistry.getSystemService方法:
public static Object getSystemService(ContextImpl ctx, String name) { if (name == null) { return null; } final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); ... final Object ret = fetcher.getService(ctx); ... return ret; }
SYSTEM_SERVICE_FETCHERS是通过registerService方法put的,registerService方法在SystemServiceRegistry的静态代码块中调用:
static { ... registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher<LayoutInflater>() { @Override public LayoutInflater createService(ContextImpl ctx) { return new PhoneLayoutInflater(ctx.getOuterContext()); }}); ... }
很清楚了,fetcher.getService获取的就是PhoneLayoutInflater,这里获取的PhoneLayoutInflater实例是一个参数的构造方法,还没完,因为在ContextThemeWrapper的getSystemService方法中,我们最终获取的mInflater是from之后又调用PhoneLayoutInflater的cloneInContext方法返回的实例:
public LayoutInflater cloneInContext(Context newContext) { return new PhoneLayoutInflater(this, newContext); } protected PhoneLayoutInflater(LayoutInflater original, Context newContext) { super(original, newContext); }
所以我们最终获取的实例其实是两个参数的构造方法返回的PhoneLayoutInflater实例,这个构造方法中会调用父类,也就是LayoutInflater的构造方法:
protected LayoutInflater(LayoutInflater original, Context newContext) { StrictMode.assertConfigurationContext(newContext, "LayoutInflater"); mContext = newContext; mFactory = original.mFactory; mFactory2 = original.mFactory2; mPrivateFactory = original.mPrivateFactory; setFilter(original.mFilter); initPrecompiledViews(); }
cloneInContext方法传入的是ContextThemeWrapper中的this,也就是from传入的Activity实例,因此LayoutInflater内部的mContext就是Activity上下文。
-
inflate流程
inflate方法是重载方法,不管调用哪一个都会调用到:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); ... XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
接着会调用又一个重载inflate方法:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { ... //还记得吗,LayoutInflater内部的mContext是调用它的Activity实例 final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; try { //parser初始到xml开始的标签处 advanceToRootNode(parser); 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 { final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { params = root.generateLayoutParams(attrs); if (!attachToRoot) { temp.setLayoutParams(params); } } rInflateChildren(parser, temp, attrs, true); if (root != null && attachToRoot) { root.addView(temp, params); } if (root == null || !attachToRoot) { result = temp; } } } ... ... return result; } }
parser.getName()会得到xml中的标签名,<merge/>标签的解析我们不做分析,看一下标准的View是如何解析的。
除了merge标签之外,其他标签会通过createViewFromTag方法尝试加载出一个View来,这个temp就是最顶层的父容器,inflate的最后一个参数会控制是否要把加载出的View添加到root中。
-
延伸:为什么Fragment的onCreateView方法中调用inflate方法传入的attachToRoot参数为true时会产生异常?
IllegalStateException("The specified child already has a parent. " + "You must call removeView() on the child's parent first.")
根据以上代码我们知道,如果attachToRoot为true且root不为null时会把加载出的View添加到root中并且返回root,而Fragment的onCreateView中的container就是Activity的xml中某个用来盛放Fragment的容器:
//In FragmentStateManager: void createView() { ... ViewGroup container = null; ... //fragmentContainer这个是HostCallback,它的onFindViewById内部就是调用的FragmentActivity的findViewById方法 container = (ViewGroup) fragmentContainer.onFindViewById(mFragment.mContainerId); ... //performCreateView最终会调用到onCreateView方法 mFragment.performCreateView(layoutInflater, container, mFragment.mSavedFragmentState); ... if (container != null) { addViewToContainer(); } ... } void addViewToContainer() { // Ensure that our new Fragment is placed in the right index // based on its relative position to Fragments already in the // same container int index = mFragmentStore.findFragmentIndexInContainer(mFragment); mFragment.mContainer.addView(mFragment.mView, index); } //In Fragment: void performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { ... mView = onCreateView(inflater, container, savedInstanceState); ... }
可见,如果我们的attachToRoot是true,则返回的会是container,也就是mFragment.mView,那么在后续的addViewToContainer中又会调用container.addView(container),又因为container是在xml中配置的View,所以它本身一定已经有了一个父容器了(哪怕是根标签View也会是DecorView的R.id.content的子View),因此在这里调用mFragment.mContainer.addView的时候就会抛出重复父容器的异常。
言归正传,来看一下createViewFromTag方法,多次重载调用后会来到:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { //view标签会从class属性的值取到待加载的View类名 if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } //默认流程下,ignoreThemeAttr是写死的false,所以这里会进来 if (!ignoreThemeAttr) { final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { //这里会把上面传进来的Activity实例包装成ContextThemeWrapper context = new ContextThemeWrapper(context, themeResId); } ta.recycle(); } //根据上下文的theme构造出View构造方法里需要的context 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(); } try { View view = tryCreateView(parent, name, context, attrs); if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) { view = onCreateView(context, parent, name, attrs); } else { view = createView(context, name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; } ... }
先说一下onCreateView方法,我们知道from方法得到的inflater其实是PhoneLayoutInflater,又因为PhoneLayoutInflater重写了onCreateView方法,因此会先执行PhoneLayoutInflater的onCreateView方法:
@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); }
sClassPrefixList是一个字符串数组:
private static final String[] sClassPrefixList = { "android.widget.", "android.webkit.", "android.app." };
因此把它的元素作为prefix参数传入createView方法:
public final View createView(@NonNull Context viewContext, @NonNull String name, @Nullable String prefix, @Nullable AttributeSet attrs){ Class<? extends View> clazz = null; try { Constructor<? extends View> constructor = sConstructorMap.get(name); if (constructor != null && !verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it 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 { ... } ... try { 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])); } return view; } finally { mConstructorArgs[0] = lastContext; } }... ... }
最后调用其父类LayoutInflater的onCreateView方法,过程中会再调用一次createView方法并传入“ android.view. ”作为前缀,如果含有前缀,则createView方法里会拼在xml解析的标签名之前作为完整类路径,然后通过反射进行实例化,也就是说, 存在于以上包下的类直接可以在xml中以类名来配置,并不需要加上完整包名 。
再结合我们前面if (-1 == name.indexOf('.')) 的判断,所以我们可以知道,当使用 android.view 、 android.widget 、 android.webkit 、 android.app包下的类(注意不能是子包,因为name不含有‘ . ’的时候才会走onCreateView逻辑 )时,可以在xml中直接使用其类名来配置,在解析时会自动加上前缀作为完整路径类名解析。如果是含有‘ . ’的标签名,则会直接走createView当作完整类名进行反射。
接下来,我们回头看tryCreateView,这是最先会调用的View加载方式:
public final View tryCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (name.equals(TAG_1995)) { // Let's party like it's 1995! return new BlinkLayout(context, attrs); } View view; if (mFactory2 != null) { view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory != null) { view = mFactory.onCreateView(name, context, attrs); } else { view = null; } if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); } return view; }
代码没有省略,很简短,可以看到是尝试通过三个Factory的onCreateView来加载的。这里没什么好说的,提供了一个可以自定义解析加载规则的接口,可以通过相关的setXxxFactory方法设置,比如,你有一个自定义View类,想要在xml中不加完整包名的话,你可以设置一个Factory,仿照createView的实现在其onCreateView中实例化你的自定义组件。
以setFactory为例:
public void setFactory(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); } }
可以看到,如果已经有mFactory了,则会创建一个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; } @Nullable public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { View v = mF1.onCreateView(name, context, attrs); if (v != null) return v; return mF2.onCreateView(name, context, attrs); } @Nullable public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull 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通过包装器模式把添加的所有Factory都保存了起来,使用的时候再一级一级的剥开调用,而没有使用List来保存。
-
网友评论