Android插件化(二):使用DexClassLoader动态载入assets中的apk

简单介绍

上一篇博客讲到。我们能够使用MultiDex.java载入离线的apk文件。须要注意的是,apk中的类是载入到当前的PathClassLoader其中的,假设apk文件过多。可能会出现ANR的情况。那么。我们能不能使用DexClassLoader载入apk呢?当然是能够的!

首先看一下Doc文档.

A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.

也就是说,DexClassLoader能够载入一个含有classes.dex文件的压缩包,既能够是jar也能够是apk。那么载入一个离线的apk文件须要注意哪些呢?

  • 1.DexClassLoader的构造方法:

    DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)

  • 2.私有文件夹

    This class loader requires an application-private, writable directory to cache optimized classes.

了解到上述两点,我们就能够依据DexClassLoader所须要的參数。动态载入assets中的apk了。

源代码

BundleClassLoaderManager

该类主要是负责管理这些DexClassLoader的,首先,我们定义了一个叫做BundleDexClassLoader的类,它继承自DexClassLoader,用于载入离线的apk文件。每个apk文件相应一个BundleDexClassLoader,而BundleClassLoaderManager则保存了一个List,在载入的时候。用于查找类。

详细代码例如以下:

package net.mobctrl.hostapk;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.List; import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build; /**
* @Author Zheng Haibo
* @PersonalWebsite http://www.mobctrl.net
* @version $Id: BundleClassLoaderManager.java, v 0.1 2015年12月11日 下午7:30:59
* mochuan.zhb Exp $
* @Description
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class BundleClassLoaderManager { public static List<BundleDexClassLoader> bundleDexClassLoaderList = new ArrayList<BundleDexClassLoader>(); /**
* 载入Assets里的apk文件
* @param context
*/
public static void install(Context context) {
AssetsManager.copyAllAssetsApk(context);
// 获取dex文件列表
File dexDir = context.getDir(AssetsManager.APK_DIR,
Context.MODE_PRIVATE);
File[] szFiles = dexDir.listFiles(new FilenameFilter() { @Override
public boolean accept(File dir, String filename) {
return filename.endsWith(AssetsManager.FILE_FILTER);
}
});
for (File f : szFiles) {
System.out.println("debug:load file:" + f.getName());
BundleDexClassLoader bundleDexClassLoader = new BundleDexClassLoader(
f.getAbsolutePath(), dexDir.getAbsolutePath(), null,
context.getClassLoader());
bundleDexClassLoaderList.add(bundleDexClassLoader);
}
} /**
* 查找类
*
* @param className
* @return
* @throws ClassNotFoundException
*/
public static Class<? > loadClass(Context context,String className) throws ClassNotFoundException {
try {
Class<?> clazz = context.getClassLoader().loadClass(className);
if (clazz != null) {
System.out.println("debug: class find in main classLoader");
return clazz;
}
} catch (Exception e) {
e.printStackTrace();
}
for (BundleDexClassLoader bundleDexClassLoader : bundleDexClassLoaderList) {
try {
Class<?> clazz = bundleDexClassLoader.loadClass(className);
if (clazz != null) {
System.out.println("debug: class find in bundle classLoader");
return clazz;
}
} catch (Exception e) {
e.printStackTrace();
}
}
throw new ClassCastException(className + " not found exception");
}
}

注意点:

  • 1.install方法

    install方法主要是将assets中的apk所有复制到私有文件夹,然后再遍历私有文件夹。使用BundleDexClassLoader载入apk文件。然后将这些BundleDexClassLoader保存到数组中。

  • 2.loadClass方法

    该方法先从当前的ClassLoader中查找须要的类,假设找不到,在从List中遍历查找。

DEMO执行

在MainActivity中,我们能够通过例如以下方式。调用apk类中的方法:

      private void loadApk() {
try {
Class<? > clazz = BundleClassLoaderManager.loadClass(getApplicationContext(),
"net.mobctrl.normal.apk.Utils");
Constructor<?> constructor = clazz.getConstructor();
Object bundleUtils = constructor.newInstance(); Method printSumMethod = clazz.getMethod("printSum", Context.class,
int.class, int.class, String.class);
printSumMethod.setAccessible(true);
Integer sum = (Integer) printSumMethod.invoke(bundleUtils,
getApplicationContext(), 10, 20, "计算结果");
System.out.println("debug:sum = " + sum);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}

与MultiDex不同一时候。我们是通过BundleClassLoaderManager来载入类的,而不是当前的ClassLoader。

改进方案

正如BundleClassLoaderManager中的loadClass方法。事实上我们创建一个ClassLoader对象,通过重写当前ClassLoader的findClass方法就可以,然后在Override的findClass方法中,首先从当前ClassLoader中查找类,然后再从BundleDexClassLoader中遍历查找,这样既能够在Host项目中调用Bundle中的类,也能够在Bundle中调用Host中的类。


mClassLoader = new ClassLoader(super.getClassLoader()) { @Override
protected Class<?> findClass(String className)
throws ClassNotFoundException {
Class clazz = BundleClassLoaderManager.loadClass(context,className);
if (clazz == null) {
throw new ClassNotFoundException(className);
}
return clazz;
}
};

总结

上一篇博客和这一篇博客将的都是类的载入。假设所须要载入的类都是工具类,不须要载入资源等,那么上面的方案都没啥问题。可是假设载入的类是Fragment或者Activity等UI,须要引用资源文件,这又改怎样处理呢?

下一篇博文:Android资源的离线载入。

參考

1.BaseDexClassLoader源代码

最新文章

  1. telnet 使用
  2. Sql Server 2012 Enterprise Edition 企业版 迅雷 下载地址
  3. 管理员必须掌握的八个cmd命令
  4. hdu 5265 pog loves szh II STL
  5. Excel操作之 导出生成多个sheet页面
  6. 帕累托分析法(Pareto Analysis)(柏拉图分析)
  7. Android Studio 将工程作为第三方类库的步骤
  8. Java课程设计——GUI密码生成器201521123035
  9. c++(快速排序)
  10. obj-c编程06:反射与元编程初步
  11. Find them, Catch them POJ - 1703
  12. 【BZOJ1013】【JSOI2008】球形空间产生器 高斯消元
  13. S5PV210初始化系统时钟
  14. maven ----&gt; 子工程中引入父工程
  15. Struts2 --简单留言系统
  16. golang中的接口实现(二)
  17. Jmeter 同一个测试计划下的多个线程组 执行顺序 希望调整为顺序执行
  18. PIG之 Hadoop 2.7.4 + pig-0.17.0 安装
  19. Protobuf3教程
  20. laravel中get方式表单提交后, 地址栏数据重复的问题

热门文章

  1. 32.AngularJS 表达式
  2. rest_framework_HyperlinkedIdentityField
  3. A list is a sequence
  4. POJ 3666 DP
  5. hive查询不加分区的一个异常
  6. PHP接收GET中文参数乱码的原因及解决方案
  7. vue打包后js和css、图片不显示,引用的字体找不到问题
  8. [笔记-统计学习方法]感知机模型(perceptron) 原理与实现
  9. Laravel+vue实现history模式URL可行方案
  10. ubuntu中开启、关闭防火墙