AndFix使用感想
2016-06-16 20:21
411 查看
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是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的。
保证迭代器按照元素递增顺序遍历的集合,可以按照元素的自然顺序(参见 Comparable)进行排序, 或者按照创建有序集合时提供的 Comparator进行排序。要采用此排序,
ConcurrentSkipListSet(在JavaSE 6新增的)提供的功能类似于TreeSet,能够并发的访问有序的set。因为ConcurrentSkipListSet是基于“跳跃列表(skip list)”实现的,只要多个线程没有同时修改集合的同一个部分,那么在正常读、写集合的操作中不会出现竞争现象。
mPatchs是一个有序并发Set,Set中的元素都必须实现 Comparable 接口,所以Patch实现了Comparable的compareTo方法,可见Patch是按照时间从小到大顺序
首先看一下Patch是什么?解压之后(盗图)
meta-inf文件夹为:
打开patch.mf文件可以发现两个apk的差异信息:
AndFix是如何把文件读取出来,并且初始化Patch的尼?Android大部分的文件都是压缩包,所以这里的处理也有一定的代表性。
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里面是指定修复的类。
下面就是AndFix中最重要的一个方法了,核心的fix逻辑都在这里
mSupport- 是否支持热更新修复,简单看了一下,就是不支持yunOS,sdk在8~23都可以。
verifyApk- 对比一下签名信息,看dex是否合法,这里面的方法也很典型,自己写库的时候也可以用。
verifyOpt- 获取file的md5值看是否改变过
DexFile.loadDex
打开一个DEX文件,并提供一个文件来保存优化过的DEX数据。如果优化过的格式已存在并且是最新的,就直接使用它。如果不是,虚拟机将试图重新创建一个。该方法主要用于应用希望在通常的应用安装机制之外下载和执行DEX文件。不能在应用里直接调用该方法,而应该通过一个类装载器例如dalvik.system.DexClassLoader.
关于动态加载可以看看扩展
接下来就是找到带MethodReplace注解的方法,这个注解是在代码对比过程自动生成的,
replaceMethod传入的有class名字,方法的名字,方法本身,这样根据meth就有找到原来app中对应的方法
一个有bug的方法,一个修复后的方法,一招乾坤大罗移,
为啥说是乾坤大罗移是因为AndFix在C的层面将源方法(meth)的各个属性被替换成了新的方法(target)的各个属性,就完成了方法的替换,当虚拟机误以为方法还是之前的“方法”。
至此,AndFix的整个过程就结束了,可见要完成这样的库,需要会写插件,NDK,还有对类的加载过程非常了解,最关键的是,从问题入手找解决方法的过程,试着想一下,如果你有了这些技术栈,现在需要解决动态修复的问题,你会怎么做?我可能会把整个类都替换掉,因为在我感觉中,一块代码的替换好像比一个类的替换要难,但是阿里的同学做到了。实在佩服佩服,也为阿里在开源届做的贡献点赞!!!!
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 23 | public int compareTo(Patch another) { return this.mTime.compareTo(another.getTime()); } |
Patch生成
首先看一下Patch是什么?解压之后(盗图)meta-inf文件夹为:
打开patch.mf文件可以发现两个apk的差异信息:
1 23 | 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 |
1 23 | 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 23 | 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); } } } } |
verifyApk- 对比一下签名信息,看dex是否合法,这里面的方法也很典型,自己写库的时候也可以用。
verifyOpt- 获取file的md5值看是否改变过
DexFile.loadDex
打开一个DEX文件,并提供一个文件来保存优化过的DEX数据。如果优化过的格式已存在并且是最新的,就直接使用它。如果不是,虚拟机将试图重新创建一个。该方法主要用于应用希望在通常的应用安装机制之外下载和执行DEX文件。不能在应用里直接调用该方法,而应该通过一个类装载器例如dalvik.system.DexClassLoader.
关于动态加载可以看看扩展
接下来就是找到带MethodReplace注解的方法,这个注解是在代码对比过程自动生成的,
1 23 | 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); } } |
1 | Method src1 = clazz.getDeclaredMethod(meth, method.getParameterTypes()); |
1 | AndFix.addReplaceMethod(src1, method); |
1 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使用感想
- Deep Learning(深度学习)学习笔记整理系列之(二)
- AndFix使用感想
- 动态添加控件
- mysql远程连接
- 数据结构(Splay平衡树):HAOI2008 排名系统
- 二分+DP HDU 3433 A Task Process
- iOS常用define宏定义
- Android Sax解析XML出现空值或脏数据的解决方案
- 风靡欧洲杯的足球手环?
- LeetCode刷题系列(十)Dynamic Programming(3)补充
- Codeforces Round #357 (Div. 2) Runaway to a Shadow
- 四月
- java导出word模板
- C++面向对象中的向上转换 向下转换 以及OOP相关的类型转换
- centos的软件安装方法rpm和yum
- LeakDiag使用手记
- golang 构建web应用
- JS中的this好神奇,都把我弄晕了
- [ARM] [linux master] 调试技术001 GDB-Advanced-001 Code Dump 的调试