8

我有一个线程,它根据系统的具体实现为它需要的资源加载不同的类。我的实现是在 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,下拉菜单会显示 aJVoiceXmlGrammarProcessor应该有的两个私有字段,所以看起来它已经加载好了。有任何想法吗?

4

1 回答 1

12

我想我明白这里发生了什么,但我必须做出一个不是org.jvoicexml.android你的包的假设,即你是从不同的 apk 加载的(正如赏金所暗示的那样)。

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

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

然后,您实例化一个新的类加载器。如果您查看source,您会发现它PathClassLoader只是一个薄包装器,BaseDexClassLoader它依次委托给 a DexPathList,而后者又委托给DexFile对象,这些对象又在本机代码中进行加载。呸。

其中有一个微妙的部分是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 不会先检查其父级!

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

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

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

有针对这个的解决方法吗?

有一个以前做过,但你不会喜欢它。Facebook在他们的应用程序中使用它来加载一堆dex文件之间有很强的关系。(它就在那里,在所有乱七八糟的事情之前LinearAlloc):

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

我有 90% 的把握他们得到了PathClassLoader你得到的(getSystemClassLoader()),得到DexPathList并覆盖了私有字段,以便在另一个 dex 文件(在你的情况下是 apk)中dexElements有一个额外的。Element像地狱一样哈克,我建议不要这样做。

我突然想到,如果您不想以框架看到它们的方式使用新加载的类,您可以扩展BaseDexClassLoader并实现正确的查看父级,然后再尝试加载行为。我还没做过,所以我不能保证它会起作用。

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

于 2013-07-27T23:38:36.007 回答