Android NDK開發之旅36 NDK 熱修復 AndFix的基本使用以及C C++源碼級分析

###前言java

熱修復也叫熱更新,又叫作動態加載、動態修復、動態更新,是指不經過從新安裝新的APK安裝包的狀況下修復一些線上的BUG。android

經過這樣作,能夠免去發版、安裝、從新打開等過程,就能夠修復線上的BUG,防止用戶流失。所以這是幾乎每個APP都須要的一個功能,所以頗有學習的必要。git

須要注意的是:熱修復只是臨時的亡羊補牢。在企業中真正的熱修復發版與正式版同樣,須要測試進行測試。可是熱修復也存在一些兼容性問題。所以高質量的版本與熱修復框架纔是解決問題的最好的手段。github

###各大熱修復框架的對比圖api

目前流行的熱修復技術有:數組

  • QQ空間的超級補丁方案
  • 微信的Tinker
  • 阿里巴巴的AndFix、Dexposed
  • 美團的Robust
  • 餓了麼的MiGo
  • 百度的HotFix

下面給出一些常見的熱修復框架的對比圖:服務器

熱修復框架對比.png

能夠看出幾乎每個框架都有優點和弊端。其中「即時生效」的意思就是是否能不經過重啓來達到修復的效果,就像AndFix,支持即時生效,可是隻能作到方法的替換,而不是替換(新增)類、資源等。選擇什麼框架,仍是須要根據APP或者BUG的實際狀況出發。微信

關於更多的熱修復框架的對比,能夠參考一些網上的文章。今天咱們主要分析的是阿里巴巴的AndFix。框架

###技術選型ide

既然有這麼多的技術,那麼咱們必將面臨技術選型的問題,所以這裏給出一些技術選型上的參考標準:

  • 框架是否符合項目的需求,需求是衡量一切的標準
  • 可以知足需求的前提下,選擇學習成本最低的(同時也表明着使用簡單、維護起來簡單)
  • 學習成本差很少的狀況下,優先選擇大公司的框架

###AndFix的基本使用

####AndFix的引入

首先咱們在gradle腳本中添加AndFix的依賴:

compile 'com.alipay.euler:andfix:0.5.0@aar'
複製代碼

因爲熱修復須要讀寫SD卡,所以須要添加一些權限,注意6.0的權限適配問題。若是你的補丁文件是從服務器下載的,那麼就須要聯網權限。

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
複製代碼

####初始化

自定義一個Application,初始化AndFix,這裏爲了方便演示,在加載Patch文件的時候,咱們省略了校驗的步驟:

public class App extends Application {

    private static final String TAG = App.class.getSimpleName();

	//Patch文件的路徑
    private static final String APATCH_PATH = "/out.apatch";

    @Override
    public void onCreate() {
        super.onCreate();

        //初始化PatchManager
        PatchManager mPatchManager = new PatchManager(this);
        mPatchManager.init("1.0");

        //加載已加載過的Patch文件
        mPatchManager.loadPatch();
        //添加外部Patch文件
        try {
            // .apatch file path
            String patchFileString = Environment.getExternalStorageDirectory().getAbsolutePath() + APATCH_PATH;
            mPatchManager.addPatch(patchFileString);
            Log.d(TAG, "加載成功:" + patchFileString);
        } catch (Exception e) {
            Log.e(TAG, "", e);
        }
    }
}
複製代碼

####AndFix的示例

而後咱們寫一個有BUG的Test類:

public class Test {

    public static int test() {
        return 1 / 0;
    }

}
複製代碼

在Activity中調用這個類:

findViewById(R.id.btn_test).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String res = Test.test() + "";
        Toast.makeText(MainActivity.this, res, Toast.LENGTH_SHORT).show();
    }
});
複製代碼

把當前的代碼簽名打包成一個APK,咱們修更名字問old.apk。

而後把Test的BUG修改,再次簽名打包成一個APK,咱們修更名字問new.apk。

public class Test {

    public static int test() {
        return 1 / 1;
    }

}
複製代碼

####下載生成Patch文件的工具、生成Patch文件

而後咱們去AndFix的官網下載生成Patch文件的工具:

//Windows電腦用
apkpatch.bat
//Linux、蘋果電腦用
apkpatch.sh
複製代碼

而後咱們把剛剛生產的兩個APK文件、簽名文件放到同一個目錄。因爲筆者使用的是Ubuntu系統,所以須要給apkpatch.sh添加執行的權限,Ubuntu下,簽名文件的格式是jks。

