2012-07-12 19 views
15

Tengo una aplicación de Android que tiene que cargar dinámicamente de clase, un número indefinido de una clase jar que implementó una interfaz.¿Cómo puedo cargar un archivo jar de forma dinámica en una aplicación de Android (4.0.3)

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

Para reanudar, tengo una lista de clase jar que implementa 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 lance mi aplicación.

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

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

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

en cuenta que el contenido de mi archivo JAR del archivo Dex

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

¿Alguien me puede ayudar por favor?

aquí el contenido de un manifiesto de un archivo jar

Manifiesto-Version: 1.0

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

Aquí mi código:

clase pública 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; 
} 

Respuesta

16

Encontré la solución a mi problema

Para cargar dinámicamente frasco, las clases que implementan una interfaz en una aplicación para Android, algunos trabajos se deben hacer en el frasco:

  • Crear su propio manisfest para el frasco y poner esta información

    Manifest-Version: 1.0 
    Module-Class: com.example.myjar.MyPeripheral 
    
  • exportación de su frasco usando Eclipse y poner en el parámetro que utiliza su propio manisfest

  • Crear la classes.dex asociado a la jarra (este archivo es necesario por el Dalvik VM, simplemente frasco no puede ser leído por la máquina virtual Dalvik)

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

Atención, el nombre del archivo Dex DEBE SER clases.dex

  • Añadir la classes.dex archivo en el archivo jar

    aapt add C:\MyJar.jar C:\classes.dex 
    
  • Es necesario también tener el derecho a escribir en el directorio de caché Dalvik

    adb shell chmod 777 /data/dalvik-cache 
    

    Hacerlo cada vez, relanzar su emulador

  • poner este archivo JAR en el emulador, por ejemplo, en la tarjeta SD

  • Utilice un PathClassLoader para cargar el archivo jar

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

NB: la LogCat en Eclipse le da información valiosa. No se olvide de mirar en sus mensajes

A continuación, el código:

Mi interfaz de:

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

} 
6

también sería una buena idea utilizar el cargador de clases en lugar del cargador de clases ruta Dalvik:

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

donde url es la ubicación del archivo que va a cargar "de". ApplicationConstants.ref_currentActivity es simplemente una clase de actividad; mi implementación es bastante complicada debido a la carga modular dinámica, así que necesitaba hacer un seguimiento de esta manera, pero otros probablemente solo puedan usar "this" si esa clase ya es una actividad.

La razón principal para usar el cargador de clases sobre el dalvik one es que no requiere que los archivos se escriban en caché, y por lo tanto el permiso chmod 777/data/dalvik-cache no es necesario, y por supuesto tampoco tendría que pasar este comando desde la raíz en un teléfono rooteado pro-gramáticamente tampoco.

Siempre es mejor no obligar a los usuarios a rootear sus teléfonos, simplemente porque su aplicación lo requiera. Especialmente si su aplicación es una más profesional "diseñada para el uso de la compañía". Las políticas de trabajo contra el uso de teléfonos rooteados generalmente también están vigentes.

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!

Cuestiones relacionadas