在Android中动态加载类时ClassCastException

时间:2012-05-29 01:29:08

标签: android dynamic classloader classcastexception

我有一个线程,根据系统的具体实现,为所需的资源加载不同的类。我的实现在Android上,我有一个类,它返回我的实现所需的特定类。我似乎能够很好地加载类,但是当我尝试将它分配给主线程中的对象时,它给了我一个ClassCastException。以下是片段:

在我的主要帖子中,我这样做:

    try {
        grammarProcessor = config.loadObject(GrammarProcessor.class);

给了我这个堆栈跟踪:

    E/AndroidRuntime(6682): FATAL EXCEPTION: JVoiceXmlMain
    E/AndroidRuntime(6682): java.lang.ClassCastException: org.jvoicexml.android.JVoiceXmlGrammarProcessor
    E/AndroidRuntime(6682):     at org.jvoicexml.JVoiceXmlMain.run(JVoiceXmlMain.java:321)

GrammarProcessor是一个接口,JVoiceXmlGrammarProcessor是我加载并实现该接口的类。加载代码如下:

else if(baseClass == GrammarProcessor.class){
        String packageName = "org.jvoicexml.android";
        String className = "org.jvoicexml.android.JVoiceXmlGrammarProcessor";           
        String apkName = null;
        Class<?> handler = null;
        T b = null;

        try {
            PackageManager manager = callManagerContext.getPackageManager();
            ApplicationInfo info= manager.getApplicationInfo(packageName, 0);
            apkName= info.sourceDir;
        } catch (NameNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
            return null;
        }
        PathClassLoader myClassLoader =
            new dalvik.system.PathClassLoader(
                    apkName,
                    ClassLoader.getSystemClassLoader());
        try {
            handler = Class.forName(className, true, myClassLoader);
            return (T) handler.newInstance();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }           
        catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }
}

调试时,我会检查从load方法返回的内容,它是一个带有id号的对象。如果我点击它,它会说org.jvoicexml.android.JVoiceXmlGrammarProcessor@40565820,下拉列表会显示JVoiceXmlGrammarProcessor应该拥有的两个私有字段,所以它看起来好像已经加载了。有什么想法吗?

1 个答案:

答案 0 :(得分:12)

我想我明白这里发生了什么,但我必须假设org.jvoicexml.android 你的包,即你从不同的apk加载(因为赏金似乎建议)。

考虑到这一点,这是不可能的,也是有充分理由的。

让我们从您自己的应用开始 - 您可以从您自己的GrammarProcessor获取类型classes.dex,并将其添加到您的默认ClassLoader(当您使用zygote进行处理时获得的PathClassLoader) 。我们称这种类型为GP1。您自己的应用程序中实现GrammarProcessor的任何类实际上在其接口列表中都有GP1

然后,您实例化一个新的类加载器。如果您查看source,您会发现PathClassLoader只是BaseDexClassLoader的一个薄包装,后者又委托给DexPathList,后者又委托给DexFile {3}}对象反过来在本机代码中进行加载。呼。

BaseDexClassLoader中有一个微妙的部分是你的麻烦的原因,但如果你以前没见过它,你可能会错过它:

this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

再向下一点:

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    Class c = pathList.findClass(name);
    if (c == null) {
        ...
    }
    return c;
}

BaseDexClassLoader不会先检查其父级!

..简而言之就是你的问题。

更准确地说,它内部的DexPathListDexFile加载了来自另一个dex的所有类,并且从不查看已经加载到VM中的类。

因此,您最终会得到两个不同的GrammarProcessor加载版本。然后,您要实例化的对象是指新的GP2类,而您尝试将其强制转换为GP1。显然不可能。

有解决方法吗?

之前有过一次,但你不会喜欢它。 Facebook use it在他们的应用中加载了一堆dex个文件,它们之间有很强的关系。 (在LinearAlloc)所有混乱之前,它就在那里:

  

我们检查了Android源代码,并使用Java反射直接修改了一些内部结构

我90%确定他们获得了PathClassLoader您获得的getSystemClassLoader(),获得DexPathList并覆盖dexElements私有字段以获得额外Element与其他dex文件(在您的情况下为apk)。 Hacky就像地狱一样,我会反对它。

我刚刚想到,如果你不想以框架看到它们的方式使用新加载的类,你可以从BaseDexClassLoader扩展并实现正确的look-in-parent-before - 加载 - 行为。我没有这样做,所以我不能保证它会起作用。

我的建议?只需使用远程服务。这就是Binder的意思。或者,重新考虑你的apk分离。