LayoutInflater相关-布局XML文件转化为View

【LayoutInflater相关-布局XML文件转化为View】概述:平时开发,我们只需要在Activity的onCreate()方法中调用setContentView()方法就能实现页面的展示,同时也能调用findViewById()获取到对应的控件实例,那么layout的XML文件到底是怎么转化成View呢?
1、探索入口:setContentView()
public class MainActivity extends Activity{@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); } }-->调用Activity的setContentView public class Activity{public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); ... } }-->调用了Window类的setContentView public class Window{ public abstract void setContentView(@LayoutRes int layoutResID); }-->Window类的唯一实现类是PhoneWindow,最终是调用到了PhoneWindow的setContentView方法 public class PhoneWindow{ private LayoutInflater mLayoutInflater; public PhoneWindow(Context context) { super(context); mLayoutInflater = LayoutInflater.from(context); }@Override public void setContentView(int layoutResID) { ...mLayoutInflater.inflate(layoutResID, mContentParent); ... }}

分析:
  • Activity的setContentView()方法最终是调用了PhoneWindow中的setContentView();
  • PhoneWindow中的setContentView()是通过LayoutInflater的inflate(int resource, ViewGroup root)方法;
  • layoutResID的xml构建成View之后,会添加为mContentParent的子View(我们设置的布局并非页面的根布局,需要了解mContentParent请看mWindow.getDecorView);
2、LayoutInflater 的初始化
@SystemService(Context.LAYOUT_INFLATER_SERVICE) public abstract class LayoutInflater {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; }}-->通过调用ContextImpl类的getSystemService()方法 class ContextImpl extends Context {@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); }}-->通过系统服务管理类来获取系统服务 /** * Manages all of the system services that can be returned by {@link Context#getSystemService}. * Used by {@link ContextImpl}. */ final class SystemServiceRegistry {private static final HashMap> SYSTEM_SERVICE_FETCHERS = new HashMap>(); --> 所有的系统服务都是在静态代码块中注册的, static { ... registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher() { @Override public LayoutInflater createService(ContextImpl ctx) { return new PhoneLayoutInflater(ctx.getOuterContext()); }}); ... }//把系统服务的名称和系统服务保存到常量Hashmap中 private static void registerService(String serviceName, Class serviceClass, ServiceFetcher serviceFetcher) { SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName); SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); }//通过服务名称获取对应的系统服务 public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher != null ? fetcher.getService(ctx) : null; }}

分析:
  • LayoutInflater实例的获取只能通过其内部的静态方法from()获取;
  • context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)实际上是通过SystemServiceRegistry的getSystemService()方法获取,调用一个常量的HashMap获取对应的服务;
  • 所有的系统服务都是在SystemServiceRegistry的静态代码块中注册保存的,这保证了服务不会被重复注册保存;
  • 所以LayoutInflater的实例获取其实是一个单例设计模式。
