AndFix已经使用了一段时间了,但是到AndFix上看了一下,最近2个月都没有更新代码了,有141个issues和3个pull
request没人处理,其实AndFix的Contributors就俩个人,一个是rogerAce还有个是supern
lee
,虽然快要沦为了阿里的KPI项目,但是并不妨碍AndFix在业界的地位-一个低成本快速接入的Bugxiuf第一方案。

第二方案Nuwa,Nuwa的原理是修改了gradle的编译task流程,替换dex的方式来实现。但是可惜的是gradle
plugin在1.5以后取消了predexdebug这个task,而Nuwa恰恰是依赖这个task的,所以导致Nuwa在gradle plugin1.5版本后无法使用,而且Nuwa的作者是贾吉鑫也在一次技术分享的时候也表示,不再维护Nuwa,因为他感觉AndFix已经做的足够好,他不想把AndFix做的事情再做一次。

闲话扯完,网上AndFix的教程和解析已经很多,这里就仅分享一下我在AndFix中学到的东西。

SortedSet

1
2
private final SortedSet<Patch> mPatchs;
this.mPatchs = new ConcurrentSkipListSet();

SortedSet是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的。

保证迭代器按照元素递增顺序遍历的集合,可以按照元素的自然顺序(参见 Comparable)进行排序, 或者按照创建有序集合时提供的 Comparator进行排序。要采用此排序,

ConcurrentSkipListSet(在JavaSE 6新增的)提供的功能类似于TreeSet,能够并发的访问有序的set。因为ConcurrentSkipListSet是基于“跳跃列表(skip list)”实现的,只要多个线程没有同时修改集合的同一个部分,那么在正常读、写集合的操作中不会出现竞争现象。

mPatchs是一个有序并发Set,Set中的元素都必须实现 Comparable 接口,所以Patch实现了Comparable的compareTo方法,可见Patch是按照时间从小到大顺序

1
2
3
public int compareTo(Patch another) {
    return this.mTime.compareTo(another.getTime());
}

Patch生成

首先看一下Patch是什么?解压之后(盗图)

meta-inf文件夹为:

打开patch.mf文件可以发现两个apk的差异信息:

1
2
3
4
5
6
7
Manifest-Version: 1.0
Patch-Name: app-release-fix
To-File: app-release-online.apk
Created-Time: 30 Mar 2016 06:26:27 GMT
Created-By: 1.0 (ApkPatch)
Patch-Classes: com.qianmi.shine.activity.me.AboutActivity_CF
From-File: app-release-fix.apk

AndFix是如何把文件读取出来,并且初始化Patch的尼?Android大部分的文件都是压缩包,所以这里的处理也有一定的代表性。

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
private void init() throws IOException {
      JarFile jarFile = null;
      InputStream inputStream = null;

      try {
          jarFile = new JarFile(this.mFile);
          JarEntry entry = jarFile.getJarEntry("META-INF/PATCH.MF");
          inputStream = jarFile.getInputStream(entry);
          Manifest manifest = new Manifest(inputStream);
          Attributes main = manifest.getMainAttributes();
          this.mName = main.getValue("Patch-Name");
          this.mTime = new Date(main.getValue("Created-Time"));
          this.mClassesMap = new HashMap();
          Iterator it = main.keySet().iterator();

          while(it.hasNext()) {
              Name attrName = (Name)it.next();
              String name = attrName.toString();
              if(name.endsWith("-Classes")) {
                  List strings = Arrays.asList(main.getValue(attrName).split(","));
                  if(name.equalsIgnoreCase("Patch-Classes")) {
                      this.mClassesMap.put(this.mName, strings);
                  } else {
                      this.mClassesMap.put(name.trim().substring(0, name.length() - 8), strings);
                  }
              }
          }
      } finally {
          if(jarFile != null) {
              jarFile.close();
          }

          if(inputStream != null) {
              inputStream.close();
          }

      }

  }

JarFile 类用于从任何可以使用 java.io.RandomAccessFile 打开的文件中读取 jar 文件的内容。它扩展了 java.util.zip.ZipFile 类,使之支持读取可选的 Manifest 条目。Manifest 可用于指定关于 jar 文件及其条目的元信息。

