首先,想要了解热更新需要明白android加载机制,此处我画了一个图来说明该机制。详细如下:
Android类加载机制
java在运行时加载对应的类是通过ClassLoader实现的,ClassLoader是一个抽象类,在Android中使用PathClassLoader类作为Android默认的类加载器。
PathClassLoader继承自BaseDexClassLoader类,而BaseDexClassLoader类重写了父类ClassLoader类的findClass()方法,该方法是一个关键点;通过查看源码得知,findClass()方法的实现由pathList.findClass()方法来完成。在该方法中,通过遍历dexElements列表等一系列操作实现类的加载。
pathList对象的由来:在BaseDexClassLoader类的构造方法中通过new产生,类型是DexPathList类。
dexElements对象的由来:在DexPathList类的构造方法中产生,类型是Element。
热更新实现原理
由上得知,如果一个类被成功加载,那么它的dex一定会出现在dexElements所对应的dex文件中,至此,所要做的就是让我们修改后的类先被加载,即让hotpacth dex文件出现在dexElements列表的前面。要实现热更新,就需要我们在运行时去更改PathClassLoader.pathList.dexElements,由于这些属性都是private的,因此需要通过反射来修改。另外,构造我们自己的dex文件所对应的dexElements数组的时候,我们也可以采取一个比较取巧的方式,就是通过构造一个DexClassLoader对象来加载我们的dex文件,并且调用一次dexClassLoader.loadClass(dummyClassName); 方法,这样,dexClassLoader.pathList.dexElements中,就会包含我们的dex,通过把dexClassLoader.pathList.dexElements插入到系统默认的classLoader.pathList.dexElements列表前面,就可以让系统优先加载我们的dex中的类,从而可以实现热更新了。
实现的工具类 DexLoaderUtil.java;
public class DexLoaderUtil {
private static final String TAG = "DexLoaderUtil";
public static String SECONDARY_DEX_NAME = "lib.apk"; //默认值
public static String THIRD_DEX_NAME = "lib2.apk"; //默认值
private static final int BUF_SIZE = 8 * 1024;
private static String packageName = "com.example.MyClass"; //默认值
//设置要修复apk的名字
public static void setSecondDexName(String arg1){
SECONDARY_DEX_NAME = arg1;
}
//设置要修复apk的名字
public static void setThirdDexName(String arg1){
THIRD_DEX_NAME = arg1;
}
public static void setPackageName(String arg1){
packageName = arg1;
}
public static String getDexPath(Context context, String dexName) {
return new File(context.getDir("dex", Context.MODE_PRIVATE), dexName).getAbsolutePath();
}
public static String getOptimizedDexPath(Context context) {
return context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath();
}
//将apk内容复制到缓存
public static void copyDex(Context context, String dexName) {
File dexInternalStoragePath = new File(context.getDir("dex", Context.MODE_PRIVATE),
dexName);
BufferedInputStream bis = null;
OutputStream dexWriter = null;
try {
// bis = new BufferedInputStream(context.getAssets().open(dexName));
//此处要将原始安装包和差分包放在root/Download路径下,否则会报错
bis = new BufferedInputStream(new FileInputStream(Environment.getExternalStorageDirectory() + "/Download/" + dexName));
dexWriter = new BufferedOutputStream(
new FileOutputStream(dexInternalStoragePath));
byte[] buf = new byte[BUF_SIZE];
int len;
while((len = bis.read(buf, 0, BUF_SIZE)) > 0) {
dexWriter.write(buf, 0, len);
}
dexWriter.close();
bis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void loadAndCall(Context context, String dexName) {
final File dexInternalStoragePath = new File(context.getDir("dex", Context.MODE_PRIVATE), dexName);
final File optimizedDexOutputPath = context.getDir("outdex", Context.MODE_PRIVATE);
DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
optimizedDexOutputPath.getAbsolutePath(),
null,
context.getClassLoader());
call(cl);
}
public static void call(ClassLoader cl) {
Class myClasz = null;
try {
myClasz =
cl.loadClass(packageName);
Object instance = myClasz.getConstructor().newInstance();
myClasz.getDeclaredMethod("test1").invoke(instance);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
//将差分包内容插入原dexElements之前
public static synchronized Boolean injectAboveEqualApiLevel14(
String dexPath, String defaultDexOptPath, String nativeLibPath, String dummyClassName) {
Log.i(TAG, "--> injectAboveEqualApiLevel14");
PathClassLoader pathClassLoader = (PathClassLoader) DexLoaderUtil.class.getClassLoader();
DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, nativeLibPath, pathClassLoader);
try {
dexClassLoader.loadClass(dummyClassName);
Object dexElements = combineArray(
getDexElements(getPathList(pathClassLoader)),
getDexElements(getPathList(dexClassLoader)));
Object pathList = getPathList(pathClassLoader);
setField(pathList, pathList.getClass(), "dexElements", dexElements);
} catch (Throwable e) {
e.printStackTrace();
return false;
}
Log.i(TAG, "<-- injectAboveEqualApiLevel14 End.");
return true;
}
private static Object getPathList(Object baseDexClassLoader)
throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}
private static Object getDexElements(Object paramObject)
throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
return getField(paramObject, paramObject.getClass(), "dexElements");
}
private static Object getField(Object obj, Class<?> cl, String field)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
return localField.get(obj);
}
private static void setField(Object obj, Class<?> cl, String field, Object value)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
localField.set(obj, value);
}
private static Object combineArray(Object arrayLhs, Object arrayRhs) {
Class<?> localClass = arrayLhs.getClass().getComponentType();
int i = Array.getLength(arrayLhs);
int j = i + Array.getLength(arrayRhs);
Object result = Array.newInstance(localClass, j);
for (int k = 0; k < j; ++k) {
if (k < i) {
Array.set(result, k, Array.get(arrayLhs, k));
} else {
Array.set(result, k, Array.get(arrayRhs, k - i));
}
}
return result;
}
}
使用方式,在MainActivity中实现如下:
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "------------start");
DexLoaderUtil.copyDex(MainActivity.this, DexLoaderUtil.SECONDARY_DEX_NAME);
DexLoaderUtil.copyDex(MainActivity.this, DexLoaderUtil.THIRD_DEX_NAME);
String secondDexPath = DexLoaderUtil.getDexPath(MainActivity.this, DexLoaderUtil.SECONDARY_DEX_NAME);
String thirdDexPath = DexLoaderUtil.getDexPath(MainActivity.this, DexLoaderUtil.THIRD_DEX_NAME);
final String optimizedDexOutputPath = DexLoaderUtil.getOptimizedDexPath(MainActivity.this);
DexLoaderUtil.injectAboveEqualApiLevel14(thirdDexPath, optimizedDexOutputPath, null, "com.example.MyClass");
DexLoaderUtil.injectAboveEqualApiLevel14(secondDexPath, optimizedDexOutputPath, null, "com.example.MyClass");
DexLoaderUtil.call(getClassLoader());
Log.d(TAG, "------------end");
}
}).start();
说明:该文章只是单纯记录本人对热更新的理解,方便日后复习和使用,全文借鉴大头鬼老师的博客,在此非常感谢大头鬼老师。