而後執行下面的命令,生產Patch文件:

./apkpatch.sh -f new.apk -t old.apk -o out -k nan.jks -p 123456 -a nan -e 123456
複製代碼

在命令裏面咱們執行了新舊兩個APK文件,輸出路徑,簽名文件,簽名密碼,簽名文件的別名以及密碼。

最終咱們輸出一個文件:

new-e726f4396cbed42d1cf50fb2d781c9d9.apatch
複製代碼

咱們修更名字爲out.apatch,放到手機的SD卡根目錄下面。

####效果

一開始若是沒有放入out.apatch的時候,APP運行的時候是直接由於BUG而崩潰的,可是放入了out.apatch以後,APP的BUG被修復了。

###AndFix源碼分析

####準備步驟

爲了更方便地查看、分析AndFix的源碼,咱們將源碼導入Android Studio中,因爲AndFix源碼是使用ndk-build進行構建的,爲了更加方便導入,筆者根據Android.mk編寫了一個CMake腳本,供讀者參考:

#配置CMake的最低版本
cmake_minimum_required(VERSION 3.4.1)

#配置工程路徑
#set(path_project /home/wuhuannan/AndroidPrj/AndFix)
set(path_project D:/AndroidStudio/AndFix)
#JNI文件夾的路徑
set(path_jni ${path_project}/jni)

#配置頭文件的包含路徑
include_directories(${path_project}/jni/art)
include_directories(${path_project}/jni/dalvik)
include_directories(${path_project}/jni)

#添加本身寫的庫
add_library(andfix
            SHARED
            ${path_jni}/andfix.cpp ${path_jni}/art/art_method_replace.cpp ${path_jni}/art/art_method_replace_4_4.cpp ${path_jni}/art/art_method_replace_5_0.cpp ${path_jni}/art/art_method_replace_5_1.cpp ${path_jni}/art/art_method_replace_6_0.cpp ${path_jni}/art/art_method_replace_7_0.cpp ${path_jni}/dalvik/dalvik_method_replace.cpp
            )

#找到Android的log庫(這個庫已經在Android平臺中了)
find_library(
            log-lib
            log
            )

#找到Android的的庫(這個庫已經在Android平臺中了),這個庫貌似用不上,姑且先加上吧
find_library(
            android-lib
            android
            )

#把須要的庫連接到本身的庫中
target_link_libraries(
            andfix
            ${log-lib}
            ${android-lib}
            )
複製代碼

####1、Patch文件分析

官網給出的AndFix核心原理以下圖所示:

AndFix核心原理.png

主要是經過Native層的Hook技術,實現方法的動態替換,而替換哪一個方法,就須要根據Patch文件中的@MethodReplace註解而決定。

從上面的例子中咱們能夠直觀地看到,咱們的BUG是經過加載一個Patch文件來修復,那麼咱們就從這個Patch文件做爲咱們源碼分析的入口吧。

Patch文件實際上一個zip壓縮的文件,所以咱們不妨把它的後綴名改成zip,而後用解壓縮工具打開。能夠看到,

Patch文件解壓.png

咱們能夠看到解壓出來的是一個MeTA-INF文件夾以及dex文件。其中MeTA-INF文件夾裏面的PATCH.MF文件保存的是這個Patch的一些關鍵信息。等下咱們分析源碼的時候須要用到。

再者就是下面的這個dex文件,咱們不妨利用dex2jar-2.0工具對其進行反編譯,Linux中的命令以下:

./d2j-dex2jar.sh -f -o out.jar classes.dex
複製代碼

反編譯出來之後咱們咱們再次解壓jar包,直接拖動AS中打開:

package com.nan.andfixdemo;

import com.alipay.euler.andfix.annotation.MethodReplace;

public class Test_CF {
    public Test_CF() {
    }

    @MethodReplace(
        clazz = "com.nan.andfixdemo.Test",
        method = "test"
    )
    public static int test() {
        return 1;
    }
}
複製代碼

能夠看到,其實這個dex文件就把咱們須要替換的方法添加了一個@MethodReplace註解。

####2、AndFix中Java層核心類分析

AndFix裏面有幾個核心的類,其中包括:

表明補丁文件的類:Patch
補丁文件的管理:PatchManager
修復的類:AndFixManager
與C/C++層交互的類:AndFix
複製代碼

一個Patch文件實質上是對應一個Patch對象的:

