原理可參考這篇文章QFix探索之路—手Q熱補丁輕量級方案java
前序文章參考 熱修復之冷啓動類加載原理與實現android
回到這張圖,從dvmResolveClass方法入手,提早解析patch類。 一開始想到的方案是提早使用"const-class" 或者 "instance-of"指令建立類,fromUnverifiedConstant = true,繞過dex檢測。實際也成功了。但有兩個問題:git
public class ApplicationApp extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
DexInstaller.installDex(base, this.getExternalCacheDir().getAbsolutePath() + "/patch.dex");
//會執行 const-class 指令
Log.d("alvin", "bug class:" + com.a.fix.M.class);
}
複製代碼
QFix放棄了此直接load patch class的方案。通過分析,github
獲得以下方案:markdown
至於怎麼找到這個方法的,固然就是源碼裏面遊蕩了。app
所有實現代碼都在github中ide
自定義gradle插件,使用smali操做dexfile,注入class。oop
1.buildSrc/build.gradle加入依賴post
//buildSrc/build.gradle
dependencies {
...
compile group: 'org.smali', name: 'dexlib2', version: '2.2.4'
...
}
複製代碼
2.plugin代碼性能
class QFixPlugin implements Plugin<Project> {
void apply(Project project1) {
project1.afterEvaluate { project ->
project.tasks.mergeDexDebug {
doLast {
println 'QFixPlugin inject Class after mergeDexDebug'
project.tasks.mergeDexDebug.getOutputs().getFiles().each { dir ->
println "outputs: " + dir
if (dir != null && dir.exists()) {
def files = dir.listFiles()
files.each { file ->
String dexfilepath = file.getAbsolutePath()
println "Outputs Dex file's path: " + dexfilepath
InjectClassHelper.injectHackClass(dexfilepath)
}
}
}
}
}
}
}
}
複製代碼
InjectClassHelper.java
public class InjectClassHelper {
public static void injectHackClass(String dexPath) {
try {
File file = new File(dexPath);
String fileName = file.getName();
String indexStr = fileName.split("\\.")[0].replace("classes", "");
System.out.println(" =============indexStr:"+indexStr);
String className = "com.a.Hack"+ indexStr;
String classType = "Lcom/a/Hack" + indexStr + ";";
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexPath, Opcodes.getDefault());
ImmutableDexFile immutableDexFile = ImmutableDexFile.of(dexFile);
Set<ClassDef> classDefs = new HashSet<>();
for (ImmutableClassDef classDef : immutableDexFile.getClasses()) {
classDefs.add(classDef);
}
ImmutableClassDef immutableClassDef = new ImmutableClassDef(
classType,
AccessFlags.PUBLIC.getValue(),
"Ljava/lang/Object;",
null, null, null, null, null);
classDefs.add(immutableClassDef);
String resultPath = dexPath;
File resultFile = new File(resultPath);
if (resultFile != null && resultFile.exists()) resultFile.delete();
DexFileFactory.writeDexFile(resultPath, new DexFile() {
@Override
public Set<ClassDef> getClasses() {
return new HashSet<>(classDefs);
}
@Override
public Opcodes getOpcodes() {
return dexFile.getOpcodes();
}
});
System.out.println("Outputs injectHackClass: " + file.getName() + ":" + className);
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製代碼
Outputs Dex file's path: /Users/mawenqiang/Documents/demo_project/hotfix_dexload/dexload_QFix/build/intermediates/dex/debug/out/classes2.dex Outputs injectHackClass: classes2.dex:com.a.Hack2 Outputs Dex file's path: /Users/mawenqiang/Documents/demo_project/hotfix_dexload/dexload_QFix/build/intermediates/dex/debug/out/classes.dex
Outputs injectHackClass: classes.dex:com.a.Hack
複製代碼
執行指令 dexdump
#dexdump -h classes2.dex > classes2.dump
Class #1697 header:
class_idx : 2277 #class_idx
......
Class descriptor : 'Lcom/a/fix/M;'
......
複製代碼
咱們能夠獲得mapping.txt
classes2.dex:com.a.Hack2:com.a.fix.M:2277
複製代碼
patch.dex的生成和加載不變,參看本文上方。
一樣在ApplicationApp.attachBaseContext()中執行,在load patch以後執行。 代碼文件 ApplicationApp.java
public static void resolvePatchClasses(Context context) {
try {
BufferedReader br = new BufferedReader(new FileReader(context.getExternalCacheDir().getAbsolutePath() + "/classIdx.txt"));
String line = "";
while (!TextUtils.isEmpty(line = br.readLine())) {
String[] ss = line.split(":");
//classes2.dex:com.a.Hack2:com.a.fix.M:2277
if (ss != null && ss.length == 4) {
String hackClassName = ss[1];
long patchClassIdx = Long.parseLong(ss[3]);
Log.d("alvin", "readLine:" + line);
String hackClassDescriptor = "L" + hackClassName.replace('.', '/') + ";";
Log.d("alvin", "classNameToDescriptor: " + hackClassName + " --> " + hackClassDescriptor);
ResolveTool.loadClass(context, hackClassName);
ResolveTool.nativeResolveClass(hackClassDescriptor, patchClassIdx);
}
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/** * * "descriptor" should have the form "Ljava/lang/Class;" or * * "[Ljava/lang/Class;", i.e. a descriptor and not an internal-form * * class name. * * @param referrerDescriptor * @param classIdx * @return */
public static native boolean nativeResolveClass(String referrerDescriptor, long classIdx);
public static void loadClass(Context context, String className) {
try {
Log.d("alvin", context.getClassLoader().loadClass(className).getSimpleName());
} catch (Exception e) {
e.printStackTrace();
Log.d("alvin", e.getMessage());
}
}
複製代碼
nativeResolveClass 就是正常的jni方法,代碼實際也是簡單的。
#include <jni.h>
#include <android/log.h>
#include <dlfcn.h>
#define LOG_TAG "alvin"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
//方法指針
void *(*dvmFindLoadedClass)(const char *);
//方法指針
void *(*dvmResolveClass)(const void *, unsigned int, bool);
extern "C" jboolean Java_com_a_dexload_qfix_ResolveTool_nativeResolveClass(JNIEnv *env, jclass thiz, jstring referrerDescriptor, jlong classIdx) {
LOGE("enter nativeResolveClass");
void *handle = 0;
handle = dlopen("/system/lib/libdvm.so", RTLD_LAZY);
if (!handle) LOGE("dlopen libdvm.so fail");
if (!handle) return false;
const char *loadClassSymbols[3] = {
"_Z18dvmFindLoadedClassPKc", "_Z18kvmFindLoadedClassPKc", "dvmFindLoadedClass"};
for (int i = 0; i < 3; i++) {
dvmFindLoadedClass = reinterpret_cast<void *(*)(const char *)>(
dlsym(handle, loadClassSymbols[i]));
if (dvmFindLoadedClass) {
LOGE("dlsym dvmFindLoadedClass success %s", loadClassSymbols[i]);
break;
}
}
const char *resolveClassSymbols[2] = {"dvmResolveClass", "vResolveClass"};
for (int i = 0; i < 2; i++) {
dvmResolveClass = reinterpret_cast<void *(*)(const void *, unsigned int, bool)>(
dlsym(handle, resolveClassSymbols[i]));
if (dvmResolveClass) {
LOGE("dlsym dvmResolveClass success %s", resolveClassSymbols[i]);
break;
}
}
if (!dvmFindLoadedClass) LOGE("dlsym dvmFindLoadedClass fail");
if (!dvmResolveClass) LOGE("dlsym dvmResolveClass fail");
if (!dvmFindLoadedClass || !dvmResolveClass) return false;
const char *descriptorChars = (*env).GetStringUTFChars(referrerDescriptor, 0);
//referrerClassObj 即爲 com.a.Hack2
void *referrerClassObj = dvmFindLoadedClass(descriptorChars);
dvmResolveClass(referrerClassObj, classIdx, true);
return true;
}
複製代碼
到此,代碼就所有實現了。