• Android LayoutInflater&LayoutInflaterCompat源码解析


    本文分析版本: Android API 23,v4基于 23.2.1

    1 简介

    实例化布局的XML文件成相应的View对象。它不能被直接使用,应该使用getLayoutInflater()getSystemService(Class)来获取已关联了当前Context并为你正在运行的设备正确配置的标准LayoutInflater实例对象。 例如:

    LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    为了创建一个对于你自己的View来说,附加了LayoutInflater.FactoryLayoutInflater,你需要使用cloneInContext(Context)来克隆一个已经存在LayoutInflater,然后调用setFactory(LayoutInflater.Factory)来替换成你自己的Factory。

    由于性能原因,View的实例化很大程度上依赖对于xml文件在编译时候的预处理。因此,目前使用LayoutInflater不能使用直接通过原始xml文件获取的XmlPullParser,只能使用一个已编译的xml资源返回的XmlPullParser((R.something file.)。

    2 获取LayoutInflater的三种方式:

    1. LayoutInflater inflater = getLayoutInflater();//调用Activity的getLayoutInflater()
    2. LayoutInflater inflater = LayoutInflater.from(context);
    3. LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    但是,这三种方式本质上还是一样的:

    1. getLayoutInflater()调用的还是Activity中的方法:

      public LayoutInflater getLayoutInflater() {
          return getWindow().getLayoutInflater();
      }
      

      其中Window是一个抽象类,它 唯一 实现类是PhoneWindow

      com/android/internal/policyPhoneWindow.java

      public PhoneWindow(Context context) {
          super(context);
          mLayoutInflater = LayoutInflater.from(context);
      }
      /**
       * Return a LayoutInflater instance that can be used to inflate XML view layout
       * resources for use in this Window.
       *
       * @return LayoutInflater The shared LayoutInflater.
       */
      @Override
      public LayoutInflater getLayoutInflater() {
          return mLayoutInflater;
      }
      

      可以看到,通过·ActivitygetLayoutInflater()最终调用的还是第二种方法。

    2. 通过 LayoutInflater.from(context); 获取LayoutInflater对象。

      android/view/LayoutInflater.java

      /**
       * Obtains the LayoutInflater from the given context.
       */
      public static LayoutInflater from(Context context) {
          //通过获取系统服务的方式获取到LayoutInflater实例对象
          LayoutInflater LayoutInflater =
                  (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
          if (LayoutInflater == null) {
              throw new AssertionError("LayoutInflater not found.");
          }
          return LayoutInflater;
      }
      

    以上两种方法,最终还是调用了context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)获取LayoutInflater实例对象。

    最终:不管以什么样的方式获取到LayoutInflater对象,最终都是通过系统服务来获取实例对象

    3 主要方法预览

    • public static LayoutInflater from(Context context);

      获取LayoutInflater实例化对象。

    • public void setFactory(Factory factory);

      设置使用当前LayoutInflater创建View的自定义实例化工厂。通过设置自定义工厂,可以在系统实例化View的时候进行一些拦截操作,比如可以把本来的TextView拦截成Button、给TextView统一指定字体等。

    • public void setFactory2(Factory2 factory);

      同上,区别是工厂2多了对实例化View的时候Parent的支持。在API 11引入。

    • public void setFilter(Filter filter);

      给当前LayoutInflater设置过滤器,如果要被填充的View不被这个过滤器允许,则会抛出InflateException。这个过滤器会覆盖当前LayoutInflater之上的任何之前设置过的过滤器。

    • public View inflate(@LayoutRes int resource, @Nullable ViewGroup root);

      把指定的布局资源填充成View。如果root不为空则把填充的View添加到root上,如果root为空则不添加。

    • public View inflate(XmlPullParser parser, @Nullable ViewGroup root);

      通过布局xml资源的解析器把布局资源填充成View。如果root不为空则把填充的View添加到root上,如果root为空则不添加。

    • public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot);

      把指定的布局资源填充成View。如果root不为空并且attachToRoot为true,则把填充的View添加到root上,否则不添加。

    • public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot);

      通过布局xml资源的解析器把布局资源填充成View。如果root不为空并且attachToRoot为true,则把填充的View添加到root上,否则不添加。

    • public final View createView(String name, String prefix, AttributeSet attrs);

      通过View的名称,前缀和attrs属性实例化View。

    • protected View onCreateView(String name, AttributeSet attrs);

      通过View的父View,View名称和attrs属性实例化View(最终调用的是createView(String name, String prefix, AttributeSet attrs);)。

    • protected View onCreateView(View parent, String name, AttributeSet attrs);

      通过View的父View,View名称、前缀和attrs属性实例化View(最终调用的是createView(String name, String prefix, AttributeSet attrs);)。

    • View createViewFromTag(View parent, String name,Context context, AttributeSet attrs);

      通过View的父View,View的名称、attrs属性实例化View(内部调用onCreateView()createView())。

    • void rInflate(XmlPullParser parser, View parent, Context context, final AttributeSet attrs, boolean finishInflate);

      解析Parent的子View并添加到Parent上(递归调用)。

    4 流程预览

    // 把xml布局资源或者通过资源解析器实例化View
    inflate{
        if(merge标签){
            // 递归实例化根节点的子View
            rInflate();
            // 返回是父View(因为根节点是merge标签)
        }else{
            // 实例化根节点的View
            createViewFromTag();
            // 递归实例化跟节点的子View
            rInflateChildren()
    
            // 这个需要注意
            if(父View是空或者不把填充的View添加到父View){
                返回根节点View
            }else{
                返回父View
            }
        }
    }
    
    // 通过View的名称实例化View
    createViewFromTag{
        //各个工厂先onCreateView()
        onCreateView()或者createView()
    }
    
    // 递归实例化Parent的子View
    rInflate{
        // 解析请求焦点
        parseRequestFocus()
        // 解析include标签
        parseInclude()
        // 通过View的名称实例化View
        createViewFromTag()
        rInflate
    }
    

    注意 ,在调用inflate方法的时候,传入的参数不一样,返回的View可是有区别的,总结起来就是:

    1. 传入的父View为空或者不添加到父View上,则返回根节点的View。
    2. 其他(也就是根节点是merge、父View不为空且添加到父View上,返回的是父View)。

    5 流程详情

    关键字段:

    /************************字段定义区**********************/
    // 反射调用构造方法的两个参数
    final Object[] mConstructorArgs = new Object[2];
    // 反射构造方法的参数类型
    static final Class<?>[] mConstructorSignature = new Class[] {
            Context.class, AttributeSet.class}; 
    protected final Context mContext;
    

    注:以下我们用 “当前View”来指代该操作的View,用“父View”指代“当前View”的父View。

    5.1 inflate方法解析

    inflate 方法主要是把布局资源实例化成View并返回。

    通过 获取LayoutInflater的三种方式 我们知道,通过布局文件填充成View对象最终调用的是下面两个方法:

    public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
        if (DEBUG) System.out.println("INFLATING from resource: " + resource);
        //获取布局资源的xml解析器,注意:在开头的时候我们强调过,普通的xml是不被支持的,必须是经过编译器处理过的。
        XmlResourceParser parser = getContext().getResources().getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    } 
    

    最终调用的是此方法:

    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
            // from传入的Context
            final Context inflaterContext = mContext;
            // 判断parser是否是AttributeSet,如果不是则用XmlPullAttributes去包装一下。
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            // 保存之前的Context
            Context lastContext = (Context) mConstructorArgs[0];
            // 赋值为传入的Context
            mConstructorArgs[0] = inflaterContext;
            // 默认返回的是传入的Parent
            View result = root;
    
            try {
                // 查找开始标签
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
    
                //如果没找到有效的开始标签则抛出InflateException
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }
    
                //获取控件的名称
                final String name = parser.getName();
    
                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }
    
                // 如果根节点是“merge”标签
                if (TAG_MERGE.equals(name)) {
                    // 根节点为空或者不添加到根节点上,则抛出异常。
                    // 因为“merge”标签必须是要被添加到父节点上的,不能独立存在。
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    // 递归实例化root(也就是传入Parent)下所有的View
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // temp是当前xml的根节点的View。通过父View、View名、Context、属性,来实例化View。也即实例化根节点的View。
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
                    ViewGroup.LayoutParams params = null;
    
                    // 如果传入Parent不为空
                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // 创建父View类型的LayoutParams参数
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // 如果不把填充的View 关联在父View上,则把父View的LayoutParams参数设置给它
                            // 如果把填充的View关联在父View上,则会走下面addView的逻辑
                            temp.setLayoutParams(params);
                        }
                    }
    
                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }
                    // 实例化根节点View下面的所有子View。
                    // TODO ..................
                    rInflate(parser, temp, attrs, true);
                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }
    
                    // Google建议关联所有找到的View
                    // 如果根节点不为null,并且需要把根节点View关联到Parent上,则使用addView方法把布局填充成的View树添加到Parent上。
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
    
                    // 决定返回的RootView(也即传入的Parent)还是xml中的根节点的View。
                    // 如果传入的Parent为空 或 实例化的View不添加到Parent上,则返回布局文件的根节点的View
                    // 否则,返回Parent
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
    
            } catch (XmlPullParserException e) {
                InflateException ex = new InflateException(e.getMessage());
                ex.initCause(e);
                throw ex;
            } catch (IOException e) {
                InflateException ex = new InflateException(
                        parser.getPositionDescription()
                        + ": " + e.getMessage());
                ex.initCause(e);
                throw ex;
            } finally {
                // Don't retain static reference on context.
                // 把这之前保存的Context从新放回全局变量中。
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
            }
    
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    
            return result;
        }
    }
    

    5.2 createViewFromTag方法解析

    createViewFromTag 方法主要通过要实例化View的父View、要实例化View的名称、Context上下文、属性值来实例化View。该方法主要做的操作有:

    1. 先尝试用用户设置的Factory以及自己私有的Factory来实例化View。
    2. 如果这几个Factory都没有实例化View,则调用onCreateView或者createView来实例化View。

    代码分析:

    /*
     * 缺省方法可见性,好让BridgeInflater能重写它。
     * 根据父View、View名称、Context、属性实例化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) {
    
        // 如果是View标签,则用class指向的类的完整名称来替换当前名称。(我们都知道,用Fragment的时候,可指定 class="Fragment完整路径名",其他widget控件也类似)
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
    
        // 应用主题包装,如果允许并且已经被指定
        // Apply a theme wrapper, if allowed and one is specified.
        if (!ignoreThemeAttr) {
            // 获取Context中主题属性
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            // 如果包含了主题,则用ContextThemeWrapper包装一下
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }
    
        // 如果根标签是“1995”,则创建一个“BlinkLayout”(其实就是一个FrameLayout)
        // ps:这个没见过在哪里用到过。
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }
    
        try {
            View view;
            // 尝试通过 mFactory2 或者 mFactory来创建View,这两个是通过setFactory和setFactory2来设置的。
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }
    
            // 如果没有设置自定义工厂并且LayoutInflater本身私有的View工厂不为空,则用私有View工厂创建View。
            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }
    
            // 如果View还为空,也即没有工厂,或者工厂未能正确创建View,则尝试通过自身的方法实例化View
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
    
                        // 如果View标签中没有".",则代表是系统的widget,则调用onCreateView,这个方法会通过"createView"方法创建View
                        // 不过前缀字段会自动补"android.view."。
                        view = onCreateView(parent, name, attrs);
                    } else {
    
                        // 非系统控件,则name本身就是控件的完整路径名。
                        //通过widget完整路径名以及属性创建View。
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
    
            return view;
        } catch (InflateException e) {
            throw e;
    
        } catch (ClassNotFoundException e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;
    
        } catch (Exception e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;
        }
    }
    

    5.3 onCreateView和createView方法解析

    onCreateView 有两个重载方法,最终调用的是createView(String name, String prefix, AttributeSet attrs)

    createView 主要做的操作有:

    1. 先通过Filter,看是否过滤。
    2. 利用反射实例化View对象。

    源码分析:

    protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }
    
    protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        // 系统控件,前缀自动补上"android.view."
        return createView(name, "android.view.", attrs);
    }
    
    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        // 通过以View的name为key,查询构造函数的缓存map中时候已经有该View的构造函数。
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        Class<? extends View> clazz = null;
    
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
    
            // 构造函数在缓存的map中没有,则尝试去创建并添加。
            if (constructor == null) {
                // 通过 类名去加载控件的字节码
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                //如果有自定义的过滤器并且加载到字节码,则通过过滤器判断是否允许加载该View。
                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 {
                // setFilter()可能会在类的构造函数被添加到map之后,所以获取到map中的构造函数后还需要判断是否过滤。
                if (mFilter != null) {
                     // 过滤的map中是否已经包含了此类名。
                    Boolean allowedState = mFilterMap.get(name);
                    // 当前类名没有被放到过滤的缓存map中
                    if (allowedState == null) {
                        // 重新加载类的字节码
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);
                        // 重新通过过滤器判断是否过滤。
                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        // 把过滤结果放到过滤的缓存map中。
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            // 如果要过滤,则抛出异常。
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        // 缓存构造函数的map中已经保存了当前要实例化的View的构造函数并且是要过滤的,抛出异常。
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }
    
            // 实例化类的参数数组,0 是获取LayoutInflater传入的Context,1 是View的属性
            Object[] args = mConstructorArgs;
            args[1] = attrs;
            // 通过构造函数实例化View(看到此行就知道,为什么自定义View或者ViewGroup的时候,如果在布局中使用的话,必须重写两个参数的构造函数了。)
            final View view = constructor.newInstance(args);
            // 如果当前View是ViewStub,则把布局填充器设置给它。(因为ViewStub在此刻并不会填充期子View,而是等需要的时候由用户手动触发。)
            if (view instanceof ViewStub) {
                // 把当前LayoutInflater的克隆传递给ViewStub,让ViewStub实例化的时候用,因为ViewStub只是在需要的时候才会实例化View。
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;
    
        } catch (NoSuchMethodException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;
    
        } catch (ClassCastException e) {
            // If loaded class is not a View subclass
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Class is not a View "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;
        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (clazz == null ? "<unknown>" : clazz.getName()));
            ie.initCause(e);
            throw ie;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
    

    5.4 rInflateChildren和rInflate方法解析

    rInflate方法主要是遍历传入的Parent的子节点,实例化Parent的所有子View。

    当遍历的时候发现当前View还有子View则递归调用此方法继续实例化当前View的子View。

    这里面主要的操作有:

    • parseRequestFocus(),处理请求焦点。
    • parseInclude(),处理include标签。
    • 实例化1995或一般View并添加到当前View的父View上。

    源码分析:

    /**
     * 循环方法用来深入xml的层级并且实例化内部的View(非根节点View),这个方法通过调用rInflate,并使用Parent的Context作为
     * 实例化View的Context。
     */
    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
    
    /**
     * 循环方法用来深入xml的层级并且实例化view以及View的子View,
     * 最后调用onFinishInflate()方法
     */
    void rInflate(XmlPullParser parser, View parent,Context context, final AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        // 获取当前xml解析的深度
        final int depth = parser.getDepth();
        int type;
        // 循环遍历xml节点
        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)) {
                // 解析<tag>元素,并且设置键控标签在它包含的View上。最终调用的是View的view.setTag(key, value);方法
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                //处理include标签
                if (parser.getDepth() == 0) {
                    // 最外层使用include标签抛出异常。
                    throw new InflateException("<include /> cannot be the root element");
                }
                // 解析include标签引入的布局
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                // 如果是merge标签则抛出异常(因为此方法实例化的是xml根节点的子View,所以非根节点不能使用merge标签。)
                throw new InflateException("<merge /> must be the root element");
            } else {
                // 一般性的View
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                // 获取父View的LayoutParams,并在把View添加到父View的时候带过去
                //(这里解释了,为什么自己手动new 一个View,添加到父View上的时候需要new父View的LayoutParams参数而不是自己的LayoutParams参数。)
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                // 递归遍历实例化
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }
        // 如果父View下的所有View都完成填充,则调用父View的onFinishInflate()方法。
        if (finishInflate) parent.onFinishInflate();
    }
    

    5.5 parseInclude方法解析

    /**
     * 解析include标签
     */
    private void parseInclude(XmlPullParser parser, Context context, View parent,
            AttributeSet attrs) throws XmlPullParserException, IOException {
        int type;
    
        // 如果include标签的父View是ViewGroup,则继续,否则抛出异常。
        if (parent instanceof ViewGroup) {
            // 使用主题包装。如果include中的View有自己的attr属性,则忽略。
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            final boolean hasThemeOverride = themeResId != 0;
            if (hasThemeOverride) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
    
            // 获取Layout标签指向布局资源id
            int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
            if (layout == 0) {
                // 获取Layout的value值
                final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
                if (value == null || value.length() <= 0) {
                    throw new InflateException("You must specify a layout in the"
                            + " include tag: <include layout="@layout/layoutID" />");
                }
    
                // 尝试解析"?attr/name"成id资源
                layout = context.getResources().getIdentifier(value.substring(1), null, null);
            }
    
            // include的布局可能引用了主题属性
            if (mTempValue == null) {
                mTempValue = new TypedValue();
            }
            // 尝试从主题中获取布局资源id,如果有的话。
            if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
                layout = mTempValue.resourceId;
            }
    
            // 如果还是无法找到布局id,抛出异常。
            if (layout == 0) {
                final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
                throw new InflateException("You must specify a valid layout "
                        + "reference. The layout ID " + value + " is not valid.");
            } else {
    
                // 获取include标签中布局的解析器
                final XmlResourceParser childParser = context.getResources().getLayout(layout);
    
                try {
                    final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
                    // 查找根节点
                    while ((type = childParser.next()) != XmlPullParser.START_TAG &&
                            type != XmlPullParser.END_DOCUMENT) {
                        // Empty.
                    }
                    // 如果找不到根节点,抛出异常。
                    if (type != XmlPullParser.START_TAG) {
                        throw new InflateException(childParser.getPositionDescription() +
                                ": No start tag found!");
                    }
    
                    // 获取标签名
                    final String childName = childParser.getName();
                    // 实例化merge标签
                    if (TAG_MERGE.equals(childName)) {
                        // <merge>标签不支持android:theme,所以不需要其他处理
                        rInflate(childParser, parent, context, childAttrs, false);
                    } else {
                        // 创建View实例化对象
                        final View view = createViewFromTag(parent, childName,
                                context, childAttrs, hasThemeOverride);
                        final ViewGroup group = (ViewGroup) parent;
    
                        // 获取View的id和可见性
                        final TypedArray a = context.obtainStyledAttributes(
                                attrs, R.styleable.Include);
                        final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
                        final int visibility = a.getInt(R.styleable.Include_visibility, -1);
                        a.recycle();
    
                        // 尝试加载<include />标签中的布局参数,如果父View无法生成布局参数(比如include标签下没有宽高参数是要抛出运行时异常的)
                        // 捕获运行时异常,然后用引用的Layout的attrs来创建布局参数
                        ViewGroup.LayoutParams params = null;
                        try {
                            // 使用include标签的attrs来生成布局参数
                            params = group.generateLayoutParams(attrs);
                        } catch (RuntimeException e) {
                            // Ignore, just fail over to child attrs.
                        }
                        if (params == null) {
                            // 如果include标签的attrs没有正确的生成布局参数,则使用Layout布局的attrs来生成布局参数
                            params = group.generateLayoutParams(childAttrs);
                        }
                        view.setLayoutParams(params);
    
                        // 实例化所有子View
                        rInflateChildren(childParser, view, childAttrs, true);
    
                        if (id != View.NO_ID) {
                            view.setId(id);
                        }
    
                        switch (visibility) {
                            case 0:
                                view.setVisibility(View.VISIBLE);
                                break;
                            case 1:
                                view.setVisibility(View.INVISIBLE);
                                break;
                            case 2:
                                view.setVisibility(View.GONE);
                                break;
                        }
                        // 把布局实例化的View添加到父View上。
                        group.addView(view);
                    }
                } finally {
                    childParser.close();
                }
            }
        } else {
            throw new InflateException("<include /> can only be used inside of a ViewGroup");
        }
    
        LayoutInflater.consumeChildElements(parser);
    }
    

    6 LayoutInflaterCompat

    LayoutInflater我们用的很多,一般都是用来把布局填充成View,它里面有两个方法:

    // api 1引入
    setFactory(Factory factory)
    
    // api 11 引入
    setFactory2(Factory2 factory)
    

    这里面需要传入接口的实现类,如下:

    public interface Factory {
        /**
         * Hook you can supply that is called when inflating from a LayoutInflater.
         * You can use this to customize the tag names available in your XML
         * layout files.
         * 
         * <p>
         * Note that it is good practice to prefix these custom names with your
         * package (i.e., com.coolcompany.apps) to avoid conflicts with system
         * names.
         * 
         * @param name Tag name to be inflated.
         * @param context The context the view is being created in.
         * @param attrs Inflation attributes as specified in XML file.
         * 
         * @return View Newly created view. Return null for the default
         *         behavior.
         */
        public View onCreateView(String name, Context context, AttributeSet attrs);
    }
    
    public interface Factory2 extends Factory {
        /**
         * Version of {@link #onCreateView(String, Context, AttributeSet)}
         * that also supplies the parent that the view created view will be
         * placed in.
         *
         * @param parent The parent that the created view will be placed
         * in; <em>note that this may be null</em>.
         * @param name Tag name to be inflated.
         * @param context The context the view is being created in.
         * @param attrs Inflation attributes as specified in XML file.
         *
         * @return View Newly created view. Return null for the default
         *         behavior.
         */
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
    }
    

    通过上述接口可以看出来,新的setFactory2(Factory2 factory)比老的setFactory(Factory factory)在构建View的时候多传入了一个Parent View。如果你想用setFactory2(Factory factory)需要实现带Parent View和不带Parent View的两个方法,比较复杂,所以v4包中的LayoutInflaterCompat就为我们提供了兼容性处理,先看用法:

    LayoutInflater layoutInflater = getLayoutInflater();
    LayoutInflaterCompat.setFactory(layoutInflater, new LayoutInflaterFactory() {
    
            @Override
            public View onCreateView(View parent, String name, Context context,
                    AttributeSet attrs) {
                // name 是布局文件中View的名称,在这里可以坐很多操作,比如:
                // 给TextView设置字体。
                // 把TextView变成Button,如果需要的话。
    
                // 修改后的View,如果返回空则会调用LayoutInflater本身实例化View的方法,详情见createViewFromTag中try下面的逻辑。
                return null;
            }
        });
    

    举个例子: 现在我们用AS开发,一般默认是继承AppCompatActivity,在初始化的时候:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        // 获取兼容包的委托类
        final AppCompatDelegate delegate = getDelegate();
    
        // 安装Factory
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        if (delegate.applyDayNight() && mThemeId != 0) {
            // If DayNight has been applied, we need to re-apply the theme for
            // the changes to take effect. On API 23+, we should bypass
            // setTheme(), which will no-op if the theme ID is identical to the
            // current theme ID.
            if (Build.VERSION.SDK_INT >= 23) {
                onApplyThemeResource(getTheme(), mThemeId, false);
            } else {
                setTheme(mThemeId);
            }
        }
        super.onCreate(savedInstanceState);
    }
    

    AppCompatDelegate的实现类AppCompatDelegateImplV7中,·installViewFactory()

    @Override
    public void installViewFactory() {
        // 使用LayoutInflaterCompat进行兼容性适配
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            // 如果之前没有设置工厂,则自己实现接口然后传入,用来实现AppCompat特性,比如支持向低版本 tint着色等新特性。
            // 实际上它也是在工厂的实现类中用AppCompatXXX去替换XXX,比如用AppConpatTextView替换TextView等诸如此类。
            LayoutInflaterCompat.setFactory(layoutInflater, this);
        } else {
            // 如果已经设置了Factory并且不是当前类,则什么也不做,只打印日志。这样就会失去上述中tint等特性。
            if (!(LayoutInflaterCompat.getFactory(layoutInflater)
                    instanceof AppCompatDelegateImplV7)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }
    

    上述方法实际上调用LayoutInflaterCompat的下面的方法:

    public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
        IMPL.setFactory(inflater, factory);
    }
    

    IMPL是LayoutInflaterCompat中内部接口LayoutInflaterCompatImpl的实现类。这个接口有三个实现类:

    1. LayoutInflaterCompatImplBase。
    2. LayoutInflaterCompatImplV11。
    3. LayoutInflaterCompatImplV21。
    static final LayoutInflaterCompatImpl IMPL;
    static {
        final int version = Build.VERSION.SDK_INT;
        if (version >= 21) {
            // 5.0及其以上版本
            IMPL = new LayoutInflaterCompatImplV21();
        } else if (version >= 11) {
            // 3.0及其以上版本
            IMPL = new LayoutInflaterCompatImplV11();
        } else {
            // 低于3.0版本
            IMPL = new LayoutInflaterCompatImplBase();
        }
    }
    

    因此,调用LayoutInflaterCompat的setFactory方法,实际是调用对应版本的IMPL的setFactory方法。

    1. 低于3.0版本

      LayoutInflaterCompatImplBase中调用的是LayoutInflaterCompatBase的setFactory方法,

      static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
          inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
      }
      
      static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
          LayoutInflater.Factory factory = inflater.getFactory();
          if (factory instanceof FactoryWrapper) {
              return ((FactoryWrapper) factory).mDelegateFactory;
          }
          return null;
      }
      

      其中,FactoryWrapperLayoutInflaterCompatBase的静态内部类:

      class LayoutInflaterCompatBase {
      
          static class FactoryWrapper implements LayoutInflater.Factory {
      
              final LayoutInflaterFactory mDelegateFactory;
      
              FactoryWrapper(LayoutInflaterFactory delegateFactory) {
                  mDelegateFactory = delegateFactory;
              }
      
              @Override
              public View onCreateView(String name, Context context, AttributeSet attrs) {
                  return mDelegateFactory.onCreateView(null, name, context, attrs);
              }
      
              public String toString() {
                  return getClass().getName() + "{" + mDelegateFactory + "}";
              }
          }
      
          static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
              inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
          }
      
          static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
              LayoutInflater.Factory factory = inflater.getFactory();
              if (factory instanceof FactoryWrapper) {
                  return ((FactoryWrapper) factory).mDelegateFactory;
              }
              return null;
          }
      
      }
      

      核心的方法就是FactoryWrapperonCreateView,可以看到它调用的是带有Parent View参数的onCreateView方法,不过Parent View传的是null。

    2. 大于3.0小于5.0

      LayoutInflaterCompatImplV11中调用的就是LayoutInflaterCompatHC的setFactory方法,LayoutInflaterCompatHC中:

      static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
          // 如果传入的Factory不为空则包装一下,否则传空
          final LayoutInflater.Factory2 factory2 = factory != null
                  ? new FactoryWrapperHC(factory) : null;
          // 设置Factory2.
          inflater.setFactory2(factory2);
          // 获取当前Inflater的Factory。
          final LayoutInflater.Factory f = inflater.getFactory();
          // 如果属于Factory2则通过反射把mFactory赋值给mFactory2。
          if (f instanceof LayoutInflater.Factory2) {
              // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
              // We will now try and force set the merged factory to mFactory2
              forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
          } else {
              // 否则设置mFactory2为新创建的Factory2。
              // Else, we will force set the original wrapped Factory2
              forceSetFactory2(inflater, factory2);
          }
      }
      
      // 利用反射修改Factory2
      /**
       * For APIs >= 11 && < 21, there was a framework bug that prevented a LayoutInflater's
       * Factory2 from being merged properly if set after a cloneInContext from a LayoutInflater
       * that already had a Factory2 registered. We work around that bug here. If we can't we
       * log an error.
       * 对于版本>- 11 并且 < 21,如果调用cloneInContext从LayoutInflater克隆一个LayoutInflater,在FrameWork层有一个bug阻止了LayoutInflater的Factory2的合并,因为已经有一个Factory2被注册了,所在在此通过反射的方式去修改Factory2。
       */
      static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
          if (!sCheckedField) {
              try {
                  sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2");
                  sLayoutInflaterFactory2Field.setAccessible(true);
              } catch (NoSuchFieldException e) {
                  Log.e(TAG, "forceSetFactory2 Could not find field 'mFactory2' on class "
                          + LayoutInflater.class.getName()
                          + "; inflation may have unexpected results.", e);
              }
              sCheckedField = true;
          }
          if (sLayoutInflaterFactory2Field != null) {
              try {
                  sLayoutInflaterFactory2Field.set(inflater, factory);
              } catch (IllegalAccessException e) {
                  Log.e(TAG, "forceSetFactory2 could not set the Factory2 on LayoutInflater "
                          + inflater + "; inflation may have unexpected results.", e);
              }
          }
      }
      

      bug的具体产生原因见:LayoutInflater在Api 21以下的setFactory2的bug是怎么产生的

    3. 大于5.0

      static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
          inflater.setFactory2(factory != null
                  ? new LayoutInflaterCompatHC.FactoryWrapperHC(factory) : null);
      }
      

      这个其实没啥用,可以跟第二条合并的,但是最新的v4包没有改,但是在api >= 20的Android源码里面,其实已经这么做了:

      static final LayoutInflaterCompatImpl IMPL;
      static {
          final int version = Build.VERSION.SDK_INT;
          if (version >= 11) {
              IMPL = new LayoutInflaterCompatImplV11();
          } else {
              IMPL = new LayoutInflaterCompatImplBase();
          }
      }
  • 相关阅读:
    结对编程项目作业4
    团队编程项目进度
    团队编程项目作业2-团队编程项目代码设计规范
    现代软件工程 阅读笔记
    个人编程作业1-GIT应用
    结对编程项目作业2-开发环境搭建过程
    结对编程项目作业2-结对编项目设计文档
    课后作业-阅读任务-阅读提问
    《团队-科学计算器-模块测试过程》
    团队-科学计算器-模块开发过程
  • 原文地址:https://www.cnblogs.com/android-blogs/p/5619874.html
Copyright © 2020-2023  润新知