2012-07-12 12 views
15

Ho un'applicazione Android che deve caricare dinamicamente una classe, un numero indefinito di una classe jar che ha implementato un'interfaccia.Come posso caricare dinamicamente un file jar in un'applicazione Android (4.0.3)

Infatti, guardo una directory ed elenco tutti i file jar che si trovano in questa directory apro il manifest del file jar e trovo la classe associata e li elenco. E dopo, ho istanziato un dexClassLoader per caricare tutti i file jar e per scoprire se le classi che ho trovato nel manisfest implementano la mia interfaccia. Come questo posso avere tutta la classe che ha implementato la mia interfaccia senza conoscerli all'inizio

Per riprendere, ho una lista di jar di classe che implementano la mia interfaccia ma l'elenco è sconosciuto dalla mia applicazione Android e da me. L'elenco delle classi jar può essere modificato ogni volta che lancio la mia applicazione.

Ma quando ho provato a creare il DexClassLoader è fallito. Ho sempre un puntatore nullo

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

Per eseguire il test ho utilizzato l'emulatore. Ho copiato con i miei DDMS i file jar nella directory /data/data/com.example.Myappli/JarFilesDirectory/*.jar

noto che il mio contenuto del file jar il file di dex

Ho letto un sacco di cose su questo. Alcuni problemi di permessi Ho provato ogni cosa ma non ho trovato la soluzione

Qualcuno può aiutarmi per favore !!!

qui il contenuto di un manifesto di un file jar

Manifest-Version: 1.0

Module-Classe: com.example.asktester.AskPeripheral

Ecco il mio codice:

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

risposta

16

Ho trovato la soluzione al mio problema

Per caricare vaso in modo dinamico, classi che implementano un'interfaccia in un'applicazione Android, alcuni posti di lavoro devono essere fatte nel barattolo:

  • Crea la tua manisfest per il vaso e rendere queste informazioni

    Manifest-Version: 1.0 
    Module-Class: com.example.myjar.MyPeripheral 
    
  • Esportare il barattolo con Eclipse e mettere in parametro che si utilizza il proprio manisfest

  • Creare il classes.dex associato al barattolo (questo file è necessario per il Dalvik VM, semplicemente vaso non può essere letto dal Dalvik VM)

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

Attenzione, il nome del file dex DEVE ESSERE classi.Dex

  • aggiungere il file classes.dex nel file jar

    aapt add C:\MyJar.jar C:\classes.dex 
    
  • È anche bisogno di avere il diritto di scrivere nella directory di cache Dalvik

    adb shell chmod 777 /data/dalvik-cache 
    

    Do it ogni volta, riavvia il tuo emulatore

  • put questo file jar nella emulatore ad esempio sulla SDcard

  • Utilizzare un PathClassLoader per caricare il file jar

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

NB: il LogCat in Eclipse fornisce informazioni preziose. Non dimenticate di guardare i suoi messaggi

Di seguito, il codice:

mia interfaccia:

package com.example.StandartPeripheral; 

public interface IPeripheral { 

    public boolean Initialize(); 
    public boolean configure(); 
    public boolean execute(); 
    public String GetName(); 
} 

MyPeripheral che implementa l'interfaccia

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

} 

Come caricare dinamicamente i file 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

sarebbe anche una buona idea di utilizzare il ClassLoader piuttosto che la Dalvik class loader percorso:

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

Dove url è la posizione del file che si sta caricando "da". ApplicationConstants.ref_currentActivity è semplicemente una classe di attività - la mia implementazione è abbastanza complicata a causa del caricamento modulare dinamico - quindi ho dovuto tenerne traccia in questo modo - ma altri potrebbero semplicemente usare "questo" se quella classe è già un'attività.

Il motivo principale per l'utilizzo del caricatore di classi su dalvik è che non richiede che i file vengano scritti nella cache e quindi il permesso chmod 777/data/dalvik-cache non è richiesto - e naturalmente voi inoltre non avrebbe bisogno di passare questo comando da root su un telefono rooted in modo pro-grammatico.

È sempre meglio non avere utenti costretti a eseguire il root dei loro telefoni, semplicemente perché la tua app lo richiede. Soprattutto se la tua app è un "professionale per uso aziendale" di tipo più professionale. Anche le politiche del lavoro contro l'uso dei telefoni radicati sono di solito disponibili.

Se qualcuno ha domande sul caricamento modulare, non esitate a chiedere. La base del mio codice attuale è tutto grazie a Virginie Voirin, insieme alle mie modifiche. Buona fortuna a tutti!

Problemi correlati