Android DexClassLoader/PathClassLoader 动态加载jar/APK

时间:2022-12-19 19:37:21

可以使用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才行,执行命令:

[cpp] view plain copy Android DexClassLoader/PathClassLoader 动态加载jar/APKAndroid DexClassLoader/PathClassLoader 动态加载jar/APK
  1. $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)

[java] view plain copy Android DexClassLoader/PathClassLoader 动态加载jar/APKAndroid DexClassLoader/PathClassLoader 动态加载jar/APK
  1. package com.test.mytest;  
  2.   
  3. public interface ITest {  
  4.     public String test();  
  5. }  
  6. package com.test.mytest;  
  7.   
  8. public class Test implements ITest {  
  9.     @Override  
  10.     public String test() {  
  11.         return "Hello";  
  12.     }  
  13. }  
动态加载jar包

[java] view plain copy Android DexClassLoader/PathClassLoader 动态加载jar/APKAndroid DexClassLoader/PathClassLoader 动态加载jar/APK
  1. public void loadDexFile(Context context) {  
  2.     copyAssetsToFiles(context);  
  3.     String filePath = context.getFilesDir().getPath() + "/testDex.jar";  
  4.       
  5.     //动态加载的也可以是APK文件,没有任何区别,APK 文件中的class.dex文件会被DexClassLoader加载。  
  6.     //但是,APK中的Activity类,由于是使用反射,无法取得Context,与普通的类毫无区别,没有生命周期。  
  7.     //String filePath = context.getFilesDir().getPath() + "/Test.apk";  
  8.       
  9.     DexClassLoader classLoader = new DexClassLoader(filePath, context.getFilesDir().getPath(), null, context.getClassLoader());  
  10.     try {  
  11.         //方法一:反射调用  
  12.         Class myClass = classLoader.loadClass("com.test.mytest.Test");  
  13.         Constructor myConstructor = myClass.getConstructor(Context.class);  
  14.         Method method = myClass.getMethod("test"null);  
  15.         String data = (String) method.invoke(myConstructor.newInstance(this), null);  
  16.         //method.setAccessible(true);访问private函数  
  17.         System.out.println(data);  
  18.           
  19.         //方法二:类型强转,前提是知道Test.jar中Class所实现的接口类<span style="font-family: Arial, Helvetica, sans-serif;">ITest</span>  
  20.         Class myClass2 = classLoader.loadClass("com.test.mytest.Test");  
  21.         Constructor myConstructor2 = myClass2.getConstructor(Context.class);  
  22.         ITest obj = (ITest) myConstructor2.newInstance(this);  
  23.         String data2 = obj.test();  
  24.         System.out.println(data2);  
  25.     } catch (Exception e) {  
  26.         e.printStackTrace();  
  27.     }  
  28. }  
  29.   
  30. /** 
  31.  * Copy the APK "assets/" to "/data/data/package-name/file/" 
  32.  */  
  33. public static void copyAssetsToFiles(Context context) {  
  34.     String fileDir = context.getFilesDir().getPath() + "/";  
  35.     File workingDir = new File(fileDir);  
  36.     if (!workingDir.exists()) {  
  37.         workingDir.mkdirs();  
  38.     }  
  39.   
  40.     File outFile_bin = new File(workingDir, "test.jar");  
  41.     if (!outFile_bin.exists()) {  
  42.         copyFile(context, "test.jar", outFile_bin);  
  43.         outFile_bin.setExecutable(truefalse);   
  44.     }  
  45. }  
  46.   
  47. /** 
  48.  * Copy assets file to the data folder 
  49.  */  
  50. private static void copyFile(Context context, String sourceFileName, File targetFile) {  
  51.     InputStream in = null;  
  52.     FileOutputStream out = null;  
  53.     try {  
  54.         in = context.getAssets().open(sourceFileName);  
  55.         out = new FileOutputStream(targetFile);  
  56.         byte[] temp = new byte[1024];  
  57.         int count = 0;  
  58.         while ((count = in.read(temp)) > 0) {  
  59.             out.write(temp, 0, count);  
  60.         }  
  61.   
  62.         if (in != null) {  
  63.             in.close();  
  64.         }  
  65.         if (out != null) {  
  66.             out.close();  
  67.         }  
  68.     } catch (Exception e) {  
  69.         if (in != null) {  
  70.             try {  
  71.                 in.close();  
  72.             } catch (IOException e1) {  
  73.                 e1.printStackTrace();  
  74.             }  
  75.         }  
  76.   
  77.         if (out != null) {  
  78.             try {  
  79.                 out.close();  
  80.             } catch (IOException e1) {  
  81.                 e1.printStackTrace();  
  82.             }  
  83.         }  
  84.     }  
  85. }  