public class Patch implements Comparable {

//省略一些代碼
private final File mFile;
private String mName;
private Date mTime;
private Map<String, List<String>> mClassesMap;

public Patch(File file) throws IOException {
	mFile = file;
	init();
}

@SuppressWarnings("deprecation")
private void init() throws IOException {
	//省略一些代碼
}

public String getName() {
	return mName;
}

public File getFile() {
	return mFile;
}

public Set<String> getPatchNames() {
	return mClassesMap.keySet();
}

public List<String> getClasses(String patchName) {
	return mClassesMap.get(patchName);
}

public Date getTime() {
	return mTime;
}

@Override
public int compareTo(Patch another) {
	return mTime.compareTo(another.getTime());
}
複製代碼

}

從上面的構造方法中能夠看出,AndFix在建立Patch對象的時候,調用了inti方法:

private void init() throws IOException {
	JarFile jarFile = null;
	InputStream inputStream = null;
	try {
		jarFile = new JarFile(mFile);
		JarEntry entry = jarFile.getJarEntry(ENTRY_NAME);
		inputStream = jarFile.getInputStream(entry);
		Manifest manifest = new Manifest(inputStream);
		Attributes main = manifest.getMainAttributes();
		mName = main.getValue(PATCH_NAME);
		mTime = new Date(main.getValue(CREATED_TIME));

		mClassesMap = new HashMap<String, List<String>>();
		Attributes.Name attrName;
		String name;
		List<String> strings;
		for (Iterator<?> it = main.keySet().iterator(); it.hasNext();) {
			attrName = (Attributes.Name) it.next();
			name = attrName.toString();
			if (name.endsWith(CLASSES)) {
				strings = Arrays.asList(main.getValue(attrName).split(","));
				if (name.equalsIgnoreCase(PATCH_CLASSES)) {
					mClassesMap.put(mName, strings);
				} else {
					mClassesMap.put(
							name.trim().substring(0, name.length() - 8),// remove
																		// "-Classes"
							strings);
				}
			}
		}
	} finally {
		if (jarFile != null) {
			jarFile.close();
		}
		if (inputStream != null) {
			inputStream.close();
		}
	}

}
複製代碼

能夠看出,在Patch構造的時候,調用了Java提供的一些讀取jar文件的API去讀取Patch文件。主要就是PATCH.MF文件這個文件:

Manifest-Version: 1.0
Patch-Name: new
Created-Time: 20 Apr 2017 02:45:20 GMT
From-File: new.apk
To-File: old.apk
Patch-Classes: com.nan.andfixdemo.Test_CF
Created-By: 1.0 (ApkPatch)
複製代碼

例如讀取了Patch名、建立日期等,其中最核心的就是讀取Patch-Classes,這就是須要修復的類的全名:

if (name.equalsIgnoreCase(PATCH_CLASSES)) {
    mClassesMap.put(mName, strings);
} else {
    mClassesMap.put(
            name.trim().substring(0, name.length() - 8),// remove
                                                        // "-Classes"
            strings);
}
複製代碼

由於這個Patch-Classes有可能會叫Classes,所以這裏須要分兩種狀況處理。

上面就分析了Patch對象的構建,最終經過getClasses()方法就能夠獲得須要修復的全部的類。

####3、AndFix中Java層基本修復流程分析

咱們回到自定義的Application中:

//初始化PatchManager
PatchManager mPatchManager = new PatchManager(this);
mPatchManager.init("1.0");

//加載已加載過的Patch文件
mPatchManager.loadPatch();
//添加外部Patch文件
try {
    // .apatch file path
    String patchFileString = Environment.getExternalStorageDirectory().getAbsolutePath() + APATCH_PATH;
    mPatchManager.addPatch(patchFileString);
    Log.d(TAG, "加載成功:" + patchFileString);
} catch (Exception e) {
    Log.e(TAG, "", e);
}
複製代碼

一開始咱們初始化了AndFix,而後調用loadPatch方法進行補丁的加載:

public void loadPatch() {
	mLoaders.put("*", mContext.getClassLoader());// wildcard
	Set<String> patchNames;
	List<String> classes;
	for (Patch patch : mPatchs) {
		patchNames = patch.getPatchNames();
		for (String patchName : patchNames) {
			classes = patch.getClasses(patchName);
			mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),
					classes);
		}
	}
}
複製代碼