这里使用了JarFile来解压.patch文件,然后找到META-INF/PATCH.MF 然后找到Patch-Classes,这样就可以取出里面被修复的类,当然这些类都是在原来的包名+CF,当真正修复的时候执行this.mAndFixManager.fix(patch.getFile(), cl, classes)方法,里面patch.getFile()里面是dex,classes里面是指定修复的类。

fix

下面就是AndFix中最重要的一个方法了,核心的fix逻辑都在这里

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
public synchronized void fix(File file, ClassLoader classLoader, List<String> classes) {
       if(this.mSupport) {
           if(this.mSecurityChecker.verifyApk(file)) {
               try {
                   File e = new File(this.mOptDir, file.getName());
                   boolean saveFingerprint = true;
                   if(e.exists()) {
                       if(this.mSecurityChecker.verifyOpt(e)) {
                           saveFingerprint = false;
                       } else if(!e.delete()) {
                           return;
                       }
                   }

                   DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(), e.getAbsolutePath(), 0);
                 。。。
                               clazz = dexFile.loadClass(entry, classLoader);
			  。。。
                                   this.fixClass(clazz, classLoader);
                       return;
                   }
               } catch (IOException var12) {
                   Log.e("AndFixManager", "pacth", var12);
               }
           }
       }
   }

mSupport- 是否支持热更新修复,简单看了一下,就是不支持yunOS,sdk在8~23都可以。

verifyApk- 对比一下签名信息,看dex是否合法,这里面的方法也很典型,自己写库的时候也可以用。

verifyOpt- 获取file的md5值看是否改变过

DexFile.loadDex

打开一个DEX文件,并提供一个文件来保存优化过的DEX数据。如果优化过的格式已存在并且是最新的,就直接使用它。如果不是,虚拟机将试图重新创建一个。该方法主要用于应用希望在通常的应用安装机制之外下载和执行DEX文件。不能在应用里直接调用该方法,而应该通过一个类装载器例如dalvik.system.DexClassLoader.

关于动态加载可以看看扩展

接下来就是找到带MethodReplace注解的方法,这个注解是在代码对比过程自动生成的,

1
2
3
4
5
6
7
8
MethodReplace methodReplace = (MethodReplace)method.getAnnotation(MethodReplace.class);
          if(methodReplace != null) {
              String clz = methodReplace.clazz();
              String meth = methodReplace.method();
              if(!isEmpty(clz) && !isEmpty(meth)) {
                  this.replaceMethod(classLoader, clz, meth, method);
              }
          }

replaceMethod传入的有class名字,方法的名字,方法本身,这样根据meth就有找到原来app中对应的方法

1
Method src1 = clazz.getDeclaredMethod(meth, method.getParameterTypes());

一个有bug的方法,一个修复后的方法,一招乾坤大罗移,

1
AndFix.addReplaceMethod(src1, method);

为啥说是乾坤大罗移是因为AndFix在C的层面将源方法(meth)的各个属性被替换成了新的方法(target)的各个属性,就完成了方法的替换,当虚拟机误以为方法还是之前的“方法”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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->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的整个过程就结束了,可见要完成这样的库,需要会写插件,NDK,还有对类的加载过程非常了解,最关键的是,从问题入手找解决方法的过程,试着想一下,如果你有了这些技术栈,现在需要解决动态修复的问题,你会怎么做?我可能会把整个类都替换掉,因为在我感觉中,一块代码的替换好像比一个类的替换要难,但是阿里的同学做到了。实在佩服佩服,也为阿里在开源届做的贡献点赞!!!!

