18

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

实际上,我查看一个目录并列出该目录中的所有 jar 文件我打开 jar 文件的清单并找到关联的类并列出它们。之后,我实例化了一个 dexClassLoader 来加载所有 jar 文件并查找我在清单中找到的类是否实现了我的接口。像这样,我可以拥有所有实现我的接口的类,而无需一开始就知道它们

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

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

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

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

请注意,我的 jar 文件包含 dex 文件

我读了很多关于这个的东西。一些权限问题我已经尝试了所有方法但没有找到解决方案

有人能帮助我吗 !!!

这里是 jar 文件清单的内容

清单版本:1.0

模块类:com.example.asktester.AskPeripheral

这是我的代码:

公共类模块加载器{

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

2 回答 2

18

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

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

  • 为 jar 创建自己的清单并放置此信息

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

  • 创建jar关联的classes.dex(这个文件是Dalvik VM需要的,只是jar不能被dalvik VM读取)

    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 文件放入模拟器中,例如 SD 卡上

  • 使用 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; 
    }

}
于 2012-07-19T08:43:10.103 回答
8

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

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

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

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

最好不要让用户仅仅因为您的应用程序需要它而被迫根植他们的手机。尤其是如果您的应用程序是更专业的“为公司使用类型”-。通常也有反对使用根电话的工作政策。

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

于 2012-10-30T13:21:24.907 回答