星期一, 九月 22, 2014

如何动态载入外部储存卡上的dex/jar包?

java的概念定制ClassLoader非常强大,Android提供了两个classloader来完成相关的任务.
PathClassLoader: 只能读取已经保存在系统里的jar/dex包
DexClassLoader:可以灵活读取任何权限允许的jar包。
我们设想可以读取保存在外部存储卡上的jar,更加符合实际的逻辑需要:
设计:
将动态jar包保存在/sdcard/temp/testjar.jar 文件里,在app里调用载入该包,并执行特定的方法:

1.Android Activtiy 准备动态载入相关代码:
将该方法加入到app某个菜单项目
onOptionsItemSelected方法里来调用,该app package为home.sunose假设:

public String classLoadFromExtern(Activity acivity)
{
 String result="";
 String sdcard=Environment.getExternalStorageDirectory().getAbsolutePath();
 String jarPath = sdcard
             + "/temp/testjar.jar";
String tmppath=acivity.getDir( "temp", 0).getAbsolutePath();
//注意tmppath是用来将testjar.jar展开的目录,这个目录必须属于当前app的用户id,所以必须使用
//context上下文相关的getDir得到该目录,避免没有权限的错误:
//DexClassLoader is not owned by the current user 这个异常.
//这是private DexFile(String sourceName, String outputName, int flags) throws IOException 
//的判断Libcore.os.getuid() != Libcore.os.stat(parent).st_uid

Log.d (TAG ,tmppath );
 DexClassLoader myClassLoader = new DexClassLoader (jarPath ,tmppath ,null, ClassLoader.getSystemClassLoader());
    try
     {
         Class<?>  clazz = myClassLoader .loadClass("com.test.First");
//         AIObject aio=(AIObject)clazz.newInstance();
         Method method = clazz.getDeclaredMethod("execute" ,String.class);
         method.setAccessible(true);
          result=(String)method .invoke(clazz.newInstance(), "test");
     }
     catch(Exception ex)
     {
         ex.printStackTrace();
     }
 return result;
}

2.准备桌面com.test.First.java类:
package com.test;
public class First
{
public String execute(String term)
{
String result=new java.util.Date()+" term is "+term;  
System.out.println(result);
return result;
}
}
5.mkdir com;mkdir com\test //建立标准java package目录结构,准备
6.cd c:\temp\com\test ,建立First.java,内容为步骤2内容

7.javac  com\test\First.java -d classes //编译java文件,并将编译后的class输出到c:\temp\classes目录

//准备dex打包编译的class
8.D:\Android\android-sdk-windows\build-tools\20.0.0\dx.bat  --dex --output c:\temp\testjar.jar  c:\temp\classes
9.adb push testjar.jar /sdcard/temp/  //推送到android上
10.在app中调用步骤1的方法后,adb logcat 可以看到
后台Logcat的输出:
D/dalvikvm(31581): DexOpt: --- BEGIN 'testjar.jar' (bootstrap=0) ---
D/dalvikvm(31608): DexOpt: load 5ms, verify+opt 1ms, 93356 bytes
D/dalvikvm(31581): DexOpt: --- END 'testjar.jar' (success) ---
D/dalvikvm(31581): DEX prep '/storage/emulated/0/temp/testjar.jar': unzip in 0ms, rewrite 98ms

11.也可以看到testjar.jar 实际展开后的目录:
 adb shell
ls /data/data/home.sunose/app_temp