AndFix使用感想的更多相关文章

  1. Building Modern Web Apps-构建现代的 Web 应用程序(一些感想)

    <iframe src="http://channel9.msdn.com/Series/MVA-China/Web20140611A01/player?h=540&w=960 ...

  2. Android热修复AndFix

    热修复主要用来修复代码.修复bug.添加独立的功能,他的原理主要是操作PathClassLoader.DexClassLoader. PathClassLoader是类加载器,DexClassLoad ...

  3. 20155206赵飞技能获取经验,C语言学习感想与对JAVA的学习目标

    自己较强的技能获取经验. 1:实话实说我自己是没有哪个技能可以超过90%的人的,只有自认为做的还可以的一些事情,例如打篮球,office软件的应用,一百米跑.至于其他方面就是很平庸了. 2:经验主要有 ...

  4. C#注解属性的感想一:

    C#当中Attribute(中文注解属性)已经知道这个概念已经很久很久了,不过悲剧的是在实际项目当中重来没有用它来做过什么东西,以致对它的理解总是很浅薄,更谈不上如何在实际项目中运用它.最近在学习&l ...

  5. 完成Matrix丶Kingdom PPT后的感想

    这次Presentation是我在这节课的第一次上台演讲,让我感悟良多. 具体对我的PPT有兴趣的朋友可以call我,我会共享给大家. 这次老师布置的任务对我而言很有意义.首先,我作为最后一组,我欣赏 ...

  6. 支付宝Andfix 原理解析

    支付宝Andfix 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49802429 原理参考地址: http://blo ...

  7. 阿里资深工程师分享支付宝热补丁技术—— AndFix原理

    本文由嵌入式企鹅圈原创团队成员.阿里资深工程师Hao分享. 上次我们介绍了用dexposed方案实施热补丁的原理,它本质上就是hook要修改的函数,这样一来在正式版本发布时就不能直接拿热补丁的代码集成 ...

  8. AndFix热修复 —— 实战与源码解析

    当你的应用发布后第二天却发现一个重要的bug要修复,头疼的同时你可能想着赶紧修复重新打个包发布出去,让用户收到自动更新重新下载.但是万事皆有可能,万一隔一天又发现一个急需修复的bug呢?难道再次发布打 ...

  9. Android热修复实践应用--AndFix

    一直关注App的热修复的技术发展,之前做的应用也没用使用到什么热修复开源框架.在App的热修复框架没有流行之前,做的应用上线后发现一个小小的Bug,就要马上发一个新的版本.我亲身经历过一周发两个版本, ...

随机推荐

  1. Dynamics CRM2016 查询数据的三种方式的性能对比

    之前写过一个博客,对非声明验证方式下连接组织服务的两种方式的性能进行了对比,但当时只是对比了实例化组织服务的时间,并没有对查询数据的时间进行对比,那有朋友也在我的博客中留言了反映了查询的时间问题,一直 ...

  2. ORACLE时间日期格式使用总结(参考网上资料汇总)

    Oracle时间日期操作 sysdate+(5/24/60/60) 在系统时间基础上延迟5秒 sysdate+5/24/60 在系统时间基础上延迟5分钟 sysdate+5/24 在系统时间基础上延迟 ...

  3. Pycharm中进行Python远程开发

    http://blog.csdn.net/pipisorry/article/details/52269952 PyCharm提供两种远程调试(Remote Debugging)的方式:    配置远 ...

  4. Java并发框架——AQS中断的支持

    线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠 ...

  5. ISP(Interface Segregation Principle),接口隔离原则

    ISP(Interface Segregation Principle),接口隔离原则 它要求如下: ①  一个类对另一个类的依赖性要建立在最小接口上. ②  使用多个专门的接口比使用单一的总接口要好 ...

  6. 2.2、Android Studio通过注解提升代码检测

    使用像Lint这样的代码检测工具可以帮助你发现问题和提升代码,但是代码检测在有些地方很难应用.例如,Android的资源ID,使用一个int类型来表示字符.图像.颜色或者其他资源类型所以代码检测工具不 ...

  7. UNIX网络编程——Socket/TCP粘包、多包和少包, 断包

    为什么TCP 会粘包 前几天,调试mina的TCP通信, 第一个协议包解析正常,第二个数据包不完整.为什么会这样吗,我们用mina这样通信框架,还会出现这种问题? TCP(transport cont ...

  8. iPhone全部设备分辨率速查

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交 ...

  9. tomcat配置集群

    在Tomcat中使用集群功能相对简单.最简单的用法是直接在server.xml文件的或节点下添加 <Cluster className="org.apache.catalina.ha. ...

  10. C++对C语言的变量检测增强

    在C语言中,重复定义多个同名的全局变量是合法的 在C++中,不允许定义多个同名的全局变量 C语言中多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上 demo #include <st ...