3、mLayoutInflater.inflate(layoutResID, mContentParent)
public abstract class LayoutInflater { /** * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. */ 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) { final Resources res = getContext().getResources(); //获取Resources资源类 ... final XmlResourceParser parser = res.getLayout(resource); //获取布局文件对应xml解析器 try { return inflate(parser, root, attachToRoot); //继续调用下一个方法 } finally { parser.close(); } } }

这里只是获取Resources资源类,然后获取xml文件对应的xml解析器,再调用下一个inflate方法 。
3.1、开始解析布局xml文件
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { ...final AttributeSet attrs = Xml.asAttributeSet(parser); //获取xml的属性集合 ... View result = root; try { //判断是否有开始结束标签 int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty }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) { //如果父布局为空或者不需要添加到父布局之中,抛出异常,不直接解析merge为根标签的布局 throw new InflateException(" can be used only with a valid " + "ViewGroup root and attachToRoot=true"); }//填充merge的xml布局,往下查看3.2 rInflate(parser, root, inflaterContext, attrs, false); } else { //转换xml的根标签为temp,查看3.3 final View temp = createViewFromTag(root, name, inflaterContext, attrs); //配置根布局temp的布局参数 ViewGroup.LayoutParams params = null; if (root != null) { // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } }//填充跟标签的子view,往下查看3.2 rInflateChildren(parser, temp, attrs, true); //判断一下,把我们的布局根元素对应的view,添加为root的子view if (root != null && attachToRoot) { root.addView(temp, params); }//把我们的布局根元素对应的view作为结果返回回去 if (root == null || !attachToRoot) { result = temp; } }} ...finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; }return result; } }

分析:
  1. 先从XmlPullParser获取到所有的属性集合,标签转化为view时,需要用到;
  2. 判断是否有开始结束标签,然后获取第一个标签,判断第一个标签是否为merge标签;
  3. 如果是merge标签,root不为空且要添加为root的子view时,调用rInflate方法解析其余标签;
  4. 如果第一个标签不是merge标签,调用createViewFromTag方法把此标签转化成view,然后调用rInflateChildren方法解析其余标签;然后判断是否需要把第一个标签添加为root的子view。
3.2、递归调用,遍历xml的所有标签
-->填充parent的所有子view, final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { rInflate(parser, parent, parent.getContext(), attrs, finishInflate); }-->对parser进行循环获取,解析所有的标签 void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {final int depth = parser.getDepth(); //获取标签嵌套的深度,即布局嵌套的深度 int type; boolean pendingRequestFocus = false; //循环解析所有标签 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)) {//标签 pendingRequestFocus = true; consumeChildElements(parser); // } else if (TAG_TAG.equals(name)) {//标签 parseViewTag(parser, parent, attrs); //给parent设置tag } else if (TAG_INCLUDE.equals(name)) {//标签 if (parser.getDepth() == 0) { throw new InflateException(" cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException(" must be the root element"); } else { final View view = createViewFromTag(parent, name, context, attrs); //把标签转换成View,往下看3.3 final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflateChildren(parser, view, attrs, true); //对view包裹的子view进行填充 viewGroup.addView(view, params); //添加为parent的子view } }... if (finishInflate) { parent.onFinishInflate(); //调用父View的onFinishInflate方法 } }/** * 循环parser当前深度下,所有嵌套的标签,但不进行任何操作; * 即跳过某个标签和这个标签包裹的所有子标签 */ final static void consumeChildElements(XmlPullParser parser) throws XmlPullParserException, IOException { int type; final int currentDepth = parser.getDepth(); while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { // Empty } }

分析:
  1. rInflateChildren和rInflate是个递归方法,会互相调用;
  2. rInflate方法中循环所有所有的标签,对标签进行判断;
  3. 如果为标签,那么跳过这个标签以及requestFocus嵌套下的所有子标签;
  4. 如果是标签,那么获取ViewTag主题,给parent设置tag,然后跳过;
  5. 如果为merge标签, 那么merge包裹的标签,会填充merge的子标签,最后也会重新调用rInflate的方法;
  6. 然后对普通的View标签调用createViewFromTag方法,转换成view;把这个view添加到父view中,然后调用rInflateChildren,递归填充此标签嵌套的子标签(如果存在子标签)。
3.3、把xml中的标签转换为View
public abstract class LayoutInflater { . private Factory mFactory; private Factory2 mFactory2; private Factory2 mPrivateFactory; //我们可以实现LayoutInflater的Factory接口,再调用setFactory(),可以hook我们的布局View创建 public interface Factory { public View onCreateView(String name, Context context, AttributeSet attrs); }-->中转方法,往下调用 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) { ...// 给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; //如果是继承Activity,而且没有实现Factory接口,mFactory和mFactory2都为空 //如果是继承了AppCompatActivity,mFactory2不为空,onCreateView会在AppCompatDelegateImplV9中被调用 if (mFactory2 != null) { view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory != null) { view = mFactory.onCreateView(name, context, attrs); } else { view = null; }//Activity赋值了mPrivateFactory,但是Activity类中的onCreateView实现为空,所以View依然为空 if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); }if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { //判断是否为自定义的View,有.的是自定义view,没有.的是系统的View if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); //往下看3.4 } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } }return view; } ... }}

分析:
  1. 首先把context转换成ContextThemeWrapper,可以带上主题属性,传递给View的创建;
  2. 调用view的创建之前,会先判断mFactory,mFactory2和mPrivateFactory是否为空;
  3. 通常如果我们继承的是Activity类,mFactory和mFactory2是null,没有被赋值,而mPrivateFactory虽然在Activity中设置了,但是Factory得onCreateView方法没有被复写,所以最后View的创建会调用onCreateView()方法(3.4);
  4. 如果我们继承的是AppCompatActivity,那么会调用mFactory2接口的onCreateView方法,往下看4;
3.4、利用反射创建name对应的View
public abstract class LayoutInflater {//用于传递给view的构造方法,保存和context和attr final Object[] mConstructorArgs = new Object[2]; private Filter mFilter; //View实例化过滤器 private HashMap mFilterMap; //过滤器记录//让我们定义是否允许这个Class对象的创建 public interface Filter { boolean onLoadClass(Class clazz); }//用于给我们设置过滤器,可以过滤某些view的创建 public void setFilter(Filter filter) { mFilter = filter; if (filter != null) { mFilterMap = new HashMap(); } }//用来保存所有View的构造方法 private static final HashMap> sConstructorMap = new HashMap>(); -->中转方法,往下调用 protected View onCreateView(View parent, String name, AttributeSet attrs) throws ClassNotFoundException { return onCreateView(name, attrs); }-->中转方法,拼接系统View的全类名,再往下调用 protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { return createView(name, "android.view.", attrs); //系统的View要拼接全类名 }-->传入name,通过反射实例化对应的View public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { Constructor constructor = sConstructorMap.get(name); //获取构造方法 //判断constructor的类加载器和LayoutInflater或者Context的类加载器是否为同一个 if (constructor != null && !verifyClassLoader(constructor)) { constructor = null; sConstructorMap.remove(name); } Class clazz = null; try {if (constructor == null) { //获取对应View的Class对象 clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); if (mFilter != null && clazz != null) {//Filter接口不设置的话,mFilter为空 boolean allowed = mFilter.onLoadClass(clazz); if (!allowed) { failNotAllowed(name, prefix, attrs); } } constructor = clazz.getConstructor(mConstructorSignature); //获取View的构造方法 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) { // 获取Class对象 clazz = mContext.getClassLoader().loadClass( prefix != null ? (prefix + name) : name).asSubclass(View.class); //如果Class对象不为空,且onLoadClass返回true,过滤这个view的创建 boolean allowed = clazz != null && mFilter.onLoadClass(clazz); mFilterMap.put(name, allowed); //记录是否过滤这个view的实例化 if (!allowed) { failNotAllowed(name, prefix, attrs); //抛出InflateException异常 } } else if (allowedState.equals(Boolean.FALSE)) { failNotAllowed(name, prefix, attrs); } } }//配置view构造方法的参数, 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); //调用反射,实例化view if (view instanceof ViewStub) { // Use the same context when inflating ViewStub later. final ViewStub viewStub = (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); //设置viewStub的LayoutInflater,用于后期填充viewStub对应的view } mConstructorArgs[0] = lastContext; return view; } ... } }

分析:
  • 实例化View是在createView方法里面;
  • 首先从构造方法的缓存里面获取View的构造方法,判断一下类加载器和LayoutInflater.class或者context的类加载器是否相同;
  • 如果View的构造方法为空,判断一下是否设置了这个View的实例化过滤,反射获取这个View的构造方法,并记录起来;
  • 如果View的构造方法不为空,判断是否设置了View过滤器,记录是否过滤这个view的实例化;
  • 最后调用constructor.newInstance()方法实例化View;
4、当我们继承AppCompatActivity,布局的填充会是另一种方式
-->如果继承的是AppCompatActivity,那么setContentView会调用到AppCompatActivity里面来 public class AppCompatActivity extends FragmentActivity implements AppCompatCallback, TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {@Override protected void onCreate(@Nullable Bundle savedInstanceState) { final AppCompatDelegate delegate = getDelegate(); //delegate是AppCompatDelegateImplV9的子类,最终会调用到AppCompatDelegateImplV9里面的installViewFactory方法 delegate.installViewFactory(); //这里会设置LayoutInflater的mFractory2,下面会讲到... }-->调用到这里的setContentView @Override public void setContentView(@LayoutRes int layoutResID) { //先获取AppCompatDelegate getDelegate().setContentView(layoutResID); }-->往下走getDelegate() @NonNull public AppCompatDelegate getDelegate() { if (mDelegate == null) { mDelegate = AppCompatDelegate.create(this, this); } return mDelegate; }}-->调用到AppCompatDelegate里面的create public abstract class AppCompatDelegate {public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) { return create(activity, activity.getWindow(), callback); }//下面的几个类都都是AppCompatDelegateImplV9的子类 private static AppCompatDelegate create(Context context, Window window, AppCompatCallback callback) { if (Build.VERSION.SDK_INT >= 24) { return new AppCompatDelegateImplN(context, window, callback); } else if (Build.VERSION.SDK_INT >= 23) { return new AppCompatDelegateImplV23(context, window, callback); } else if (Build.VERSION.SDK_INT >= 14) { return new AppCompatDelegateImplV14(context, window, callback); } else if (Build.VERSION.SDK_INT >= 11) { return new AppCompatDelegateImplV11(context, window, callback); } else { return new AppCompatDelegateImplV9(context, window, callback); //最后会调用AppCompatDelegateImplV9的setConetentView() } }}

当我们设置了继承AppCompatActivity,内部会通过getDelegate()方法,最终能获取到AppCompatDelegateImplV9获取其子类;
@RequiresApi(14) class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase implements MenuBuilder.Callback, LayoutInflater.Factory2 {-->这个方法在AppCompatActivity的onCreate()中被调用,设置LayoutInflater的mFactory2 @Override public void installViewFactory() { LayoutInflater layoutInflater = LayoutInflater.from(mContext); if (layoutInflater.getFactory() == null) { LayoutInflaterCompat.setFactory2(layoutInflater, this); } else { if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) { Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" + " so we can not install AppCompat's"); } } }-->LayoutInflater.Factory2的接口方法,LayoutInflater中的mFactory2.onCreateView会回调进来这个方法 @Override public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) { //这里会回调我们的Activity中的onCreateView方法,如果没有实现,那么返回null final View view = callActivityOnCreateView(parent, name, context, attrs); if (view != null) { return view; }//往下调用createView方法实例化View return createView(parent, name, context, attrs); }//回调Activity的onCreateView,如果我们有复写,返回onCreateView方法的结果 View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) { // Let the Activity's LayoutInflater.Factory try and handle it if (mOriginalWindowCallback instanceof LayoutInflater.Factory) { final View result = ((LayoutInflater.Factory) mOriginalWindowCallback) .onCreateView(name, context, attrs); if (result != null) { return result; } } return null; }//中转方法,用来加入一些判断 @Override public View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (mAppCompatViewInflater == null) { mAppCompatViewInflater = new AppCompatViewInflater(); }boolean inheritContext = false; if (IS_PRE_LOLLIPOP) {//如果sdk版本低于21,就是5.0以下的手机 inheritContext = (attrs instanceof XmlPullParser) // If we have a XmlPullParser, we can detect where we are in the layout ? ((XmlPullParser) attrs).getDepth() > 1 // Otherwise we have to use the old heuristic : shouldInheritContext((ViewParent) parent); }//通过AppCompatViewInflater的createView方法实例化View return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */ true, /* Read read app:theme as a fallback at all times for legacy reasons */ VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */ ); } }

分析:
  • installViewFactory()方法在AppCompatActivity的onCreate()方法中被调用,也就是我们自己的Activity的onCreate()方法;
  • installViewFactory()方法中调用了LayoutInflaterCompat.setFactory2(layoutInflater, this),这里给LayoutInflater设置了mFractory2;
  • 也就是我们在调用setContentView()方法之前,就先设置好了LayoutInflater的mFractory2,那么LayoutInflater的createViewFromTag()方法会回调进来AppCompatDelegateImplV9的onCreateView(LayoutInflater.Fractory2接口方法)方法;
  • 经过一连串的调用,如果我们没有实现onCreateView方法,那么最终会调用AppCompatViewInflater的createView,来实例化View;
class AppCompatViewInflater { public final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) { final Context originalContext = context; //配置一下主题,上下文,略过 ...View view = null; //根据标签名,返回android.support.v7.widget中的控件 //如果是继承Activity,返回的是android.view中的控件 switch (name) { case "TextView": view = new AppCompatTextView(context, attrs); break; case "ImageView": view = new AppCompatImageView(context, attrs); break; case "Button": view = new AppCompatButton(context, attrs); break; case "EditText": view = new AppCompatEditText(context, attrs); break; case "Spinner": view = new AppCompatSpinner(context, attrs); break; case "ImageButton": view = new AppCompatImageButton(context, attrs); break; case "CheckBox": view = new AppCompatCheckBox(context, attrs); break; case "RadioButton": view = new AppCompatRadioButton(context, attrs); break; case "CheckedTextView": view = new AppCompatCheckedTextView(context, attrs); break; case "AutoCompleteTextView": view = new AppCompatAutoCompleteTextView(context, attrs); break; case "MultiAutoCompleteTextView": view = new AppCompatMultiAutoCompleteTextView(context, attrs); break; case "RatingBar": view = new AppCompatRatingBar(context, attrs); break; case "SeekBar": view = new AppCompatSeekBar(context, attrs); break; }//如果view还是空,那么就走反射实例化View if (view == null && originalContext != context) { view = createViewFromTag(context, name, attrs); }if (view != null) { // If we have created a view, check its android:onClick checkOnClickListener(view, attrs); }return view; } }

这里其实也很easy,判断一下标签名,直接新建一个android.support.v7.widge包中的控件;如果还是为空,就调用createViewFromTag利用反射实例化;
总结:
1、如果我们的MainActivity设置继承Activity,那么setContentView是会调用PhoneWindow的setContentView(),然后调用LayoutInflater.inflater()方法进行布局填充;
2、LayoutInflater中首先会用XmlPullParser对布局文件进行解析,然后循环遍历xml中的所有标签;
3、对标签的名字进行判断,处理一些特殊标签,然后普通的View标签调用createViewFromTag方法进行转换;
4、createViewFromTag方法会判断一下是否有设置Factory接口并且实现onCreateView方法来实例化View,最后如果view还是为空,则调用createView方法,通过标签的全类名反射进行View的实例化;
5、如果我们的MainActivity设置继承AppCompatActivity,AppCompatActivity的onCreate中会设置LayoutInflater的mFactory2,对View的实例化进行代理;
6、AppCompatDelegateImplV9类中继承了LayoutInflater.Factory2方法,并实现了onCreateView方法,那么View的实例化由AppCompatDelegateImplV9这里进行主导;
7、AppCompatDelegateImplV9会调用AppCompatViewInflater的createView方法实例化View,对标签名进行判断,如果是系统的控件名,返回android.support.v7.widge包中对应的控件,其他控件则用反射进行实例化。

    推荐阅读