美文网首页Android进阶之路Android-View篇
LayoutInflater 源码解析及应用(解决插件化中类型转

LayoutInflater 源码解析及应用(解决插件化中类型转

作者: trampcr | 来源:发表于2019-11-23 07:45 被阅读0次

    一、类型转换问题

    插件化过程中经常遇到这么一个问题:

    java.lang.ClassCastException: com.trampcr.CustomView cannot be cast to com.trampcr.CustomView
    

    明明相同的两个类,为什么会出现类型转换错误呢?

    我们知道判断两个类是否相同的依据有两点:

    • 全路径是否相同
    • 加载这两个类的 ClassLoader 是否相同

    这里明显全路径相同,那就是加载这两个类的 ClassLoader 不同,验证一下:

    // 打印当前类(CustomView.java)和获取到的 view 的 Classloader
    View rootView = LayoutInflater.from(context).inflate(R.layout.custom_view_layout, null);
    View view = null;
    try {
        view = (View) rootView.findViewById(R.id.custom_view);
    } catch (Exception e) {
        e.printStackTrace();
    }
        
    ClassLoader curClassloader = this.class.getClassLoader();
    Log.e("trampcr", "current ClassLoader = " + curClassloader);
    
    if (null != view) {
        ClassLoader classloader = view.getClass().getClassLoader();
        while (null != classloader) {
            Log.e("trampcr", "inflate ClassLoader = " + classloader);
            classloader = classloader.getParent();
        }
    }
    
    //打印结果
    current ClassLoader = BundleClassLoader[Bundlecom.trampcr.plugin.alpha]
    inflate ClassLoader = BundleClassLoader[Bundlecom.trampcr.plugin.beta]
    inflate ClassLoader = java.lang.BootClassLoader@9e408d8
    

    验证发现:当前类和获取到的 view 的 Classloader 确实不同。

    这里先说明原因及解决方案,后续再通过源码分析进行说明。

    原因: LayoutInflater.from(context).inflate() 底层是通过反射创建 View 对象,使用的 ClassLoader 和当前的 ClassLoader 不相同。

    解决方案::重写 LayoutInflater、LayoutInflater.Factory、LayoutInflater.Factory2,将 xml 转换成 View 对象时传入 ClassLoader,保证创建 View 的 ClassLoader 和当前 ClassLoader 相同。

    这里说的可能有点抽象,看完源码分析和解决方案分析,再返回来看这句话。

    二、LayoutInflater 源码解析

    从 LayoutInflater 的使用入手看起:

    LayoutInflater.from(context).inflate(R.layout.custom_view_layout, null);
    

    这里可以拆为两步:

    • LayoutInflater.from():创建 LayoutInflater 对象
    • layoutInflater.inflate():创建 View 对象

    1、LayoutInflater.from():创建 LayoutInflater 对象

    // LayoutInflater.java from() 方法作用:创建 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;
    }
    

    这里调用 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) 创建 LayoutInflater 对象,Context 是一个抽象类,它的实现是 ContextImpl。

    // ContextImpl.java
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
    
    // SystemServiceRegistry.java
    // private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new HashMap<String, ServiceFetcher<?>>();
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
    

    ContextImpl.getSystemService() 调用了 SystemServiceRegistry.getSystemService(),然后从 SYSTEM_SERVICE_FETCHERS 这个 HashMap 中获取 ServiceFetcher 对象,通过 ServiceFetcher.getService() 获取我们想要的对象。

    有 get() 肯定有 put(),找到 put() 是在 registerService() 方法中。

    // SystemServiceRegistry.java
    // 静态注册系统服务,这个方法在初始化时必须调用
    private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }
    

    接着往回看,哪里调用了 registerService(),最后发现在 SystemServiceRegistry 类的开头,有一个 static 代码块,其中包含了很多 registerService() 方法,我们常用的 ActivityManager、WindowManger 等服务都在这里注册的。

    SystemServiceRegistry.java
    final class SystemServiceRegistry {
        ...
        
        static {
            ...
    
            registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
                    new CachedServiceFetcher<ActivityManager>() {
                @Override
                public ActivityManager createService(ContextImpl ctx) {
                    return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
                }});
            ...
    
            registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                    new CachedServiceFetcher<LayoutInflater>() {
                @Override
                public LayoutInflater createService(ContextImpl ctx) {
                    return new PhoneLayoutInflater(ctx.getOuterContext());
                }});
            ...
            
            registerService(Context.WINDOW_SERVICE, WindowManager.class,
                    new CachedServiceFetcher<WindowManager>() {
                @Override
                public WindowManager createService(ContextImpl ctx) {
                    return new WindowManagerImpl(ctx.getDisplay());
                }});
            ...
        }
    }
    

    这里我们只看 LayoutInflater 的创建,因为 LayoutInflater 也是一个抽象类,所以这里创建的是 PhoneLayoutInflater 对象。

    2、layoutInflater.inflate():创建 View 对象

    // LayoutInflater.java
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
    
    // LayoutInflater.java
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
    
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
    
    // LayoutInflater.java
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
    
            try {
                ...
    
                final String name = parser.getName();
    
                if (TAG_MERGE.equals(name)) {
                    // merge 标签情况下,root 不能为空
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                    + "ViewGroup root and attachToRoot=true");
                    }
                    
                    // 这里其实最终调用的也是 createViewFromTag()
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // 通过标签创建 View
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
                    ...
    
                    // 加载子 View 
                    rInflateChildren(parser, temp, attrs, true);
    
                    ...
                }
    
            } 
            ...
    
            return result;
        }
    }
    
    // LayoutInflater.java
    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) {
            ...
    
            final String name = parser.getName();
            
            // requestFocus
            if (TAG_REQUEST_FOCUS.equals(name)) {
                parseRequestFocus(parser, parent);
            // tag
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            // include
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            // merge
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                // 最终还是调用 createViewFromTag()
                final View view = createViewFromTag(parent, name, context, attrs);
                ...
            }
        }
    
        ...
    }
    
    // LayoutInflater.java
    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }
    
    // LayoutInflater.java
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
        ...
    
        try {
            View view;
            // 尝试用 Factory 和 Factory2 来创建 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);
            }
    
            // 如果 Factory 和 Factory2 没有创建出 View,则最终会调用 createView()
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        // onCreateView() 最终会调用 createView()
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    ...
                }
            }
    
            return view;
        }
        ...
    }
    
    // LayoutInflater.java
    public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        Class<? extends View> clazz = null;
    
        try {
            ...
    
            if (constructor == null) {
                clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
                    
                ...
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
                ...
            }
    
            ...
            
            // 最终调用 constructor.newInstance() 创建 View
            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;
    
        } 
        ...
    }
    

    总结一下创建 View 对象的过程:

    1、传入 resId,根据 resId 获取到 Xml 文件。

    2、解析 Xml 文件,根据标签进行不同的操作(优化等)。

    3、通过 Factory 和 Factory2 尝试创建 View。

    4、Factory 和 Factory2 如果没有成功创建 View,最终通过 mContext.getClassLoader() 反射创建 View。

    由于插件化的原因,这里的 mContext.getClassLoader() 和我们当前的 ClassLoader 不同,所以出现类型转换错误。

    这里比较关键的是最后两步,我们可以通过继承 Factory 和 Factory2 自定义 Factory,把 ClassLoader 传进来,然后通过同一个 ClassLoader 来创建 View,具体过程看第三部分。

    三、解决类型转换方案

    1、封装 LayoutInflater 的 from() 方法,传入 ClassLoader。

    2、继承 Factory 和 Factory2 自定义 Factory,实现 onCreateView() 方法,自己实现 ClassLoader 创建 View 的过程。

    public class PluginLayoutInflater {
        // 1、封装 LayoutInflater 的 from() 方法,传入 ClassLoader
        public static LayoutInflater from(Context context, ClassLoader classLoader) {
            LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            if (layoutInflater == null) {
                throw new AssertionError("LayoutInflater not found.");
            }
    
            LayoutInflater.Factory2 baseFactory2 = layoutInflater.getFactory2();
            LayoutInflater.Factory baseFactory1 = layoutInflater.getFactory();
    
            PluginInflaterFactory factory;
            if (checkBaseFactory2(baseFactory2)) {
                // 2、将 ClassLoader 进一步传入自定义 Factory 中
                factory = new PluginInflaterFactory(baseFactory2, classLoader);
                setFactory2(layoutInflater, factory);
            } else if (checkBaseFactory1(baseFactory1)) {
                // 2、将 ClassLoader 进一步传入自定义 Factory 中
                factory = new PluginInflaterFactory(baseFactory1, classLoader);
                setFactory(layoutInflater, factory);
            }
    
            return layoutInflater;
        }
    
        private static boolean checkBaseFactory1(LayoutInflater.Factory baseFactory) {
            if (baseFactory == null) {
                return true;
            }
            
            if (baseFactory instanceof PluginInflaterFactory) {
                return false;
            }
            
            return true;
        }
    
        private static boolean checkBaseFactory2(LayoutInflater.Factory2 baseFactory2) {
            if (baseFactory2 == null || baseFactory2 instanceof PluginInflaterFactory) {
                return false;
            }
            
            return true;
        }
    
        private static void setFactory(LayoutInflater layoutInflater, PluginInflaterFactory factory) {
            try {
                Field mFactory = LayoutInflater.class.getDeclaredField("mFactory");
                mFactory.setAccessible(true);
                mFactory.set(layoutInflater, factory);
            } catch (NoSuchFieldException e1) {
                e1.printStackTrace();
            } catch (IllegalAccessException e1) {
                e1.printStackTrace();
            }
        }
    
        private static void setFactory2(LayoutInflater layoutInflater, PluginInflaterFactory factory) {
            try {
                Field mFactory = LayoutInflater.class.getDeclaredField("mFactory2");
                mFactory.setAccessible(true);
                mFactory.set(layoutInflater, factory);
            } catch (NoSuchFieldException e1) {
                e1.printStackTrace();
            } catch (IllegalAccessException e1) {
                e1.printStackTrace();
            }
        }
    }
    
    // 3、继承 Factory 和 Factory2 自定义 Factory
    public class PluginInflaterFactory implements LayoutInflater.Factory, LayoutInflater.Factory2 {
        private static final String TAG = "PluginInflaterFactory";
        private LayoutInflater.Factory mBaseFactory;
        private LayoutInflater.Factory2 mBaseFactory2;
        private ClassLoader mClassLoader;
    
        public PluginInflaterFactory(LayoutInflater.Factory base, ClassLoader classLoader) {
            if (null == classLoader) {
                throw new IllegalArgumentException("classLoader is null");
            }
            mBaseFactory = base;
            mClassLoader = classLoader;
        }
    
        public PluginInflaterFactory(LayoutInflater.Factory2 base2, ClassLoader classLoader) {
            if (null == classLoader) {
                throw new IllegalArgumentException("classLoader is null");
            }
            
            mBaseFactory2 = base2;
            mClassLoader = classLoader;
        }
    
        // 4、实现 onCreateView() 方法
        @Override
        public View onCreateView(String s, Context context, AttributeSet attributeSet) {
            if (!s.contains(".")) {
                return null;
            }
            
            View v = getView(s, context, attributeSet);
            if (v != null) {
                return v;
            }
            
            if (mBaseFactory != null && !(mBaseFactory instanceof PluginInflaterFactory)) {
                v = mBaseFactory.onCreateView(s, context, attributeSet);
            }
            
            return v;
        }
    
        // 4、实现 onCreateView() 方法
        @Override
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            if (!name.contains(".")) {
                return null;
            }
            
            View v = getView(name, context, attrs);
            if (v != null) {
                return v;
            }
            
            if (mBaseFactory2 != null && !(mBaseFactory2 instanceof PluginInflaterFactory)) {
                v = mBaseFactory2.onCreateView(parent, name, context, attrs);
            }
            
            return v;
        }
    
        // 5、自己实现 ClassLoader 创建 View 的过程
        private View getView(String name, Context context, AttributeSet attrs) {
            View v = null;
            try {
                Class<?> clazz = mClassLoader.loadClass(name);
                Constructor c = clazz.getConstructor(Context.class, AttributeSet.class);
                v = (View) c.newInstance(context, attrs);
            } catch (ClassNotFoundException e) {
    
            } catch (NoSuchMethodException e) {
    
            } catch (IllegalAccessException e) {
    
            } catch (InstantiationException e) {
    
            } catch (InvocationTargetException e) {
    
            }
            
            return v;
        }
    }
    

    具体使用:

    LayoutInflater pluginInflater = PluginLayoutInflater.from(this, this.getClass().getClassLoader());
    

    参考:「Android10源码分析」为什么复杂布局会产生卡顿?-- LayoutInflater详解

    相关文章

      网友评论

        本文标题:LayoutInflater 源码解析及应用(解决插件化中类型转

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