博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AndFix Bug热修复框架原理及源码解析
阅读量:4069 次
发布时间:2019-05-25

本文共 20390 字,大约阅读时间需要 67 分钟。



AndFix原理

AndFix的原理就是方法的替换,把有bug的方法替换成补丁文件中的方法。 
这里写图片描述

注:在Native层使用指针替换的方式替换bug方法,已达到修复bug的目的。

使用AndFix修复热修复的整体流程:

这里写图片描述

方法替换过程:

这里写图片描述

源码解析

解析源码从使用的方法一一解析。

在自定义Application中初始化PatchManger:

PatchManager mPatchManager = new PatchManager(); 
1
1

直接实例化了一个PatchManger实例对象,接下看PatchManager类源码:

public PatchManager(Context context) {        mContext = context;        mAndFixManager = new AndFixManager(mContext);//初始化AndFixManager        mPatchDir = new File(mContext.getFilesDir(), DIR);//初始化存放patch补丁文件的文件夹        mPatchs = new ConcurrentSkipListSet
();//初始化存在Patch类的集合,此类适合大并发 mLoaders = new ConcurrentHashMap
();//初始化存放类对应的类加载器集合}
1
2
3
4
5
6
7
1
2
3
4
5
6
7

然后看AndFixManager的初始化:

public AndFixManager(Context context) {        mContext = context;        mSupport = Compat.isSupport();//判断Android机型是否适支持AndFix        if (mSupport) {            mSecurityChecker = new SecurityChecker(mContext);//初始化签名判断类            mOptDir = new File(mContext.getFilesDir(), DIR);//初始化patch文件存放的文件夹            if (!mOptDir.exists() && !mOptDir.mkdirs()) {
// make directory fail mSupport = false; Log.e(TAG, "opt dir create error."); } else if (!mOptDir.isDirectory()) {
// not directory mOptDir.delete();//如果不是文件目录就删除 mSupport = false; } }}public static synchronized boolean isSupport() {
//此处加了同步锁机制 if (isChecked) return isSupport; isChecked = true; // not support alibaba's YunOs boolean isYunOs = isYunOS();//判断系统是否是YunOs系统,YunOs系统是阿里巴巴的系统 boolean setup =AndFix.setup();//判断是Dalvik还是Art虚拟机,来注册Native方法 boolean isSupportSDKVersion = isSupportSDKVersion();//根据sdk版本判断是否支持 if (!isYunOs && setup && isSupportSDKVersion) {
//根据上面三个boolean值判断是否支持 isSupport = true; } if (inBlackList()) { isSupport = false; } return isSupport;}private static boolean isSupportSDKVersion() { if (android.os.Build.VERSION.SDK_INT >= 8 && android.os.Build.VERSION.SDK_INT <= 23) { return true; } return false;}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; }}//签名机制的初始化过程public SecurityChecker(Context context) { mContext = context; init(mContext);}//主要是获取当前应用的签名及其他信息,为了判断与patch文件的签名是否一致private void init(Context context) { try { PackageManager pm = context.getPackageManager(); String packageName = context.getPackageName(); PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); CertificateFactory certFactory = CertificateFactory .getInstance("X.509"); ByteArrayInputStream stream = new ByteArrayInputStream( packageInfo.signatures[0].toByteArray()); X509Certificate cert = (X509Certificate) certFactory .generateCertificate(stream); mDebuggable = cert.getSubjectX500Principal().equals(DEBUG_DN); mPublicKey = cert.getPublicKey(); } catch (NameNotFoundException e) { Log.e(TAG, "init", e); } catch (CertificateException e) { Log.e(TAG, "init", e); }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

接下,看一下版本的初始化:

mPatchManager.init("version") 
1
1

init方法源码:

public void init(String appVersion) {        if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {
// make directory fail Log.e(TAG, "patch dir create error."); return; } else if (!mPatchDir.isDirectory()) {
// not directory mPatchDir.delete(); return; } SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);//存储关于patch文件的信息 //根据你传入的版本号和之前的对比,做不同的处理 String ver = sp.getString(SP_VERSION, null); if (ver == null || !ver.equalsIgnoreCase(appVersion)) { cleanPatch();//删除本地patch文件 sp.edit().putString(SP_VERSION, appVersion).commit();//并把传入的版本号保存 } else { initPatchs();//初始化patch列表,把本地的patch文件加载到内存 }}private void cleanPatch() { File[] files = mPatchDir.listFiles(); for (File file : files) { mAndFixManager.removeOptFile(file);//删除所有的本地缓存patch文件 if (!FileUtil.deleteFile(file)) { Log.e(TAG, file.getName() + " delete error."); } }}private void initPatchs() { File[] files = mPatchDir.listFiles(); for (File file : files) { addPatch(file);//加载Patch文件 }}private Patch addPatch(File file) { Patch patch = null; if (file.getName().endsWith(SUFFIX)) { try { patch = new Patch(file);//实例化Patch对象 mPatchs.add(patch);//把patch实例存储到内存的集合中,在PatchManager实例化集合 } catch (IOException e) { Log.e(TAG, "addPatch", e); } } return patch;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

Patch文件的加载

public Patch(File file) throws IOException {        mFile = file;        init();}@SuppressWarnings("deprecation")private void init() throws IOException {        JarFile jarFile = null;        InputStream inputStream = null;        try {            jarFile = new JarFile(mFile);//使用JarFile读取Patch文件            JarEntry entry = jarFile.getJarEntry(ENTRY_NAME);//获取META-INF/PATCH.MF文件            inputStream = jarFile.getInputStream(entry);            Manifest manifest = new Manifest(inputStream);            Attributes main = manifest.getMainAttributes();            mName = main.getValue(PATCH_NAME);//获取PATCH.MF属性Patch-Name            mTime = new Date(main.getValue(CREATED_TIME));//获取PATCH.MF属性Created-Time            mClassesMap = new HashMap
>(); Attributes.Name attrName; String name; List
strings; for (Iterator
it = main.keySet().iterator(); it.hasNext();) { attrName = (Attributes.Name) it.next(); name = attrName.toString(); //判断name的后缀是否是-Classes,并把name对应的值加入到集合中,对应的值就是class类名的列表 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(); } }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

loadPatch方法源码

mPatchManager.loadPatch(); 
1
1

loadPatch源码:

public void loadPatch() {        mLoaders.put("*", mContext.getClassLoader());// wildcard        Set
patchNames; List
classes; for (Patch patch : mPatchs) { patchNames = patch.getPatchNames(); for (String patchName : patchNames) { classes = patch.getClasses(patchName);//获取patch对用的class类的集合List mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(), classes);//修复bug方法 } }}
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13

fix bug

public synchronized void fix(File file, ClassLoader classLoader,            List
classes) { if (!mSupport) { return; } //判断patch文件的签名 if (!mSecurityChecker.verifyApk(file)) {
// security check fail return; } try { File optfile = new File(mOptDir, file.getName()); boolean saveFingerprint = true; if (optfile.exists()) { // need to verify fingerprint when the optimize file exist, // prevent someone attack on jailbreak device with // Vulnerability-Parasyte. // btw:exaggerated android Vulnerability-Parasyte // http://secauo.com/Exaggerated-Android-Vulnerability-Parasyte.html 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 patchClassLoader = new ClassLoader(classLoader) { @Override protected Class
findClass(String className) throws ClassNotFoundException {
//重写ClasLoader的findClass方法 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
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);//获取有bug的类文件 if (clazz != null) { fixClass(clazz, classLoader);// next code } } } catch (IOException e) { Log.e(TAG, "pacth", e); }}private void fixClass(Class
clazz, ClassLoader classLoader) { Method[] methods = clazz.getDeclaredMethods(); MethodReplace methodReplace; String clz; String meth; for (Method method : methods) { //获取此方法的注解,因为有bug的方法在生成的patch的类中的方法都是有注解的,下面会给图进行展示 methodReplace = method.getAnnotation(MethodReplace.class); if (methodReplace == null) continue; clz = methodReplace.clazz();//获取注解中clazz的值 meth = methodReplace.method();//获取注解中method的值 if (!isEmpty(clz) && !isEmpty(meth)) { replaceMethod(classLoader, clz, meth, method);//next code } }}private void replaceMethod(ClassLoader classLoader, String clz, String meth, Method method) { try { String key = clz + "@" + classLoader.toString(); Class
clazz = mFixedClass.get(key);//判断此类是否被fix if (clazz == null) {
// class not load Class
clzz = classLoader.loadClass(clz); // initialize target class clazz = AndFix.initTargetClass(clzz);//初始化class } if (clazz != null) {
// initialize class OK mFixedClass.put(key, clazz); Method src = clazz.getDeclaredMethod(meth, method.getParameterTypes());//根据反射获取到有bug的类的方法(有bug的apk) AndFix.addReplaceMethod(src, method);//src是有bug的方法,method是补丁方法 } } catch (Exception e) { Log.e(TAG, "replaceMethod", e); }}public static void addReplaceMethod(Method src, Method dest) { try { replaceMethod(src, dest);//调用了native方法,next code initFields(dest.getDeclaringClass()); } catch (Throwable e) { Log.e(TAG, "addReplaceMethod", e); }}private static native void replaceMethod(Method dest, Method src);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

由于Android4.4后才用的Art虚拟机,之前的系统都是Dalvik虚拟机,因此Natice层写了2个方法,对不同的系统做不同的处理方式。

extern void dalvik_replaceMethod(JNIEnv* env, jobject src, jobject dest);//Dalvikextern void art_replaceMethod(JNIEnv* env, jobject src, jobject dest);//Art 
1
2
1
2

Dalvik replaceMethod的实现:

extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(        JNIEnv* env, jobject src, jobject dest) {    jobject clazz = env->CallObjectMethod(dest, jClassMethod);    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->jniArgInfo = 0x80000000;    meth->accessFlags |= ACC_NATIVE;//把Method的属性设置成Native方法    int argsSize = dvmComputeMethodArgsSize_fnPtr(meth);    if (!dvmIsStaticMethod(meth))        argsSize++;    meth->registersSize = meth->insSize = argsSize;    meth->insns = (void*) target;    meth->nativeFunc = dalvik_dispatcher;//把方法的实现替换成native方法} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Art replaceMethod的实现:

//不同的art系统版本不同处理也不同extern void __attribute__ ((visibility ("hidden"))) art_replaceMethod(        JNIEnv* env, jobject src, jobject dest) {    if (apilevel > 22) {        replace_6_0(env, src, dest);    } else if (apilevel > 21) {        replace_5_1(env, src, dest);    } else {        replace_5_0(env, src, dest);    }}//以5.0为例:void replace_5_0(JNIEnv* env, jobject src, jobject dest) {    art::mirror::ArtMethod* smeth =            (art::mirror::ArtMethod*) env->FromReflectedMethod(src);    art::mirror::ArtMethod* dmeth =            (art::mirror::ArtMethod*) env->FromReflectedMethod(dest);    dmeth->declaring_class_->class_loader_ =            smeth->declaring_class_->class_loader_; //for plugin classloader    dmeth->declaring_class_->clinit_thread_id_ =            smeth->declaring_class_->clinit_thread_id_;    dmeth->declaring_class_->status_ = (void *)((int)smeth->declaring_class_->status_-1);    //把一些参数的指针给补丁方法    smeth->declaring_class_ = dmeth->declaring_class_;    smeth->access_flags_ = dmeth->access_flags_;    smeth->frame_size_in_bytes_ = dmeth->frame_size_in_bytes_;    smeth->dex_cache_initialized_static_storage_ =            dmeth->dex_cache_initialized_static_storage_;    smeth->dex_cache_resolved_types_ = dmeth->dex_cache_resolved_types_;    smeth->dex_cache_resolved_methods_ = dmeth->dex_cache_resolved_methods_;    smeth->vmap_table_ = dmeth->vmap_table_;    smeth->core_spill_mask_ = dmeth->core_spill_mask_;    smeth->fp_spill_mask_ = dmeth->fp_spill_mask_;    smeth->mapping_table_ = dmeth->mapping_table_;    smeth->code_item_offset_ = dmeth->code_item_offset_;    smeth->entry_point_from_compiled_code_ =            dmeth->entry_point_from_compiled_code_;    smeth->entry_point_from_interpreter_ = dmeth->entry_point_from_interpreter_;    smeth->native_method_ = dmeth->native_method_;//把补丁方法替换掉    smeth->method_index_ = dmeth->method_index_;    smeth->method_dex_index_ = dmeth->method_dex_index_;    LOGD("replace_5_0: %d , %d", smeth->entry_point_from_compiled_code_,            dmeth->entry_point_from_compiled_code_);} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

添加Patch

mPatchManager.addPatch(path) 
1
1

源码:

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);//同loadPatch中的addPatch一样的操作        if (patch != null) {            loadPatch(patch);//加载pach,同上loadPatch        }    } 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

移除Patch

mPatchManager.removeAllPatch(); 
1
1

源码:

public void removeAllPatch() {        cleanPatch();//删除本地缓存的patch文件列表        SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,                Context.MODE_PRIVATE);        sp.edit().clear().commit();//把关于patch的数据进行清空    } 
1
2
3
4
5
6
1
2
3
4
5
6

到此源代码就解析结束。

反编译Patch dex文件代码

patch文件中.dex文件反编译后,看到源码效果如下: 
这里写图片描述 
红框标注的部分,就是补丁方法。方法的注解部分写了clazz和method的值,对应着apk包中的类名和方法名称。

转载请标明出处

欢迎加入我们的技术交流群:66756039
版权声明:本文为博主原创文章,未经博主允许不得转载。


你可能感兴趣的文章
新版本的linux如何生成xorg.conf
查看>>
xorg.conf的编写
查看>>
启用SELinux时遇到的问题
查看>>
virbr0 虚拟网卡卸载方法
查看>>
No devices detected. Fatal server error: no screens found
查看>>
新版本的linux如何生成xorg.conf
查看>>
virbr0 虚拟网卡卸载方法
查看>>
Centos 6.0_x86-64 终于成功安装官方显卡驱动
查看>>
Linux基础教程:CentOS卸载KDE桌面
查看>>
hd cdnServer 51cdn / ChinaCache / ATS / Apache Traffic Server
查看>>
project web architecture
查看>>
OS + Unix HP-UX
查看>>
OS + Unix Solaris / openSolaris
查看>>
db sql montior
查看>>
Unix + SCO UnixWare
查看>>
db db2 books
查看>>
read humor_campus
查看>>
my read_soft
查看>>
my pdfs
查看>>
framework Schedule Quartz
查看>>