在loadPatch方法方法中,先拿到Map中的ClassLoader,最後經過AndFixManager的fix方法進行修復。

因爲AndFix在加載Patch以後,會將當前的Patch保存起來,下次將再也不加載。那麼咱們的Patch一開始實際上是從下面這段代碼加載的:

try {
    // .apatch file path
    String patchFileString = Environment.getExternalStorageDirectory().getAbsolutePath() + APATCH_PATH;
    mPatchManager.addPatch(patchFileString);
    Log.d(TAG, "加載成功:" + patchFileString);
} catch (Exception e) {
    Log.e(TAG, "", e);
}
複製代碼

核心邏輯就是經過PatchManager的addPatch方法進行加載:

public void addPatch(String path) throws IOException {
	File src = new File(path);
	File dest = new File(mPatchDir, src.getName());
	if(!src.exists()){
		throw new FileNotFoundException(path);
	}
	if (dest.exists()) {
		Log.d(TAG, "patch [" + path + "] has be loaded.");
		return;
	}
	FileUtil.copyFile(src, dest);// copy to patch's directory
	Patch patch = addPatch(dest);
	if (patch != null) {
		loadPatch(patch);
	}
}
複製代碼

咱們能夠看到,AndFix是把咱們添加進來的Patch文件進行了copy,放到一個特定的目錄中去的,下次就不會再加載了。所以咱們在使用AndFix的時候,若是再次修復BUG的時候,就須要修改Patch文件的名字了,不然將不會再次加載,這是一個隱藏的大坑啊!不過這樣作的好處就是省去了每次重複加載的工做,提升了APP的性能。

最後也是調用loadPatch方法進行加載以及fix。

####4、AndFix中Java層修復流程分析

從上面的分析咱們知道,Java層最終會調用AndFixManager的fix方法進行修復(方法替換)。那麼咱們不妨先進來看一看究竟:

public synchronized void fix(File file, ClassLoader classLoader,
		List<String> classes) {

	//判斷AndFix是否可用,主要是判斷是否正確初始化了
	if (!mSupport) {
		return;
	}

	//驗證Patch文件(MD5驗證,相關代碼請自行分析)
	if (!mSecurityChecker.verifyApk(file)) {// security check fail
		return;
	}

	try {
		File optfile = new File(mOptDir, file.getName());
		boolean saveFingerprint = true;
		if (optfile.exists()) {
			if (mSecurityChecker.verifyOpt(optfile)) {
				saveFingerprint = false;
			} else if (!optfile.delete()) {
				return;
			}
		}

		//加載Patch文件中的dex文件
		final DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
				optfile.getAbsolutePath(), Context.MODE_PRIVATE);

		if (saveFingerprint) {
			mSecurityChecker.saveOptSig(optfile);
		}

		//自定義一個ClassLoader去加載須要修復的類
		ClassLoader patchClassLoader = new ClassLoader(classLoader) {
			@Override
			protected Class<?> findClass(String className)
					throws ClassNotFoundException {
				Class<?> clazz = dexFile.loadClass(className, this);
				if (clazz == null
						&& className.startsWith("com.alipay.euler.andfix")) {
					return Class.forName(className);// annotation’s class
													// not found
				}
				if (clazz == null) {
					throw new ClassNotFoundException(className);
				}
				return clazz;
			}
		};

        //查找相應的修復註解,若是找到,就進行修復
		Enumeration<String> entrys = dexFile.entries();
		Class<?> clazz = null;
		while (entrys.hasMoreElements()) {
			String entry = entrys.nextElement();
			if (classes != null && !classes.contains(entry)) {
				continue;// skip, not need fix
			}
			clazz = dexFile.loadClass(entry, patchClassLoader);
			if (clazz != null) {
				fixClass(clazz, classLoader);
			}
		}
	} catch (IOException e) {
		Log.e(TAG, "pacth", e);
	}
}
複製代碼

在AndFixManager的fix方法中,一開始進行了一些是否初始化的驗證、Patch文件的驗證,而後就加載Patch包中的dex文件,生成一個DexFile對象。

而後經過自定義的類加載器加載DexFile對象中的類。這裏因爲是加載咱們第三方的class,所以須要自定義一個類加載器。加載成功之後,就經過反射的方式循環去讀方法上面的註解,若是找到了註解,就進行修復。

下面繼續看fixClass方法,這裏就是經過循環找到MethodReplace註解,而後調用replaceMethod進行方法替換。(AndFix熱修復實質就是方法的替換)

