LayoutInflater & Factory2

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。
未经允许不得转载

在这里插入图片描述

一、导读

我们继续总结学习基础知识,温故知新。

我们在学习布局加载原理的时候,用到了这个类,他的作用还是很多的,这里我们花点时间集中来看一看。
再深入学习一番。

二、概览

LayoutInflater 的作用是将布局XML文件实例化为其相应的View对象,是一个视图填充器。
除了基本的布局解析功能,LayoutInflater 还可以用于实现 动态换肤、 视图转换、 属性转换等多种功能。

这是一个抽象类,下面我们进入学习模式。

public abstract class LayoutInflater

三、使用

3.1 LayoutInflater 实例获取


//1 如果是在 Activity 或 Fragment 可直接获取到实例
LayoutInflater inflater = getLayoutInflater();//调用Activity的getLayoutInflater()

//2 通过 LayoutInflater 的静态方法 from 获取
LayoutInflater inflater = LayoutInflater.from(context);

//3 通过系统服务 getSystemService 方法获取
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

其实最后都是调用context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);来获取,我们一起看看源码

ContextImpl.java

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

SystemServiceRegistry.java


    /**
     * Gets a system service from a given context.
     * @hide
     */
    public static Object getSystemService(ContextImpl ctx, String name) {

        // 这是一个ArrayMap,在启动时就填充好了数据,我们根据名字来查找对应的service
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        
        final Object ret = fetcher.getService(ctx);
        
        return ret;
    }

在看看初始化

    private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
                new ArrayMap<String, ServiceFetcher<?>>();
    static {
        
        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
        
    }
    
    private static <T> void registerService(@NonNull String serviceName,
        @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
        SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
    }

再来看看 CachedServiceFetcher

    static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {

        @Override
        public final T getService(ContextImpl ctx) {

            T ret = null;

            for (;;) {
                boolean doInitialize = false;
                synchronized (cache) {
                    // 从缓存里面取
                    T service = (T) cache[mCacheIndex];
                    if (service != null) {
                        ret = service;
                        break; // exit the for (;;)
                    }
                }

                if (doInitialize) {
                    T service = null;
                    try {
                        // 缓存里面没有,则创建一个
                        service = createService(ctx);
                        
                    } catch (ServiceNotFoundException e) {
                    } finally {
                        
                    }
                    ret = service;
                    break; // exit the for (;;)
                }
            }
            return ret;
        }

        public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
    }

对于 LayoutInflater 来说,最终获得的服务对象为 PhoneLayoutInflater,这个在静态注册方法的回调里面

    return new PhoneLayoutInflater(ctx.getOuterContext());

这其实是一个隐藏的类


/**
 * @hide
 */
public class PhoneLayoutInflater extends LayoutInflater {
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };

    @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) {
            }
        }

        return super.onCreateView(name, attrs);
    }

    public LayoutInflater cloneInContext(Context newContext) {
        return new PhoneLayoutInflater(this, newContext);
    }
}

3.2 调用 inflate 方法解析

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
    
    

    /**
     * 
     * @param resource xml资源id
     * @param root     父布局
     * @param attachToRoot   是否把inflate得到的View对象添加到root中
     *                       false,表示不直接添加到root,如果要添加view,我们需要手动添加
     *                       true,表示直接添加到root中
     * 这里还要注意root为空及不为空时,的意思                      
     *                       
     */
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();

        // 解析预编译的布局
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        
        // 构造 XmlPull 解析器
        XmlResourceParser parser = res.getLayout(resource);
        try {
            
            // 执行解析
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

注意:
我们给控件所指定的layout_weight和layout_height等属性表示一个控件在容器中大小,就是说这个控件必须在容器中,如果没有添加进容器,
其实就是失效的,所以
root为空,即不为填充控件指定父布局,那xml里定义的最外层view的layoutparam将全部无效。

设置root不为空,是为了让布局宽高有效,attachToRoot就是不自动将inflateLayout.xml填充到父容器中。

大家可以自己去做个试验,这块的代码逻辑在inflate方法里面;


    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            try {
                
                // "merge" 标签处理,
                if (TAG_MERGE.equals(name)) {

                } else {
                    
                    创建 LayoutParams
                    ViewGroup.LayoutParams params = null;

                    root不为空 且 attachToRoot 为false, 给子view设置宽高属性,
                    if (root != null) {
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    
                    root不为空 且 attachToRoot 为true,则将view添加到root中, 此时设置的宽高属性会生效
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    root为空,这个时候attachToRoot无论是true还是false都是没有意义的,根节点设置的高宽会失效。
                    返回创建的view
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } finally {
            }

            return result;
        }
    }

再后面就是通过反射进行view的创建了,我们看看解析的过程。


    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {

            // 最终生成的view
            View result = root;

            try {
                
                // 获取最外层的标签
                final String name = parser.getName();

                // "merge" 标签处理,
                if (TAG_MERGE.equals(name)) {
                    // 递归执行解析
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    
                    根据xml文件生成的view,为xml文件的根view
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    以 temp 即xml根view 为 root,递归执行解析
                    rInflateChildren(parser, temp, attrs, true);
                    
                }

            } finally {
            }

            return result;
        }
    }
    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (TAG_REQUEST_FOCUS.equals(name)) {
                
            } else {
                
                // 创建view
                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);
            }
        }

    }

