Android DexClassLoader/PathClassLoader 动态加载jar/APK

可以使用Android ClassLoader完成DEX的动态加载,DEX文件可以附属在assets或raw目录也可以运行时从网络下载。

1.当前动态加载常用的class loader

  (1)DexClassLoader:这个可以加载jar/apk/dex,也可以从SD卡中加载,也是本文的重点。
  (2)PathClassLoader:只能加载dex文件和已经安装到Android系统中的apk文件。

这两者的区别在于DexClassLoader需要提供一个可写的outpath路径,用来释放.apk包或者.jar包中的dex文件。换个说法来说,就是PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。另外,PathClassLoader在加载类时调用的是DexFile的loadClassBinaryName,而DexClassLoader调用的是loadClass。因此,在使用PathClassLoader时类全名需要用”/”替换”.”。

2.首先将打包好的 jar 转为dex 格式。在Android中无法像Java中那样方便动态加载jar, Android的虚拟机(Dalvik VM)是不认识Java打出jar的byte code,需要通过dx工具来优化转换成Dalvik byte code才行,执行命令:

 $dx --dex --output=testDex.jar test.jar

(注意:打包test.jar时请不要把接口文件打进来. 否则加载时会报错:java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation)

3. 动态加载jar 包示例代码:

首先编写接口和实现,生成jar包(打包jar的时候不要把接口ITest.class打包进来,否则加载时会有冲突报错。之所以定义接口ITest是为了说明下面动态加载方法二:类型强转,前提是知道Test.jar中Class所实现的接口类ITest)

package com.test.mytest;

public interface ITest {
    public String test();
}
package com.test.mytest;

public class Test implements ITest {
    @Override
    public String test() {
        return "Hello";
    }
}
动态加载jar包

    public void loadDexFile(Context context) {
        copyAssetsToFiles(context);
        String filePath = context.getFilesDir().getPath() + "/testDex.jar";
        
        //动态加载的也可以是APK文件,没有任何区别,APK 文件中的class.dex文件会被DexClassLoader加载。
        //但是,APK中的Activity类,由于是使用反射,无法取得Context,与普通的类毫无区别,没有生命周期。
        //String filePath = context.getFilesDir().getPath() + "/Test.apk";
        
        DexClassLoader classLoader = new DexClassLoader(filePath, context.getFilesDir().getPath(), null, context.getClassLoader());
        try {
            //方法一:反射调用
            Class myClass = classLoader.loadClass("com.test.mytest.Test");
            Constructor myConstructor = myClass.getConstructor(Context.class);
            Method method = myClass.getMethod("test", null);
            String data = (String) method.invoke(myConstructor.newInstance(this), null);
            //method.setAccessible(true);访问private函数
            System.out.println(data);
            
            //方法二:类型强转,前提是知道Test.jar中Class所实现的接口类<span style="font-family: Arial, Helvetica, sans-serif;">ITest</span>
            Class myClass2 = classLoader.loadClass("com.test.mytest.Test");
            Constructor myConstructor2 = myClass2.getConstructor(Context.class);
            ITest obj = (ITest) myConstructor2.newInstance(this);
            String data2 = obj.test();
            System.out.println(data2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Copy the APK "assets/" to "/data/data/package-name/file/"
     */
    public static void copyAssetsToFiles(Context context) {
        String fileDir = context.getFilesDir().getPath() + "/";
        File workingDir = new File(fileDir);
        if (!workingDir.exists()) {
            workingDir.mkdirs();
        }

        File outFile_bin = new File(workingDir, "test.jar");
        if (!outFile_bin.exists()) {
            copyFile(context, "test.jar", outFile_bin);
            outFile_bin.setExecutable(true, false); 
        }
    }

    /**
     * Copy assets file to the data folder
     */
    private static void copyFile(Context context, String sourceFileName, File targetFile) {
        InputStream in = null;
        FileOutputStream out = null;
        try {
            in = context.getAssets().open(sourceFileName);
            out = new FileOutputStream(targetFile);
            byte[] temp = new byte[1024];
            int count = 0;
            while ((count = in.read(temp)) > 0) {
                out.write(temp, 0, count);
            }

            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        } catch (Exception e) {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }

            if (out != null) {
                try {
                    out.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }
4. 动态加载APK示例代码:

/**
 * 在APK1中调用手机上已经安装的APK2中的类
 */
public void loadAPKFile(){
    //在APK1中已知APK2的包名,寻找APK2文件所在路径
    Intent intent = new Intent();
    intent.setPackage("com.example.test");
    PackageManager pm = mContext.getPackageManager();
    final List<ResolveInfo> plugins = pm.queryIntentActivities(intent,0);
    if(plugins.size() <= 0){
        Log.i(TAG, "resolve info size is:" + plugins.size());
        return;
    }

    ResolveInfo resolveInfo = plugins.get(0);
    ActivityInfo activityInfo = resolveInfo.activityInfo;
    String packageName = activityInfo.packageName;
    //目标类所在的apk路径,class loader会通过这个路径来加载目标类文件
    String dexPath = activityInfo.applicationInfo.sourceDir;
    //Classloader用来释放dex文件的输出路径
    String dexOutputDir = mContext.getApplicationInfo().dataDir;
    //目标类可能使用的c或者c++的库文件的存放路径
    String libPath = activityInfo.applicationInfo.nativeLibraryDir;

    Log.i(TAG, "div:" + div + "   " +   
            "packageName:" + packageName + "   " +  
            "dexPath:" + dexPath + "   " +  
            "dexOutputDir:" + dexOutputDir + "   " +  
            "libPath:" + libPath);

    DexClassLoader dcLoader = new DexClassLoader(dexPath, dexOutputDir,libPath,this.getClass().getClassLoader());
    try {
        Class<?> clazz = dcLoader.loadClass(packageName + ".TestClass");//包名分隔符应该由“/”变为“.”
        Object obj = clazz.newInstance();
        Class[] param = new Class[1];
        param[0] = String.class;
        Method action = clazz.getMethod("invoke", param);
        action.invoke(obj, "test this function");
    } catch (ClassNotFoundException e) {
        Log.i(TAG, "ClassNotFoundException");
    } catch (InstantiationException e) {
        Log.i(TAG, "InstantiationException");
    } catch (IllegalAccessException e) {
        Log.i(TAG, "IllegalAccessException");
    } catch (NoSuchMethodException e) {
        Log.i(TAG, "NoSuchMethodException");
    } catch (IllegalArgumentException e) {
        Log.i(TAG, "IllegalArgumentException");
    } catch (InvocationTargetException e) {
        Log.i(TAG, "InvocationTargetException");
    }
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值