LayoutInflater 原理

333 阅读7分钟
前言

LayoutInflater对于我们Android开发来说应该是既熟悉又陌生。熟悉是因为我们所有的布局XML文件都是经过他来实例化为对应View实例,我们基本上经常跟他打交道;陌生是因为我们在开发的过程中是直接拿过来使用,对于他从何而来,除了实例化我们的布局文件还能用来做什么却不是很了解。

所以这篇文章重点讲下LayoutInflater原理

LayoutInflater 流程分析

获取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;
		}
}

上面是LayoutInflater类中的静态方法可以获取LayoutInflater 实例。我们可以看到LayoutInflater是一个抽象类,不能直接实例化LayoutInflater 方法里面是通过context.getSystemService获取系统提供的服务,而且具体实例对象就是PhoneLayoutInflater,我们可以从服务注册的地方查到。

SystemServiceRegistry 注册 LayoutInflater 服务

在SystemServiceRegistry类中的静态语句块中我们可以找到注册方法,正是返回了PhoneLayoutInflater的实例。

static {
  	....
    // 注册LayoutInflater 服务,这里创建的是PhoneLayoutInflater 的实例
  	registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                    //PhoneLayoutInflater第一次创建之后会缓存实例,下次会从缓存取
                    new CachedServiceFetcher<LayoutInflater>() {
                      @Override
                      public LayoutInflater createService(ContextImpl ctx) {
                        return new PhoneLayoutInflater(ctx.getOuterContext());
                      }});
  	....
}

CachedServiceFetcher会缓存PhoneLayoutInflater实例,后面获取的都是缓存实例

View的加载

加载View的几个方法:

// 方法一
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();
    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();
    }
}
// 方法三
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        final Context inflaterContext = mContext;
      	//1.从XML文件中获取属性集
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
            // Look for the root node.
            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) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }

                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
              	//2.解析属性集并创建返回xml中的根View temp
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    // Create layout params that match root, if supplied
                    //3.给temp View 创建params
                    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);
                    }
                }

                // Inflate all children under temp against its context.
              	//4.创建temp View 下的所有子View,并建立他们的层级关系
                rInflateChildren(parser, temp, attrs, true);

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            final InflateException ie = new InflateException(e.getMessage(), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } catch (Exception e) {
            final InflateException ie = new InflateException(parser.getPositionDescription()
                    + ": " + e.getMessage(), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        return result;
    }
}

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;
    boolean pendingRequestFocus = false;
	
  	// 遍历ziView
    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);
        } 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 {
          	//5.遍历执行createViewFromTag创建childView
            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);
        }
    }
		.....
}

前两个方法最终都会调到第三个方法,所以我们主要看第三个方法,主要是5个关键步骤:

  1. 从XML解析初属性集合,创建view用
  2. 根据属性集合通过createViewFromTag方法创建temp View
  3. 给temp View创建布局参数,根据初始化传递进来的attachToRoot变量是否要将布局参数关联到temp View上
  4. 执行rInflateChildren解析childView 并建立他们的层级关系
  5. 所有的childView都需要通过createViewFromTag来创建,同第二个步骤一样
createViewFromTag做了什么
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    ....

    try {
        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);
        }

      	//1.如果前面没有创建拦截创建View,那么久进入下面默认逻辑
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
              	//判断name是否是view的全称 例如com.view.TextView
                if (-1 == name.indexOf('.')) {
                  	//name名称拼接全称,最后还是调用createView
                    view = onCreateView(parent, name, attrs);
                } else {
                  	//2.通过反射创建View
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } catch (InflateException e) {
      	....
    } catch (ClassNotFoundException e) {
      	....
    } catch (Exception e) {
      	....
    }
}

在创建我们View之前,我们可以看到mFactory2、mFactory、mPrivateFactory 三个LayoutInflater的成员变量hook View的创建。也就是说Api 提供了一个可能,我们可以按照我们的想法来创建View,而且我们只需要通过代码设置到LayoutInflater实例中去,这种设计模式代码的嵌入性少。

如果我们没有拦截View的创建的话,他将会走接下来默认的流程,最后调用createView方法创建View

Factory

factory 接口
public interface Factory {
    public View onCreateView(String name, Context context, AttributeSet attrs);
}

public interface Factory2 extends Factory {
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

setFactory2是在SDK>11时候添加的,如果你是基于11以上的就使用setFactory2,否则就使用setFactory ,两者功能基本一致。当然你不想考虑兼容问题可以直接使用LayoutInflaterCompat.setFactory()。

设置Factory
/**
* Attach a custom Factory interface for creating views while using
* this LayoutInflater.  This must not be null, and can only be set once;
* after setting, you can not change the factory.  This is
* called on each element name as the xml is parsed. If the factory returns
* a View, that is added to the hierarchy. If it returns null, the next
* factory default {@link #onCreateView} method is called.
*
* <p>If you have an existing
* LayoutInflater and want to add your own factory to it, use
* {@link #cloneInContext} to clone the existing instance and then you
* can use this function (once) on the returned new instance.  This will
* merge your own factory with whatever factory the original instance is
* using.
*/
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);
    }
}

/**
 * Like {@link #setFactory}, but allows you to set a {@link Factory2}
 * interface.
 */
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 {
      	// 如果有factory 则用FactoryMerger将factory打包
        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);
    }
}

//PhoneLayoutInflater 中重载的方法
public LayoutInflater cloneInContext(Context newContext) {
    return new PhoneLayoutInflater(this, newContext);
}

//使用责任链设计模式将两个facotory 打包到一起:优先调用新设置的factory的方法,如果返回为空再用之前的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);
    }
}

上面的三个设置factory的方法我们总结以下几点:

  • setPrivateFactory是framework层使用的,我们不要使用。
  • mFactorySet成员变量标示,导致我们一个PhoneLayoutInflater 只能设置一次Fractory,如果已经设置过factory 我们需要调用cloneInContext 复制一个实例进行设置 factory
  • 通过cloneInContext复制的实例设置factory,他会创建FactoryMerger将之前的factory打包在一起,调用onCreateView的时候优先调用新设置的factory的方法
  • 总结上面3点我们可以总结出,如果要给LayoutInflater设置全局的factory,需要在Application设置保证,因为Activity还是Fragment 都会有设置factory的代码(这里就不展开了,可以从源码中可以看出来)
Factory 有什么用?

使用LayoutInflater Factory的这一特性可以做很多事:

  • 提高view构建的效率
    • 当我们使用自定义view时,需要在xml中使用完整类名,系统实际就是根据完整类名进行反射构建。我们可以自己new出view避免系统反射调用,提高效率。
  • 替换默认view实现,改变或添加属性
    • 替换系统控件
    • 一键换肤解决方案
总结

LayoutInflater Factory这一特性很强大,能做的远不止上面那些事。比如大部分“一键换肤”都是使用了这一特性来实现的。