我们在服务端下载修复bug后的classes.dex并且动态替换原有的classes.dex这个过程的源代码的分析是我们今天要讨论的。
文末给出所有源码。
Context家族的classloader就是Android默认的加载器PathClassLoader。所以我们很简单一句话就可以获得了。
ClassLoader pathClassLoader = context.getClassLoader();
PathClassLoader只能加载已安装的apk,dex class loader则没有这个限制。所以我们需要创建一个dex class loader
ClassLoader classLoader = new DexClassLoader(patchFile.getAbsolutePath(),// dexPath oDexFile.getAbsolutePath(),// optimizedDirectory null, pathClassLoader );
oDexFile.getAbsolutePath()是odex的路径,他相当于是classes.dex的helper,我们约定俗成在创建dex class loader需要传入他的路径。
patchFile.getAbsolutePath(),他就是我们的服务端传来的用于替换原来bug的dex的dex了。
这样声明以后,这个新的dex中所有的类,我们都可以通过dex class loader去访问了哦!!
然后我们就是分别获取PathClassLoader和dex class loader的element数组,然后把dex class loader的element插入到前者的某一位上即可。PathClassLoader中每一位都是一个dex,如果你不分包的话,他只有一个dex,所以我们想要热修复,自然是需要分包的。
我们看一下从classloader中获取element数组的方法
private static Object getDexElementByClassLoader(ClassLoader classLoader) throws Exception { Class> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); Field pathListField = classLoaderClass.getDeclaredField("pathList"); pathListField.setAccessible(true); Object pathList = pathListField.get(classLoader); Class> pathListClass = pathList.getClass(); Field dexElementsField = pathListClass.getDeclaredField("dexElements"); dexElementsField.setAccessible(true); Object dexElements = dexElementsField.get(pathList); return dexElements; }
我们用反射去拿到了classloader中的pathlist,再用反射去拿到pathlist中的element数组,就这么简单
我们看一下我们的dex class loader的element数组是怎么插入到前者的
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; }
lhs是dex。。。的,rhs是path。。。的。我们可以看到,前面的部分,都用dex。。。的,后面的部分,才用path。。。相应位上的。我觉得这里的代码不是很对,因为这个element数组,你怎么知道,你要替换的类是前多少个?所以这里的代码还有待考究。(但是如果你仅仅有2个包的话,那就没有问题了。因为分包的时候,一个包是作为启动包的,他肯定会被率先加载,然后 被cache起来。所以哪怕被覆盖了,也不要紧)
然后再把最新生成的数组,重新使用反射的方式,设置给pathclassloader了,这和上面某个代码是类似的。
全部代码给出
package com.example.myapplication; import android.app.Application; import android.content.Context; import android.os.Environment; import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Field; import dalvik.system.DexClassLoader; public class App extends Application { @Override public void onCreate() { super.onCreate(); try { injectDexElements(this, Environment.getExternalStorageDirectory().getAbsolutePath()+"/classes2.dex"); } catch (Exception e) { e.printStackTrace(); } } public static void injectDexElements(Context context, String patchFilePath) throws Exception { ClassLoader pathClassLoader = context.getClassLoader(); /**dex, 优化后的路径, 必须在要App data目录下, 否则会没有权限*/ File oDexFile = new File(context.getDir("odex", Context.MODE_PRIVATE).getAbsolutePath()); /**dex 补丁文件路径(文件夹)*/ File patchFile = new File(patchFilePath); // if (!patchFile.exists()) { // patchFile.mkdirs(); // } // 合并成一个数组 Object applicationDexElement = getDexElementByClassLoader(pathClassLoader); // for (File dexFile : patchFile.listFiles()) { ClassLoader classLoader = new DexClassLoader(patchFile.getAbsolutePath(),// dexPath oDexFile.getAbsolutePath(),// optimizedDirectory null, pathClassLoader ); // 获取这个classLoader中的Element Object classElement = getDexElementByClassLoader(classLoader); //Log.e("TAG", classElement.toString()); applicationDexElement = combineArray(classElement, applicationDexElement); // } // 注入到pathClassLoader中 injectDexElements(pathClassLoader, applicationDexElement); } /** * 把dexElement注入到已运行classLoader中 * * @param classLoader * @param dexElement * @throws Exception */ private static void injectDexElements(ClassLoader classLoader, Object dexElement) throws Exception { Class> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); Field pathListField = classLoaderClass.getDeclaredField("pathList"); pathListField.setAccessible(true); Object pathList = pathListField.get(classLoader); Class> pathListClass = pathList.getClass(); Field dexElementsField = pathListClass.getDeclaredField("dexElements"); dexElementsField.setAccessible(true); dexElementsField.set(pathList, dexElement); } /** * 合并两个dexElements数组 * * @param arrayLhs * @param arrayRhs * @return */ 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; } /** * 获取classLoader中的DexElement * * @param classLoader ClassLoader */ private static Object getDexElementByClassLoader(ClassLoader classLoader) throws Exception { Class> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); Field pathListField = classLoaderClass.getDeclaredField("pathList"); pathListField.setAccessible(true); Object pathList = pathListField.get(classLoader); Class> pathListClass = pathList.getClass(); Field dexElementsField = pathListClass.getDeclaredField("dexElements"); dexElementsField.setAccessible(true); Object dexElements = dexElementsField.get(pathList); return dexElements; } }