我有一个线程,根据系统的具体实现,为它需要的资源加载不同的类。我的实现是在 Android 上,我有一个类返回我的实现所需的特定类。我似乎能够很好地加载类,但是当我尝试将它分配给主线程中的对象时,它给了我一个 ClassCastException。以下是 fragment :
在我的主线程中,我这样做:
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
是一个接口(interface),JVoiceXmlGrammarProcessor
是我加载并实现该接口(interface)的类。加载代码如下:
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;
}
}
调试时,我会检查加载方法返回的内容,它是一个带有 ID 号的对象。如果我点击它,它会显示 org.jvoicexml.android.JVoiceXmlGrammarProcessor@40565820
,下拉列表将显示 JVoiceXmlGrammarProcessor
应该有的两个私有(private)字段,所以看起来装得很好。有什么想法吗?
最佳答案
我想我明白这里发生了什么,但我必须假设 org.jvoicexml.android
不是你的包,也就是说,你从不同的 apk(正如赏金所暗示的那样)。
考虑到这一点,这是不可能的,而且有充分的理由。
让我们从您自己的应用程序开始 - 您可以从自己的 classes.dex
中获得类型 GrammarProcessor
并进入默认的 ClassLoader(PathClassLoader
当 zygote fork 你的进程时你会得到)。我们称此类型为 GP1
。您自己的应用程序中实现 GrammarProcessor
的任何类实际上在其接口(interface)列表中都有 GP1
。
然后,您实例化一个新的类加载器。如果您查看 source ,您会看到 PathClassLoader
只是 BaseDexClassLoader
的一个薄包装器这又委托(delegate)给一个 DexPathList
, 又委托(delegate)给 DexFile
对象,这些对象又以 native 代码进行加载。呸。
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
加载所有类,从不查看已经加载的类虚拟机。
因此,您最终得到了 GrammarProcessor
的两个不同加载版本。然后,您正在实例化的对象引用新的 GP2
类,而您正试图将其转换为 GP1
。显然不可能。
有解决办法吗?
有一个以前做过,但你不会喜欢它。 Facebook use it在他们的应用程序中加载一堆 dex
文件,它们之间有很强的关系。 (它就在那里,在 LinearAlloc
乱七八糟之前):
we examined the Android source code and used Java reflection to directly modify some of its internal structures
我 90% 确定他们获得了您提供的 PathClassLoader
(getSystemClassLoader()
),获得 DexPathList
并覆盖dexElements
私有(private)字段有一个额外的 Element
和另一个 dex 文件(在你的例子中是 apk)。太黑了,我建议不要这样做。
我刚刚想到,如果您不想以框架看到它们的方式使用新加载的类,您可以从 BaseDexClassLoader
扩展并实现适当的查找- parent-before-try-to-load 行为。我还没有做过,所以我不能保证它会奏效。
我的建议?只需使用远程服务。这就是 Binder
的用途。或者,重新考虑您的 apk 分离。
关于android - 在 Android 中动态加载类时发生 ClassCastException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10792136/