我有一个线程,根据系统的具体实现,为所需的资源加载不同的类。我的实现在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
应该拥有的两个私有字段,所以它看起来好像已经加载了。有什么想法吗?
答案 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不会先检查其父级!
..简而言之就是你的问题。
更准确地说,它内部的DexPathList
和DexFile
加载了来自另一个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分离。