¿Cómo puedo cargar un archivo jar dinámicamente en una aplicación de Android (4.0.3)

Tengo una aplicación de Android que tiene que cargar dinámicamente la clase, un número indefinido de una clase jar que implementó una interfaz.

De hecho, miro un directorio y lista todos los archivos jar que están en este directorio abro el manifiesto del archivo jar y encuentro la clase asociada y los lista. Y después, instancié un dexClassLoader para cargar todos los archivos jar y para encontrar si las clases que he encontrado en el manisfest implementar mi interfaz. Así puedo tener toda la clase que implementó mi interfaz sin conocerlos al principio

Para reanudar, tengo una lista de jar clase que implementar mi interfaz, pero la lista es desconocida por mi aplicación Android y por mí. La lista de la clase jar puede cambiar cada vez que lanzo mi aplicación.

Pero cuando intenté crear el DexClassLoader él falló. Siempre tengo un puntero nulo

DexClassLoader classLoader = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),dexOutputDir.getAbsolutePath(), null, ClassLoader.getSystemClassLoader()); 

Para hacer mi prueba he utilizado el emulador. He copiado con mi DDMS los archivos jar en el directorio /data/data/com.example.Myappli/JarFilesDirectory/*.jar

Observe que mi archivo jar contiene el archivo dex

He leído un montón de cosas sobre esto. Algunos problemas de permisos He intentado todo pero no he encontrado la solución

Puede alguien ayudarme por favor !!!

Aquí el contenido de un manifiesto de un archivo jar

Manifest-Version: 1.0

Módulo-Clase: com.example.asktester.AskPeripheral

Aquí mi código:

Public class ModuleLoader {

 private static List<URL> urls = new ArrayList<URL>(); private static List<String> getModuleClasses(String folder) { List<String> classes = new ArrayList<String>(); //we are listing the jar files File[] files = new File(folder).listFiles(new ModuleFilter()); for(File f : files) { JarFile jarFile = null; try { //we open the jar file jarFile = new JarFile(f); //we recover the manifest Manifest manifest = jarFile.getManifest(); //we recover the class String moduleClassName = manifest.getMainAttributes().getValue("Module-Class"); classes.add(moduleClassName); urls.add(f.toURI().toURL()); } catch (IOException e) { e.printStackTrace(); } finally { if(jarFile != null) { try { jarFile.close(); } catch (IOException e) { e.printStackTrace(); } } } } return classes; } private static class ModuleFilter implements FileFilter { @Override public boolean accept(File file) { return file.isFile() && file.getName().toLowerCase().endsWith(".jar"); } } private static ClassLoader classLoader; public static List<IPeripheral> loadModules(String folder, Context CurrentContext) throws IOException, ClassNotFoundException { List<IPeripheral> modules = new ArrayList<IPeripheral>(); List<String> classes = getModuleClasses(folder); final File dexInternalStoragePath = new File(CurrentContext.getDir("dex", Context.MODE_PRIVATE),"ask.dex"); File dexOutputDir = CurrentContext.getDir("dex", Context.MODE_PRIVATE); final File dexClasses = new File(CurrentContext.getDir("dex", Context.MODE_PRIVATE),"ASK.jar"); DexFile dexFile = DexFile.loadDex(dexClasses.getAbsolutePath(), dexOutputDir.getAbsolutePath(), 0); DexClassLoader classLoader = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),dexOutputDir.getAbsolutePath(), null, ClassLoader.getSystemClassLoader()); //Class<?> myClass = classLoader.loadClass("com.example.asktester.AskPeripheral"); if(IPeripheral.class.isAssignableFrom(myClass )){ Class<IPeripheral> castedClass = (Class<IPeripheral>)myClass ; IPeripheral module = castedClass.newInstance(); modules.add(module); } } catch (ClassNotFoundException e1) { e1.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return modules; } 

Encontré la solución a mi problema.

Para cargar de forma dinámica jar, las clases que implementan una interfaz en una aplicación de Android, algunos trabajos deben hacerse en el frasco:

  • Cree su propio manisfest para el tarro y ponga esta información

      Manifest-Version: 1.0 Module-Class: com.example.myjar.MyPeripheral 
  • Exportar su tarro usando eclipse y poner en parámetro que utiliza su propio manisfest

  • Cree el class.dex asociado al jar (este archivo es necesario para la VM de Dalvik, simplemente el jar no puede ser leído por la VM de dalvik)

     dx --dex --output=C:\classes.dex C:\MyJar.jar 

Tenga cuidado, el nombre del archivo dex DEBE SER classes.dex

  • Agregue el archivo class.dex en el archivo jar

     aapt add C:\MyJar.jar C:\classes.dex 
  • También necesita tener el derecho de escribir en el directorio dalvik cache

      adb shell chmod 777 /data/dalvik-cache 

    Hágalo cada vez, relance su emulador

  • Poner este archivo jar en el emulador por ejemplo en la SDcard

  • Utilice un PathClassLoader para cargar el archivo jar

     dalvik.system.PathClassLoader myClassLoader = new dalvik.system.PathClassLoader("/Sdcard/MyJar.jar", ModuleLoader.class.getClassLoader()); 

Nota: el LogCat en Eclipse le da información valiosa. No te olvides de mirar sus mensajes

A continuación, el código:

Mi interfaz :

 package com.example.StandartPeripheral; public interface IPeripheral { public boolean Initialize(); public boolean configure(); public boolean execute(); public String GetName(); } 

MyPeripheral que implementa la interfaz

 public class MyPeripheral implements IPeripheral { //public static void main(String[] args) {} private final String PeripheralName = "MyPeripheral"; public boolean Initialize() { System.out.println("Initialize "); return true; }; public boolean configure() { System.out.println("Configure !"); return true; }; public boolean execute() { System.out.println("Execute !"); return true; }; public String GetName() { return PeripheralName; } } 

Cómo cargar dinámicamente los archivos jar

 package com.example.ModuleLoader; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.jar.JarFile; import java.util.jar.Manifest; import android.annotation.SuppressLint; import android.content.Context; import com.example.StandartPeripheral.IPeripheral; public class ModuleLoader { private static List<URL> urls = new ArrayList<URL>(); // to retrieve the unknown list of jar files contained in the directory folder // in my case it was in the SDCard folder // link to create a SDCard directory on the Eclipse emulator // http://blog.lecacheur.com/2010/01/14/android-avoir-acces-a-une-carte-memoire-dans-lemulateur/ // retrieve the classes of all this jar files and their URL (location) private static List<String> getModuleClasses(String folder) { List<String> classes = new ArrayList<String>(); //we are listing the jar files File[] files = new File(folder).listFiles(new ModuleFilter()); for(File f : files) { JarFile jarFile = null; try { //we open the jar file jarFile = new JarFile(f); //we recover the manifest Manifest manifest = jarFile.getManifest(); //we recover the class name of our peripherals thanks to ours manifest String moduleClassName = manifest.getMainAttributes().getValue("Module-Class"); classes.add(moduleClassName); urls.add(f.toURI().toURL()); } catch (IOException e) { e.printStackTrace(); } finally { if(jarFile != null) { try { jarFile.close(); } catch (IOException e) { e.printStackTrace(); } } } } return classes; } private static class ModuleFilter implements FileFilter { @Override public boolean accept(File file) { return file.isFile() && file.getName().toLowerCase().endsWith(".jar"); } } //This function loads the jar file into the dalvik system // retrieves the associated classes using its name // and try to know if the loaded classes are implementing our interface public static List<IPeripheral> loadModules(String folder, Context CurrentContext) { List<IPeripheral> modules = new ArrayList<IPeripheral>(); List<String> classes = getModuleClasses(folder); int index = 0; for(String c : classes) { try { dalvik.system.PathClassLoader myClassLoader = new dalvik.system.PathClassLoader(urls.get(index).toString(), ModuleLoader.class.getClassLoader()); Class<?> moduleClass = Class.forName(c, true, myClassLoader); //check and cast to an interface, then use it if(IPeripheral.class.isAssignableFrom(moduleClass)) { @SuppressWarnings("unused") Class<IPeripheral> castedClass = (Class<IPeripheral>)moduleClass; IPeripheral module = (IPeripheral)moduleClass.newInstance(); modules.add(module); } index++; } catch (ClassNotFoundException e1) { e1.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return modules; } } 

También sería una buena idea usar el ClassLoader en lugar del cargador de clases de ruta de Dalvik:

  ClassLoader cl = new DexClassLoader(url, ApplicationConstants.ref_currentActivity.getFilesDir().getAbsolutePath(), null, ModuleList.class.getClassLoader()); 

Donde url es la ubicación del archivo que está cargando "from". ApplicationConstants.ref_currentActivity es simplemente una clase de actividad – mi implementación es bastante complicada debido a la carga dinámica modular – por lo que necesitaba mantener un seguimiento de esta manera -, pero otros probablemente sólo puede utilizar "esto" si esa clase ya es una actividad.

La principal razón para usar el cargador de clases sobre el dalvik es que no requiere que los archivos se escriban en caché y, por lo tanto, el permiso chmod 777 / data / dalvik-cache no se requiere – y por supuesto tampoco Necesidad de pasar este comando de la raíz en un teléfono arraigado pro-gramaticalmente cualquiera.

Siempre es mejor no tener usuarios forzados a root sus teléfonos, simplemente porque su aplicación lo requiere. Especialmente si su aplicación es un más profesional "significa-para-empresa-use-tipo". Las políticas de trabajo contra el uso de teléfonos con raíces suelen estar en su lugar también.

Si alguien tiene alguna pregunta sobre la carga modular – no dude en preguntar. La base de mi código actual es todo gracias a Virginie Voirin, junto con mis propias modificaciones. ¡Buena suerte a todos!

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.