2012-05-29 24 views
8

Mam wątek, który ładuje różne klasy dla potrzebnych zasobów w zależności od konkretnej implementacji systemu. Moja implementacja dotyczy Androida i mam klasę, która zwraca specyficzne klasy wymagane przez moją implementację. Wydaje się, że mogę ładować klasę dobrze, ale kiedy próbuję przypisać ją do obiektu w moim głównym wątku, daje mi to wyjątek ClassCastException. Oto fragmenty:Wyjątek ClassCastException podczas dynamicznego ładowania klasy w systemie Android.

W moim głównym wątku, robię:

try { 
     grammarProcessor = config.loadObject(GrammarProcessor.class); 

który daje mi ten 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 jest interfejs i JVoiceXmlGrammarProcessor jest klasa, że ​​załadunek i implementuje ten interfejs. Kod ładowania jest następująca:

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; 
     } 
} 

podczas debugowania, sprawdzić co powrocie z metody ładowania i jest to obiekt z numerem ID. Jeśli go kliknę, wyświetli się [email protected], a menu pokaże dwa prywatne pola, które powinien mieć JVoiceXmlGrammarProcessor, więc wygląda na dobrze załadowany. Jakieś pomysły?

+0

zmienna grammarProcessor należy do klasy GrammarProcessor.class ?? –

+0

Czy próbowałeś użyć 'ClassLoader.getSystemClassLoader()' zamiast wstawiania nowego 'PathClassLoader'? –

+0

@Marakatu Czekaj, jestem zdezorientowany. Co dokładnie próbujesz zrobić? Zwrócić obiekt klasy według nazwy? Użyj 'Class.forName'. W systemie Android wystarczy użyć nowych programów ładujących klasy, aby załadować klasy z osobnego pliku .dex ([tutaj jest dobry artykuł] (http://android-developers.blogspot.co.uk/2011/07/custom -class-loading-in-dalvik.html)). Proszę wyjaśnij, co próbujesz zrobić. Edycja: Czy ładujesz z tego samego apk lub innego? – Delyan

Odpowiedz

12

Chyba rozumiem, co się tutaj dzieje, ale muszę zrobić założenie, że org.jvoicexml.android jest nie pakiet, to znaczy, jesteś ładowania z innej apk (jak laska wydaje się sugerować).

Mając to na uwadze, jest to niemożliwe i nie bez powodu.

Zacznijmy własnej aplikacji - masz typ GrammarProcessor dostępny z własnego classes.dex i do domyślnego ClassLoader (na PathClassLoader, że pojawi się, gdy widły zygoty procesu). Nazwijmy ten typ GP1. Każda klasa we własnej aplikacji, która implementuje GrammarProcessor, ma na swojej liście GP1.

Następnie tworzysz nowy moduł ładujący klasy. Jeśli spojrzeć na source, zobaczysz, że PathClassLoader jest tylko cienka owijka wokół BaseDexClassLoader co z kolei delegatów do DexPathList, co z kolei delegatów DexFile obiektów, które z kolei zrobić ładowanie w natywnym kodzie. Uff.

Istnieje subtelna część BaseDexClassLoader to przyczyną twoich kłopotów, ale jeśli nie widziałem tego wcześniej, można go przegapić:

this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); 

i nieco dalej:

@Override 
protected Class<?> findClass(String name) throws ClassNotFoundException { 
    Class c = pathList.findClass(name); 
    if (c == null) { 
     ... 
    } 
    return c; 
} 

BaseDexClassLoader nie sprawdza najpierw z rodzicem!

.. i to w skrócie jest twój problem.

Dokładniej, wewnątrz nich DexPathList i DexFile ładuje wszystkie klasy z innego dex i nigdy nie zagląda do klas już załadowanych w maszynie wirtualnej.

W ten sposób otrzymujesz dwie różne załadowane wersje GrammarProcessor.Następnie obiekt, który tworzysz, odnosi się do nowej klasy GP2, podczas gdy próbujesz przesłać ją do GP1. Oczywiście niemożliwe.

Czy istnieje rozwiązanie tego problemu?

Jest taki, który został zrobiony wcześniej, ale nie spodoba ci się. Facebook use it w aplikacji, aby załadować kilka plików dex z silnymi relacjami między nimi. (To tam, przed wszystkimi ingerować LinearAlloc):

we examined the Android source code and used Java reflection to directly modify some of its internal structures

jestem 90% pewien, że dostać PathClassLoader że dostaniemy (getSystemClassLoader()), uzyskać DexPathList i zastąpić prywatne pole dexElements mieć dodatkowy Element z innym plikiem dex (apk w twoim przypadku). Hacky jak diabli i odradzam to.

Po prostu przyszło mi do głowy, że jeśli nie chcesz używać nowo wczytanych klas w taki sposób, jaki je widzi, możesz przedłużyć od BaseDexClassLoader i zaimplementować właściwy wygląd w rodzicach przed próbą. zachowanie obciążenia. Nie zrobiłem tego, więc nie mogę obiecać, że to zadziała.

Moja rada? Po prostu korzystaj z usług zdalnych. To właśnie ma oznaczać Binder. Alternatywnie, przemyśl swoją separację apk.

+0

To było bardzo pouczające i dobre wnętrze do wewnętrznych działań ClassLoader, a sugestia wydaje się realna (nie hak FB). Moja sytuacja, a być może nieco związana z programami operacyjnymi, polega również na tym, że mam aplikację, którą chcę "sprzedać" rozszerzeniom, ale rozszerzenia muszą wykraczać poza to, co AIDL jest w stanie zrobić, przede wszystkim pracując z obiektami, które nie są paczkami. Tak więc chciałbym przynieść duże części aplikacji (takie jak w pełni funkcjonalne fragmenty, które w większości pracowałem z wyjątkiem interakcji z aktualnie ładowanymi klasami). – JRomero

+0

Problem jest teraz: 'Spowodowany przez: java.lang.IllegalAccessError: Ref klasy w wstępnie zweryfikowanej klasie rozwiązany na nieoczekiwaną implementację' – JRomero

+1

Ah, 'dexopt', bałem się, że zrobi coś dziwnego. Ponieważ zasadniczo nadpisujesz klasę w załadowanym dokumencie, wszystkie odwołania, które zostały wstępnie sprawdzone przez kompilator, stały się nieważne. Zajęło by to komuś bardziej kompetentnemu ode mnie, aby nawet zacząć się nim ominąć (chociaż obawiam się, że może to być całkowicie niemożliwe). Moją pierwszą propozycją jest wypróbowanie hakowania FB i dodanie innego deka, upewniając się, że tylko ładuje się obcy interfejs, a nie własny. – Delyan

Powiązane problemy