private void fixClass(Class<?> clazz, ClassLoader classLoader) {
	Method[] methods = clazz.getDeclaredMethods();
	MethodReplace methodReplace;
	String clz;
	String meth;
	for (Method method : methods) {
		methodReplace = method.getAnnotation(MethodReplace.class);
		if (methodReplace == null)
			continue;
		clz = methodReplace.clazz();
		meth = methodReplace.method();
		if (!isEmpty(clz) && !isEmpty(meth)) {
        	//方法替換,參數分別是:類加載器、須要修復的類、修復好的方法、被修復的方法
			replaceMethod(classLoader, clz, meth, method);
		}
	}
}
複製代碼

MethodReplace註解的定義以下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodReplace {
    String clazz();

    String method();
}
複製代碼

能夠看到,這是一個運行時的註解,只可以使用在方法上面。註解中指定了類名以及方法名。

經過分析方法的調用鏈,replaceMethod方法最終會調用AndFix的靜態方法replaceMethod:

private static native void replaceMethod(Method dest, Method src);
複製代碼

能夠看到這是一個native方法。

####5、AndFix中C/C++層修復流程分析

咱們找到andfix.cpp,找到了replaceMethod的實現:

static void replaceMethod(JNIEnv* env, jclass clazz, jobject src, jobject dest) {
    if (isArt) {
        art_replaceMethod(env, src, dest);
    } else {
        dalvik_replaceMethod(env, src, dest);
    }
}
複製代碼

這裏須要判斷當前的虛擬機類型是dalvik仍是art。在JNI初始化的時候,須要註冊虛擬機(方法替換的實質就是經過Hook虛擬機層的一些流程實現的,下面將會介紹到):

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);

    if (!registerNatives(env)) { //註冊
        return -1;
    }
    
	//須要返回JNI 1.4以上的版本
    result = JNI_VERSION_1_4;

    return result;
}
複製代碼

其中註冊虛擬機的時候,調用了registerNatives,最終調用registerNativeMethods方法,進行native方法的連接(註冊與一些與類關聯的native方法,向虛擬機進行登記,這是JNI實現的另一種方式,具體能夠參考JNI相關文檔,這裏再也不深刻):

static int registerNatives(JNIEnv* env) {
    if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,
            sizeof(gMethods) / sizeof(gMethods[0])))
        return JNI_FALSE;

    return JNI_TRUE;
}
複製代碼

這裏傳入的gMethods數組以下:

static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "setup", "(ZI)Z", (void*) setup }, { "replaceMethod",
        "(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V",
        (void*) replaceMethod }, { "setFieldFlag",
        "(Ljava/lang/reflect/Field;)V", (void*) setFieldFlag }, };
複製代碼

gMethods實質就是Andfix中的一些方法:setup、replaceMethod、setFieldFlag,會在JNI加載的時候調用,進行初始化,下面主要看setup方法中的Java實現:

public static boolean setup() {
	try {
		final String vmVersion = System.getProperty("java.vm.version");
		boolean isArt = vmVersion != null && vmVersion.startsWith("2");
		int apilevel = Build.VERSION.SDK_INT;
		return setup(isArt, apilevel);
	} catch (Exception e) {
		Log.e(TAG, "setup", e);
		return false;
	}
}
複製代碼

在Java代碼中,判斷了當前虛擬機類型是dalvik仍是art,獲取了API的等級。最終又調用了native層的setup方法,下面來看native層setup方法的實現:

static jboolean setup(JNIEnv* env, jclass clazz, jboolean isart,
        jint apilevel) {
    isArt = isart;
    LOGD("vm is: %s , apilevel is: %i", (isArt ? "art" : "dalvik"),
            (int )apilevel);
    if (isArt) {
        return art_setup(env, (int) apilevel);
    } else {
        return dalvik_setup(env, (int) apilevel);
    }
}
複製代碼

具體的虛擬機註冊比較複雜,爲了簡單起見,咱們只分析一下dalvik虛擬機的初始化:

extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup(
		JNIEnv* env, int apilevel) {
	//經過dlopen(該方法在系統頭文件dlfcn.h中)加載libdvm.so
	void* dvm_hand = dlopen("libdvm.so", RTLD_NOW);
	//進行Hook
	if (dvm_hand) {
		dvmDecodeIndirectRef_fnPtr = dvm_dlsym(dvm_hand,
				apilevel > 10 ?
						"_Z20dvmDecodeIndirectRefP6ThreadP8_jobject" :
						"dvmDecodeIndirectRef");
		if (!dvmDecodeIndirectRef_fnPtr) {
			return JNI_FALSE;
		}
		dvmThreadSelf_fnPtr = dvm_dlsym(dvm_hand,
				apilevel > 10 ? "_Z13dvmThreadSelfv" : "dvmThreadSelf");
		if (!dvmThreadSelf_fnPtr) {
			return JNI_FALSE;
		}
		jclass clazz = env->FindClass("java/lang/reflect/Method");
		jClassMethod = env->GetMethodID(clazz, "getDeclaringClass",
						"()Ljava/lang/Class;");

		return JNI_TRUE;
	} else {
		return JNI_FALSE;
	}
}
複製代碼

先來補充一些基本知識:

Android虛擬機初始化.png

  1. 如上圖所示,Android虛擬機是有別於Java原生的虛擬機的,它執行的是dex文件而不是class文件。Android虛擬機分爲dalvik虛擬機和art虛擬機。
  2. 虛擬機(進程)啓動的時候會加載一個很重要的動態庫文件(libdalvik.so或者libart.so)。
  3. Java在虛擬機環境中執行,每一個Java方法都會對應一個底層的函數指針,當Java方法被調用的時候,實質虛擬機會找到這個函數指針而後去執行底層的方法,從而Java方法被執行。

咱們回到虛擬機初始化的分析中來,dalvik_setup方法主要作了兩個步驟:

  1. 經過調用dlopen(該方法在系統頭文件dlfcn.h中)加載libdvm.so(這個so在APP進程初始化的時候會加載),這個加載是爲了下一步的Hook作準備。
  2. 加載完libdvm.so以後,就能夠進行Hook了。在API10以上、如下,Java方法調用的時候會執行不一樣的底層的系統函數,所以必須Hook不一樣的系統函數纔會有效。Hook成功之後,在這些系統函數調用的時候,就會調用咱們本身的代碼,進行替換。

咱們在loadPatch的時候,最終會調用AndFixManager的fix方法,根據一系列的調用鏈,最終會調用dalvik_replaceMethod或者art_replaceMethod。下面繼續以dalvik虛擬機爲例,繼續來看dalvik_replaceMethod方法的實現:

extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(
		JNIEnv* env, jobject src, jobject dest) {
	jobject clazz = env->CallObjectMethod(dest, jClassMethod);

	//咱們能夠看到剛剛咱們Hook成功的兩個函數
	ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(dvmThreadSelf_fnPtr(), clazz);
	clz->status = CLASS_INITIALIZED;

	//進行方法替換
	Method* meth = (Method*) env->FromReflectedMethod(src);
	Method* target = (Method*) env->FromReflectedMethod(dest);
	LOGD("dalvikMethod: %s", meth->name);

//	meth->clazz = target->clazz;
	meth->accessFlags |= ACC_PUBLIC;
	meth->methodIndex = target->methodIndex;
	meth->jniArgInfo = target->jniArgInfo;
	meth->registersSize = target->registersSize;
	meth->outsSize = target->outsSize;
	meth->insSize = target->insSize;

	meth->prototype = target->prototype;
	meth->insns = target->insns;
	meth->nativeFunc = target->nativeFunc;
}
複製代碼

這就是AndFix的核心代碼了,當BUG方法被底層系統函數調用的時候,咱們的Hook的鉤子函數就會調用,而後就是進行有BUG與無BUG的Java方法的全部成員的變量替換,達到一個狸貓換太子的目的。

總結一句話就是,經過Hook系統的底層函數,在咱們有BUG的Java方法被調用的時候,經過一句「刀下留人」,而後狸貓換太子同樣,調用咱們已經修復好的方法。

###結束語

關於AndFix熱修復的使用與分析就到這裏了,有一些東西可能解析得不是特別清楚,畢竟這些玩意仍是很是深刻的,對於咱們通常的開發者來講,會使用一些常見的熱修復框架便可,無需太過深刻。深刻分析源碼一般來講只是爲了咱們更好去使用而已。

若是以爲個人文字對你有所幫助的話,歡迎關注個人公衆號:

公衆號:Android開發進階

個人羣歡迎你們進來探討各類技術與非技術的話題,有興趣的朋友們加我私人微信huannan88,我拉你進羣交(♂)流(♀)

相關文章
相關標籤/搜索