4. 动态加载APK示例代码:

[java] view plain copy Android DexClassLoader/PathClassLoader 动态加载jar/APKAndroid DexClassLoader/PathClassLoader 动态加载jar/APK
  1. /** 
  2.  * 在APK1中调用手机上已经安装的APK2中的类 
  3.  */  
  4. public void loadAPKFile(){  
  5.     //在APK1中已知APK2的包名,寻找APK2文件所在路径  
  6.     Intent intent = new Intent();  
  7.     intent.setPackage("com.example.test");  
  8.     PackageManager pm = mContext.getPackageManager();  
  9.     final List<ResolveInfo> plugins = pm.queryIntentActivities(intent,0);  
  10.     if(plugins.size() <= 0){  
  11.         Log.i(TAG, "resolve info size is:" + plugins.size());  
  12.         return;  
  13.     }  
  14.   
  15.     ResolveInfo resolveInfo = plugins.get(0);  
  16.     ActivityInfo activityInfo = resolveInfo.activityInfo;  
  17.     String packageName = activityInfo.packageName;  
  18.     //目标类所在的apk路径,class loader会通过这个路径来加载目标类文件  
  19.     String dexPath = activityInfo.applicationInfo.sourceDir;  
  20.     //Classloader用来释放dex文件的输出路径  
  21.     String dexOutputDir = mContext.getApplicationInfo().dataDir;  
  22.     //目标类可能使用的c或者c++的库文件的存放路径  
  23.     String libPath = activityInfo.applicationInfo.nativeLibraryDir;  
  24.   
  25.     Log.i(TAG, "div:" + div + "   " +     
  26.             "packageName:" + packageName + "   " +    
  27.             "dexPath:" + dexPath + "   " +    
  28.             "dexOutputDir:" + dexOutputDir + "   " +    
  29.             "libPath:" + libPath);  
  30.   
  31.     DexClassLoader dcLoader = new DexClassLoader(dexPath, dexOutputDir,libPath,this.getClass().getClassLoader());  
  32.     try {  
  33.         Class<?> clazz = dcLoader.loadClass(packageName + ".TestClass");//包名分隔符应该由“/”变为“.”  
  34.         Object obj = clazz.newInstance();  
  35.         Class[] param = new Class[1];  
  36.         param[0] = String.class;  
  37.         Method action = clazz.getMethod("invoke", param);  
  38.         action.invoke(obj, "test this function");  
  39.     } catch (ClassNotFoundException e) {  
  40.         Log.i(TAG, "ClassNotFoundException");  
  41.     } catch (InstantiationException e) {  
  42.         Log.i(TAG, "InstantiationException");  
  43.     } catch (IllegalAccessException e) {  
  44.         Log.i(TAG, "IllegalAccessException");  
  45.     } catch (NoSuchMethodException e) {  
  46.         Log.i(TAG, "NoSuchMethodException");  
  47.     } catch (IllegalArgumentException e) {  
  48.         Log.i(TAG, "IllegalArgumentException");  
  49.     } catch (InvocationTargetException e) {  
  50.         Log.i(TAG, "InvocationTargetException");  
  51.     }  
  52. }