2012-05-29 15 views
8

Tengo un hilo que carga diferentes clases para los recursos que necesita dependiendo de la implementación específica del sistema. Mi implementación está en Android y tengo una clase que devuelve las clases específicas que necesita mi implementación. Parece que puedo cargar bien la clase, pero cuando trato de asignarlo al objeto en mi hilo principal, me da una ClassCastException. Aquí están los fragmentos:ClassCastException al cargar dinámicamente una clase en Android

En mi hilo principal, que hago:

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

que me da este 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 es una interfaz y JVoiceXmlGrammarProcessor es la clase que se carga e implementa esa interfaz. El código de carga es la siguiente:

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

Cuando la depuración, puedo comprobar lo que está de regresar del método de carga y es un objeto con un número de identificación. Si hago clic en él, dirá [email protected], y el menú desplegable mostrará los dos campos privados que debería tener un JVoiceXmlGrammarProcessor, por lo que parece que está bien cargado. ¿Algunas ideas?

+0

grammarProcessor variable es de la clase GrammarProcessor.class ?? –

+0

¿Ha intentado utilizar 'ClassLoader.getSystemClassLoader()' en lugar de crear un nuevo 'PathClassLoader'? –

+0

@Marakatu Espera, estoy súper confundido. ¿Qué estás tratando de hacer exactamente? ¿Devuelve un objeto de clase por su nombre? Use 'Class.forName'. En Android, solo necesita utilizar nuevos cargadores de clases si desea cargar clases desde un archivo .dex separado ([aquí hay un buen artículo]) (http://android-developers.blogspot.co.uk/2011/07/custom -class-loading-in-dalvik.html)). Por favor explique lo que está tratando de hacer. Editar: ¿Estás cargando desde la misma aplicación o una diferente? – Delyan

Respuesta

12

creo que entiendo lo que está pasando aquí, pero tengo que hacer una suposición de que es org.jvoicexml.androidno su paquete, es decir, va a cargar desde un apk diferente (como la generosidad parece sugerir).

Teniendo esto en cuenta, esto es imposible y por una buena razón.

Vamos a empezar con su propia aplicación - usted tiene el tipo GrammarProcessor disponible en su propia classes.dex y en su defecto cargador de clases (la PathClassLoader que se obtiene cuando las horquillas cigoto su proceso). Llamemos a este tipo GP1. Cualquier clase en su propia aplicación que implementa GrammarProcessor en realidad tiene GP1 en su lista de interfaz.

Luego, crea una instancia de un nuevo cargador de clases. Si nos fijamos en el source, verá que PathClassLoader es solo una envoltura delgada alrededor de BaseDexClassLoader que a su vez delega en un DexPathList, que a su vez delega en DexFile objetos que a su vez realizan la carga en código nativo. Uf.

Hay una parte sutil de BaseDexClassLoader que es la causa de sus problemas, pero si usted no ha visto antes, es posible que pierda:

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

y un poco más abajo:

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

¡BaseDexClassLoader no comprueba primero con su padre!

.. y que en breve es su problema.

Más precisamente, el DexPathList y el DexFile dentro de él cargan todas las clases del otro dex y nunca miran en las clases ya cargadas en el VM.

Por lo tanto, terminas con dos versiones cargadas diferentes de GrammarProcessor.Entonces, el objeto que estás instanciando se refiere a la nueva clase GP2, mientras tratas de lanzarlo al GP1. Obviamente imposible.

¿Hay alguna solución a esto?

Hay uno que se ha hecho antes, pero no te gustará. Facebook use it en su aplicación para cargar un montón de archivos dex con fuertes relaciones entre ellos. (Está allí, delante de toda la perder el tiempo con LinearAlloc):

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

estoy 90% seguro de que obtener el PathClassLoader que te dan (getSystemClassLoader()), obtener el DexPathList y anular el campo privado dexElements tener un extra Element con el otro archivo dex (apk en su caso). Hacky como el infierno y aconsejaría no hacerlo.

Se me acaba de ocurrir que si no quiere usar las clases recién cargadas de una manera que el framework las vea, puede extender desde BaseDexClassLoader e implementar el apropiado look-in-parent-before-trying- comportamiento de carga. No lo he hecho, así que no puedo prometer que funcionará.

Mi consejo? Solo use servicios remotos. Esto es para lo que está destinado el Binder. Alternativamente, reconsidera tu separación apk.

+0

Eso fue extremadamente informativo y bueno dentro del funcionamiento interno del ClassLoader y la sugerencia parece viable (no el truco FB). Mi situación, y tal vez algo relacionada con los PO también es que tengo una aplicación que quiero "vender" extensiones, pero las extensiones deben ir más allá de lo que AIDL es capaz de hacer, trabajando principalmente con objetos no Parcelable. Entonces, lo que me gustaría hacer es traer grandes partes de la aplicación (como fragmentos de funcionamiento completo, que en su mayoría he conseguido trabajar con la excepción de interactuar con las clases cargadas actuales). – JRomero

+0

El problema ahora es: 'Causado por: java.lang.IllegalAccessError: Ref clase en clase previamente verificada resuelta a implementación inesperada' – JRomero

+1

Ah,' dexopt', temía que fuera a hacer algo raro. Como esencialmente está sobrescribiendo una clase en el dex cargado, todas las referencias que han sido verificadas previamente por el compilador se han vuelto inválidas. Sería necesario que alguien más conocedor que yo, incluso para empezar a moverse por ahí (aunque me temo que podría ser completamente imposible). Mi primera sugerencia es probar el truco de FB y anteponer el otro dex, asegurándome de cargar la interfaz externa, no la tuya. – Delyan

Cuestiones relacionadas