ClassCastException при динамической загрузке класса в Android

У меня есть поток, который загружает разные классы для требуемых ресурсов в зависимости от конкретной реализации системы. Моя реализация на 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; } } 

При отладке я проверяю, что возвращается из метода загрузки, и это объект с номером id. Если я org.jvoicexml.android.JVoiceXmlGrammarProcessor@40565820 на него, он скажет org.jvoicexml.android.JVoiceXmlGrammarProcessor@40565820 , а в раскрывающемся org.jvoicexml.android.JVoiceXmlGrammarProcessor@40565820 будут показаны два частных поля, которые должен иметь JVoiceXmlGrammarProcessor , поэтому он выглядит так, что он хорошо загружен. Есть идеи?

Solutions Collecting From Web of "ClassCastException при динамической загрузке класса в Android"

Я думаю, что я понимаю, что здесь происходит, но я должен сделать предположение, что org.jvoicexml.android не является вашим пакетом, т. org.jvoicexml.android Вы загружаетесь из другого apk (как кажется, как подсказывает баунти).

Имея это в виду, это невозможно и по уважительной причине.

Начнем с вашего собственного приложения – у вас есть тип GrammarProcessor доступный из ваших собственных classes.dex и в ваш класс ClassLoader по умолчанию ( PathClassLoader который вы получаете, когда PathClassLoader вилки вашего процесса). Назовем этот тип GP1 . Любой класс в вашем собственном приложении, который реализует GrammarProcessor фактически имеет GP1 в своем списке интерфейсов.

Затем вы создаете новый загрузчик классов. Если вы посмотрите на источник , вы увидите, что PathClassLoader – это всего лишь тонкая оболочка BaseDexClassLoader которая, в свою очередь, делегирует 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 и никогда не заглядывают в классы, уже загруженные в виртуальную DexFile .

Таким образом, вы GrammarProcessor две разные загруженные версии GrammarProcessor . Затем объект, который вы создаете, ссылается на новый класс GP2 , в то время как вы пытаетесь передать его в GP1 . Очевидно, невозможно.

Есть ли этому решение?

Есть один, который был сделан раньше, но вам это не понравится. Facebook использует его в своем приложении, чтобы загрузить кучу файлов dex с прочными отношениями между ними. (Это там, прежде чем все возиться с LinearAlloc ):

Мы изучили исходный код Android и использовали отражение Java для непосредственного изменения некоторых его внутренних структур

Я на 90% уверен, что они получают PathClassLoader которые вам даны ( getSystemClassLoader() ), получают DexPathList и переопределяют частное поле dexElements чтобы иметь дополнительный Element с другим файлом dex (apk в вашем случае). Хакки, черт возьми, и я бы посоветовал это сделать.

Мне просто пришло в голову, что если вы не хотите использовать вновь загруженные классы таким образом, что среда видит их, вы можете BaseDexClassLoader от BaseDexClassLoader и внедрить правильное BaseDexClassLoader родительское поведение перед попыткой загрузки» , Я этого не сделал, поэтому я не могу обещать, что это сработает.

Мой совет? Просто используйте удаленные службы. Это то, для чего предназначен Binder . В качестве альтернативы, переосмыслите ваше разделение apk.