ClassCastException while dynamically loading a class in Android
android, classcastexception, classloader, dynamic
Solution
I think I understand what's happening here but I have to make an assumption that `org.jvoicexml.android` is not your package, i.e., you're loading from a different apk (as the bounty seems to suggest).
With that in mind, this is impossible and for a good reason.
Let's start with your own app - you have the type `GrammarProcessor` available from your own `classes.dex` and into your default ClassLoader (the `PathClassLoader` that you get when the zygote forks your process). Let's call this type `GP1`. Any class in your own application that implements `GrammarProcessor` actually has `GP1` in their interface list.
Then, you instantiate a new classloader. If you look at the source, you'll see that `PathClassLoader` is just a thin wrapper around `BaseDexClassLoader` which in turn delegates to a `DexPathList`, which in turn delegates to `DexFile` objects which in turn do the loading in native code. Phew.
There's a subtle part of `BaseDexClassLoader` that's the cause of your troubles but if you haven't seen it before, you might miss it:
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
and a bit further down:
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class c = pathList.findClass(name);
if (c == null) {
...
}
return c;
}
BaseDexClassLoader does not check with its parent first!
.. and that in short is your problem.
More precisely, the `DexPathList` and `DexFile` inside it load all the classes from the other `dex` and never look into the classes already loaded in the VM.
So, you end up with two different loaded versions of `GrammarProcessor`. Then, the object you're instantiating is referring to the new `GP2` class, while you're trying to cast it to `GP1`. Obviously impossible.
Is there a solution to this?
There's one that's been done before, but you won't like it. Facebook use it in their app to load a bunch of `dex` files with strong relationships between them. (It's there, before all the messing about with `LinearAlloc`):
we examined the Android source code and used Java reflection to directly modify some of its internal structures
I'm 90% sure they get the `PathClassLoader` that you're given (`getSystemClassLoader()`), get the `DexPathList` and override the `dexElements` private field to have an extra `Element` with the other dex file (apk in your case). Hacky as hell and I would advise against it.
It just occurred to me that if you don't want to use the newly loaded classes in a way that the framework sees them, you could extend from `BaseDexClassLoader` and implement the proper look-in-parent-before-trying-to-load behaviour. I haven't done it, so I can't promise it will work.
My advice? Just use remote services. This is what `Binder` is meant for. Alternatively, rethink your apk separation.
Problem
I have a thread that loads different classes for the resources it needs depending on the specific implementation of the system. My implementation is on Android and I have a class that returns the specific classes needed by my implementation. I seem to be able to load the class fine, but when I try to assign it to the object in my main thread, it gives me a ClassCastException. Here are the snippets: In my main thread, I do: ``` try { grammarProcessor = config.loadObject(GrammarProcessor.class); ``` which gives me this stacktrace: ``` 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` is an interface and `JVoiceXmlGrammarProcessor` is the class that I load and implements that interface. The loading code is as follows: ``` 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; } } ``` When debugging, I check what's returning from the load method and it is an object with an id number. If I click on it, it'll say `org.jvoicexml.android.JVoiceXmlGrammarProcessor@40565820`, and the dropdown will show the two private fields that a `JVoiceXmlGrammarProcessor` should have, so it looks like it's well loaded. Any ideas?