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

一、类型转换问题

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

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详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容