后面代码都是view的创建,感兴趣的自行跟踪代码。

3.3

四、 LayoutInflater.Factory (2)

实例化 View 的优先顺序为:Factory2 / Factory -> mPrivateFactory -> PhoneLayoutInflater;

通过源码,我们知道,在setContentView时,我们需要先创建view,首先会对Factory、Factory2的设置判断,
如果有设置那么就会通过设置Factory、Factory2进行生成View,我们姑且简单的理解为是一个拦截器,直接在
这里创建view,然后返回。

使用 Factory2 接口可以拦截实例化 View 对象的步骤,

Factory & Factory2其实就是两个接口,view的创建过程需要自己实现,在这个地方,我们可以做自己想做的事情,

  • 比如创建view,添加log,或者其他的。
  • 在XML布局中自定义标签名称
  • 全局替换系统控件为自定义View
  • 替换app中字体
  • 全局换肤
  • 获取控件加载耗时等

Factory2 继承自 Factory,其方法多了一个父View。

    public interface Factory {
        @Nullable
        View onCreateView(@NonNull String name, @NonNull Context context,
                @NonNull AttributeSet attrs);
    }

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

4.1 使用

    LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
        @Override
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {

            // 创建view
            View view = getDelegate().createView(parent, name, context, attrs);
            
            // 返回生成的view
            return view;
        }

        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            return null;
        }
    });

我们看下view的创建
AppCompatDelegateImpl.java


    public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
    
        return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
    }

    final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        Context originalContext = context;

        View view = null;
        switch (name) {
            case "TextView":
                view = this.createTextView(context, attrs);
                this.verifyNotNull((View)view, name);
                break;
            case "ImageView":
                view = this.createImageView(context, attrs);
                this.verifyNotNull((View)view, name);
                break;
                
             ...
                
            default:
                view = this.createView(context, name, attrs);
        }
        
        return (View)view;
    }

4.2 注意点

需要注意的是,setFactory2一定是要在super.onCreate前调用的,我们从源码中来找答案 。
在 AppCompatDialog & AppCompatActivity 初始化时,都通过setFactory2()设置了拦截器,设置的对象是 AppCompatDelegateImpl:


    protected void onCreate(@Nullable Bundle savedInstanceState) {
        AppCompatDelegate delegate = this.getDelegate();
        
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);

        super.onCreate(savedInstanceState);
    }

我们动动小手来跟一下installViewFactory的代码


    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
        
        如果AppCompatActivity没有在onCreate之前设置LayoutInflaterFactory,
        那么AppCompatActivity会尝试设置一个Factory2,其中Factory2AppCompatDelegate的具体子类代码中实现
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
            
        } else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
        }

    }

如果我们设置了就用我们的,如果没有就用系统的,

通过上面代码,我们知道,如果没有在onCreate前设置factory,会默认设置一个,如果我们在oncreate后面在设置一个是或抛出异常的,我们看代码


    /**
     * 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 {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }

AppCompatViewInflater 与 LayoutInflater 的核心流程差不多,主要差别是前者会将等标签解析为AppCompatTextView对象

factory的用途我们后面再写一篇文章一起学习换肤。

五、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

未经允许不得转载

ddd

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android西红柿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值