上一小节创建的module提供相应的方法来让我们对项目的类进行代码注入,我们需要在build.gradle来配置让它自动来做这件事:
apply plugin: ‘com.android.application’ task(‘processWithJavassist’) << { String classPath = file(‘build/intermediates/classes/debug’)// 项目编译class所在目录 com.devilwwj.patch.PatchClass.process(classPath, project(‘:hackdex’).buildDir.absolutePath + “/intermediates/classes/debug”) // 第二个参数是hackdex的class所在目录 } android { compileSdkVersion 23 buildToolsVersion “23.0.1” defaultConfig { applicationId “com.devilwwj.hotfixdemo” minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName “1.0” } buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’ } release { minifyEnabled false proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’ } } applicationVariants.all { variant -> variant.dex.dependsOn << processWithJavassist // 在执行dx命令之前将代码打入到class中 } } dependencies { compile fileTree(dir: ‘libs’, include: [‘*.jar’]) testCompile ‘junit:junit:4.12’ compile ‘com.android.support:appcompat-v7:23.1.1’ compile ‘com.android.support:design:23.1.1’ compile project(‘:hotfixlib’) } 这时候我们run项目,反编译build/output/apk下的app-debug.apk文件,你就可以看到代码已经成功植入了。 mac下的反编译工具: https://sourceforge.net/projects/jadx/?source=typ_redirect 反编译的结果如下图: 其实你也可以直接在项目中看: 创建hotfixlib模块,并关联到项目中 这差不多是最后一步了,也是最核心的一步,提供将heck_dex.jar动态插入到dexElements的方法。 核心代码: package com.devilwwj.hotfixlib; import android.annotation.TargetApi; import android.content.Context; import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import dalvik.system.DexClassLoader; import dalvik.system.PathClassLoader; /** • com.devilwwj.hotfixlib • Created by devilwwj on 16/3/9. */ public final class HotFix { public static void patch(Context context, String patchDexFile, String patchClassName) { if (patchDexFile != null && new File(patchDexFile).exists()) { try { if (hasLexClassLoader()) { injectAliyunOs(context, patchDexFile, patchClassName); } else if (hasDexClassLoader()) { injectAboveEqualApiLevel14(context, patchDexFile, patchClassName); } else { injectBelowApiLevel14(context, patchDexFile, patchClassName); } } catch (Throwable th) { } } } private static boolean hasLexClassLoader() { try { Class.forName(“dalvik.system.LexClassLoader”); return true; } catch (ClassNotFoundException e) { e.printStackTrace(); return false; } } private static boolean hasDexClassLoader() { try { Class.forName(“dalvik.system.BaseDexClassLoader”); return true; } catch (ClassNotFoundException e) { e.printStackTrace(); return false; } } private static void injectAliyunOs(Context context, String patchDexFile, String patchClassName) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { PathClassLoader obj = (PathClassLoader) context.getClassLoader(); String replaceAll = new File(patchDexFile).getName().replaceAll(“\.[a-zA-Z0-9]+”, “.lex”); Class cls = Class.forName(“dalvik.system.LexClassLoader”); Object newInstance = cls.getConstructor(new Class[]{String.class, String.class, String.class, ClassLoader.class}).newInstance( new Object[]{context.getDir(“dex”, 0).getAbsolutePath() • File.separator + replaceAll, context.getDir(“dex”, 0).getAbsolutePath(), patchDexFile, obj}); cls.getMethod(“loadClass”, new Class[]{String.class}).invoke(newInstance, new Object[]{patchClassName}); setField(obj, PathClassLoader.class, “mPaths”, appendArray(getField(obj, PathClassLoader.class, “mPaths”), getField(newInstance, cls, “mRawDexPath”))); setField(obj, PathClassLoader.class, “mFiles”, combineArray(getField(obj, PathClassLoader.class, “mFiles”), getField(newInstance, cls, “mFiles”))); setField(obj, PathClassLoader.class, “mZips”, combineArray(getField(obj, PathClassLoader.class, “mZips”), getField(newInstance, cls, “mZips”))); setField(obj, PathClassLoader.class, “mLexs”, combineArray(getField(obj, PathClassLoader.class, “mLexs”), getField(newInstance, cls, “mDexs”))); } @TargetApi(14) private static void injectBelowApiLevel14(Context context, String str, String str2) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { PathClassLoader obj = (PathClassLoader) context.getClassLoader(); DexClassLoader dexClassLoader = new DexClassLoader(str, context.getDir(“dex”, 0).getAbsolutePath(), str, context.getClassLoader()); dexClassLoader.loadClass(str2); setField(obj, PathClassLoader.class, “mPaths”, appendArray(getField(obj, PathClassLoader.class, “mPaths”), getField(dexClassLoader, DexClassLoader.class, “mRawDexPath”))); setField(obj, PathClassLoader.class, “mFiles”, combineArray(getField(obj, PathClassLoader.class, “mFiles”), getField(dexClassLoader, DexClassLoader.class, “mFiles”))); setField(obj, PathClassLoader.class, “mZips”, combineArray(getField(obj, PathClassLoader.class, “mZips”), getField(dexClassLoader, DexClassLoader.class, “mZips”))); setField(obj, PathClassLoader.class, “mDexs”, combineArray(getField(obj, PathClassLoader.class, “mDexs”), getField(dexClassLoader, DexClassLoader.class, “mDexs”))); obj.loadClass(str2); } /** • 将dex注入dexElements数组中 • @param context • @param str • @param str2 • @throws ClassNotFoundException • @throws NoSuchFieldException • @throws IllegalAccessException */ private static void injectAboveEqualApiLevel14(Context context, String str, String str2) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader(); Object a = combineArray(getDexElements(getPathList(pathClassLoader)), getDexElements(getPathList(new DexClassLoader(str, context.getDir(“dex”, 0).getAbsolutePath(), str, context.getClassLoader())))); Object a2 = getPathList(pathClassLoader); setField(a2, a2.getClass(), “dexElements”, a); pathClassLoader.loadClass(str2); } /** • 通过PathClassLoader拿到pathList • @param obj • @return • @throws ClassNotFoundException • @throws NoSuchFieldException • @throws IllegalAccessException */ private static Object getPathList(Object obj) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { return getField(obj, Class.forName(“dalvik.system.BaseDexClassLoader”), “pathList”); } /** • 通过pathList取得dexElements对象 • @param obj • @return • @throws NoSuchFieldException • @throws IllegalAccessException */ private static Object getDexElements(Object obj) throws NoSuchFieldException, IllegalAccessException { return getField(obj, obj.getClass(), “dexElements”); } /** • 通过反射拿到指定对象 • @param obj • @param cls • @param str • @return • @throws NoSuchFieldException • @throws IllegalAccessException */ private static Object getField(Object obj, Class cls, String str) throws NoSuchFieldException, IllegalAccessException { Field declaredField = cls.getDeclaredField(str); declaredField.setAccessible(true); return declaredField.get(obj); } /** • 通过反射设置属性 • @param obj • @param cls • @param str • @param obj2 • @throws NoSuchFieldException • @throws IllegalAccessException */ private static void setField(Object obj, Class cls, String str, Object obj2) throws NoSuchFieldException, IllegalAccessException { Field declaredField = cls.getDeclaredField(str); declaredField.setAccessible(true); declaredField.set(obj, obj2); } /** • 合并数组 • @param obj • @param obj2 • @return */ private static Object combineArray(Object obj, Object obj2) { Class componentType = obj2.getClass().getComponentType(); int length = Array.getLength(obj2); int length2 = Array.getLength(obj) + length; Object newInstance = Array.newInstance(componentType, length2); for (int i = 0; i < length2; i++) { if (i < length) { Array.set(newInstance, i, Array.get(obj2, i)); } else { Array.set(newInstance, i, Array.get(obj, i - length)); } } return newInstance; } /** • 添加到数组 • @param obj • @param obj2