如何在Android应用程序中动态加载jar文件(4.0.3)

时间:2012-07-12 14:05:41

标签: android

我有一个Android应用程序,它必须加载动态类,一个未定义数量的实现接口的jar类。

实际上,我查看一个目录并列出该目录中的所有jar文件 我打开jar文件的清单,找到关联的类并列出它们。 之后,我安装了一个dexClassLoader来加载所有jar文件,并查找我在manisfest中找到的类是否实现了我的界面。 像这样我可以拥有实现我的界面的所有类,而不知道它们在前面的

要恢复,我有一个实现我的界面的类jar列表,​​但我的android应用程序和我都知道该列表。每次启动我的应用程序时,jar类的列表都会更改。

但是当我尝试创建DexClassLoader时,它失败了。我总是一个空指针

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

为了进行测试,我使用了模拟器。我已将我的DDMS jar文件复制到目录中 /data/data/com.example.Myappli/JarFilesDirectory / *。罐

请注意我的jar文件内容是dex文件

我读了很多关于此事的内容。一些权限问题 我已经尝试了所有的东西,但没有找到解决方案

请有人帮帮我!!!

这里是jar文件清单的内容

清单 - 版本:1.0

Module-Class:com.example.asktester.AskPeripheral

这是我的代码:

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

2 个答案:

答案 0 :(得分:16)

我找到了解决问题的方法。

要动态加载jar,在android应用程序中实现接口的类,需要在jar中完成一些工作:

  • 为jar创建自己的manisfest并提供此信息

     Manifest-Version: 1.0
     Module-Class: com.example.myjar.MyPeripheral
    
  • 使用eclipse导出你的jar并输入它使用自己的manisfest的参数

  • 创建与jar相关联的classes.dex (这个文件是Dalvik VM所需要的,dalvik VM无法读取jar文件)

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

小心,dex文件的名称必须 classes.dex

  • 在jar文件中添加文件classes.dex

    aapt add C:\MyJar.jar C:\classes.dex
    
  • 您还需要有权写入dalvik缓存目录

       adb shell chmod 777 /data/dalvik-cache
    

    每次都重新启动模拟器

  • 将此jar文件放入模拟器中,例如SDcard

  • 使用PathClassLoader加载jar文件

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

注意:Eclipse中的LogCat为您提供了宝贵的信息。不要忘记查看其消息

下面是代码:

我的界面

package com.example.StandartPeripheral; 

public interface IPeripheral {

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

实现界面的MyPeripheral

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

}

如何动态加载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; 
    }

}

答案 1 :(得分:7)

使用ClassLoader而不是Dalvik路径类加载器也是一个好主意:

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

其中url是您正在加载“from”的文件的位置。 ApplicationConstants.ref_currentActivity只是一个活动类 - 由于动态模块化加载,我的实现相当复杂 - 所以我需要以这种方式跟踪它 - 但是如果该类已经是一个活动,其他人可能只是使用“this”。 p>

在dalvik上使用类加载器的主要原因是它不需要将文件写入缓存,因此权限chmod 777 / data / dalvik-cache是​​不需要的 - 当然你也是不需要以编程方式从root用户的root上传递此命令。

最好不要让用户强行拔出手机,只是因为你的应用需要它。特别是如果你的应用程序是一个更专业的“意味着公司使用类型” - 。工作策略也反对使用有根电话。

如果有人对模块化装载有任何疑问 - 请随时询问。 我当前代码的基础都归功于Virginie Voirin以及我自己的修改。祝你好运!