美文网首页
LayoutInflater解析

LayoutInflater解析

作者: 就叫汉堡吧 | 来源:发表于2022-11-30 15:20 被阅读0次
  • 概述

    在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.viewandroid.widgetandroid.webkitandroid.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来保存。

相关文章

网友评论

      本文标题:LayoutInflater解析

      本文链接:https://www.haomeiwen.com/subject/rrhsfdtx.html