[关闭]
@linux1s1s 2017-01-22T16:55:28.000000Z 字数 7195 阅读 1981

Android 在线热修复框架 AndFix 初步 四

AndroidExtend 2016-03


我们接着分析阿里开源的AndFix库,上次留下了三个坑,一个方法,两个类,不知道你们是否想急切了解呢? loadPatch()方法和AndFixManager和Patch类。

分析loadPatch()方法的时候离不开AndFixManager这个类,所以,我会在分析loadPatch()方法的时候分析AndFixManager这个类。 Patch类相当于一个容器,把修复bug所需的信息放在其中,Patch类相对来说比较独立,不需要牵扯到另外两个坑,所以,就先把这个坑埋了。

要分析Patch类,就不能不分析阿里提供的打包.apatch的工具apkpatch-1.0.2.jar,Patch获取的信息其实就是apkpatch打包时放入其中的信息。 我下面对apkpatch.jar的分析以这个版本的源码为准。

分析源码,那些try-catch-finally和逻辑无关,所以,我会把这些代码从源码中删掉的,除非有提示用户的操作

我们先来看看Patch类
从前一篇的分析中,我们看到调用了Patch的构造函数,那我们就从构造函数开始看。

  1. public Patch(File file) throws IOException {
  2. this.mFile = file;
  3. this.init();
  4. }

将传入的文件保存在类变量中,并调用init()函数,那么init()函数干什么呢? 代码里异常和清理的代码我给删掉了,毕竟,这和我们对源码的分析关系不大,那么我们看一下剩下的代码

  1. public void init(){
  2. JarFile jarFile = new JarFile(this.mFile);
  3. JarEntry entry = jarFile.getJarEntry(ENTRY_NAME);
  4. InputStream inputStream = jarFile.getInputStream(entry);
  5. Manifest manifest = new Manifest(inputStream);
  6. Attributes main = manifest.getMainAttributes();
  7. this.mName = main.getValue(PATCH_NAME);
  8. this.mTime = new Date(main.getValue(CREATED_TIME));
  9. this.mClassesMap = new HashMap();
  10. Iterator it = main.keySet().iterator();
  11. while(it.hasNext()) {
  12. Name attrName = (Name)it.next();
  13. String name = attrName.toString();
  14. if(name.endsWith(CLASSES)) {
  15. List strings = Arrays.asList(main.getValue(attrName).split(","));
  16. if(name.equalsIgnoreCase(PATCH_CLASSES)) {
  17. this.mClassesMap.put(this.mName, strings);
  18. } else {
  19. this.mClassesMap.put(name.trim().substring(0, name.length() - 8), strings);
  20. }
  21. }
  22. }
  23. }

先分析上面的代码,通过JarFile, JarEntry, Manifest, Attributes一层层的获取jar文件中值,并放入对应的类变量中 这些值是从哪里来的呢?是从生成这个.apatch文件的时候写入的,即apkpatch.jar文件中写入的。 虽然这个文件后缀名是apatch,不是jar,但是它生成的时候是使用Attributes,Manifest,jarEntry将数据写入的, 其实依旧是jar格式,修改了扩展名而已

上面的那些常量都是什么呢,我们来看看这个类的类变量,就是一些对应的字符串。

  1. private static final String ENTRY_NAME = "META-INF/PATCH.MF";
  2. private static final String CLASSES = "-Classes";
  3. private static final String PATCH_CLASSES = "Patch-Classes";
  4. private static final String CREATED_TIME = "Created-Time";
  5. private static final String PATCH_NAME = "Patch-Name";
  6. private final File mFile;
  7. private String mName;
  8. private Date mTime;
  9. private Map<String, List<String>> mClassesMap;

看完了Patch类,是不是还一头雾水呢, 其他的好理解, mClassesMap里面的各种类名是从哪里来,里面的类作用又是什么呢? 这里,我们就需要进入apkpatch.jar一探究竟。这个Jar包作用就是生成patch包,这个已经在第二篇博文中详细介绍了,所以可以直接参考Android 在线热修复框架 AndFix 初步 二

到这里,Patch类基本作用就明白了,这个类相当于我们提供补丁的容器,容器里有了东西,我们要对容器进行操作了。于是我们开始填最后两个坑,即loadPatch()方法和AndFixManager类。

在阿里给的Demo里,我们还有最后的loadPatch()方法没有深入,所以先从loadPatch()方法开始。

  1. public void loadPatch() {
  2. mLoaders.put("*", mContext.getClassLoader());// wildcard
  3. Set<String> patchNames;
  4. List<String> classes;
  5. for (Patch patch : mPatchs) {
  6. patchNames = patch.getPatchNames();
  7. for (String patchName : patchNames) {
  8. classes = patch.getClasses(patchName);
  9. mAndFixManager.fix(patch.getFile(),mContext.getClassLoader(), classes);
  10. }
  11. }
  12. }

不知道大家是否还记得之前提到的mLoaders这个成员变量,隔了这么久,说实话我也忘记了,在这里我先带大家一起回忆一下, private final Map mLoaders;,mLoaders原来是储存不同ClassLoader的Map啊。 好的,我们继续向下进行,在第一篇,通过private的initPatchs()方法,和public的addPatch()方法,将Patch加入了mPatchs这个List, 所以,这里只要去遍历这个List,来获取不同的Patch,并对每个Patch做操作即可。 每个Patch,代表了一个.apatch的文件,getClasses(patchName)代表着这个patch的patchName对应的所有需要修改的类。 patchName从两方面而来,一个是你用apkpatch.jar的时候使用-n选项指定或者默认的,另外一个方面是写入Manifest的时候以 -Classes结尾的key,这个我暂时还没有遇到过,遇到过的同学可以给我讲讲,抱歉博客暂时没有评论功能,可以发邮件给我,airzhaoyn@gmail.com。

好了,这里讲的差不多了,我们继续深入。 可以看到,获取了一个patchName对应的所有的需要修改的类后,就会调用AndFixManager类的fix(File, ClassLoader,List)方法, 先来看看源码

  1. /**
  2. * fix
  3. *
  4. * @param file
  5. * patch file
  6. * @param classLoader
  7. * classloader of class that will be fixed
  8. * @param classes
  9. * classes will be fixed
  10. */
  11. public synchronized void fix(File file, ClassLoader classLoader,
  12. List<String> classes) {
  13. //是否是支持的Android版本(在AndFixManager类初始化的时候会修改mSupport变量)
  14. if (!mSupport) {
  15. return;
  16. }
  17. //验证这个文件的签名和此应用是否一致
  18. if (!mSecurityChecker.verifyApk(file)) {// security check fail
  19. return;
  20. }
  21. //这段代码的源码中的注释很清楚,我就不写了
  22. File optfile = new File(mOptDir, file.getName());
  23. boolean saveFingerprint = true;
  24. if (optfile.exists()) {
  25. // need to verify fingerprint when the optimize file exist,
  26. // prevent someone attack on jailbreak device with
  27. // Vulnerability-Parasyte.
  28. // btw:exaggerated android Vulnerability-Parasyte
  29. // http://secauo.com/Exaggerated-Android-Vulnerability-Parasyte.html
  30. if (mSecurityChecker.verifyOpt(optfile)) {
  31. saveFingerprint = false;
  32. } else if (!optfile.delete()) {
  33. return;
  34. }
  35. }
  36. //start
  37. final DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
  38. optfile.getAbsolutePath(), Context.MODE_PRIVATE);
  39. if (saveFingerprint) {
  40. mSecurityChecker.saveOptSig(optfile);
  41. }
  42. ClassLoader patchClassLoader = new ClassLoader(classLoader) {
  43. @Override
  44. protected Class<?> findClass(String className)
  45. throws ClassNotFoundException {
  46. Class<?> clazz = dexFile.loadClass(className, this);
  47. if (clazz == null
  48. && className.startsWith("com.alipay.euler.andfix")) {
  49. return Class.forName(className);// annotation’s class
  50. // not found
  51. }
  52. if (clazz == null) {
  53. throw new ClassNotFoundException(className);
  54. }
  55. return clazz;
  56. }
  57. };
  58. //Enumerate the names of the classes in this DEX file.
  59. Enumeration<String> entrys = dexFile.entries();
  60. Class<?> clazz = null;
  61. while (entrys.hasMoreElements()) {
  62. String entry = entrys.nextElement();
  63. if (classes != null && !classes.contains(entry)) {
  64. continue;// skip, not need fix
  65. }
  66. clazz = dexFile.loadClass(entry, patchClassLoader);
  67. if (clazz != null) {
  68. fixClass(clazz, classLoader);
  69. }
  70. }
  71. }

对这部分的源码解析,从源码中标注//start开始。 我们先看一下DexFile是什么,官方文档这样说:Manipulates DEX files. It is used primarily by class loaders. 就是主要被类加载器使用的操作Dex文件的类。 好了,我们可以继续看源码了,先获取一个DexFile对象的,然后通过匿名内部类实现了一个ClassLoaders的子类,遍历这个Dex文件中所有的类, 如果需要修改的类集合(即从PatchManager loadPatch()方法中传过来的类集合)中在这个Dex文件中找到了一样的类,则使用loadClass(String, ClassLoader)加载这个类, 然后调用fixClass(String, ClassLoader)修复这个类。

  1. private void fixClass(Class<?> clazz, ClassLoader classLoader) {
  2. //使用反射获取这个类中所有的方法
  3. Method[] methods = clazz.getDeclaredMethods();
  4. //MethodReplace是这个库自定义的Annotation,标记哪个方法需要被替换
  5. MethodReplace methodReplace;
  6. String clz;
  7. String meth;
  8. for (Method method : methods) {
  9. //找到被MethodReplace注解的方法
  10. methodReplace = method.getAnnotation(MethodReplace.class);
  11. if (methodReplace == null)
  12. continue;
  13. clz = methodReplace.clazz();
  14. meth = methodReplace.method();
  15. if (!isEmpty(clz) && !isEmpty(meth)) {
  16. replaceMethod(classLoader, clz, meth, method);
  17. }
  18. }
  19. }

在源码中基本把这个方法给解读完了,接下来就要看看它调用的replaceMethod(ClassLoader, String,String, Method)方法

  1. private void replaceMethod(ClassLoader classLoader, String clz,
  2. String meth, Method method) {
  3. //对每个类创建一个不会冲突的key
  4. String key = clz + "@" + classLoader.toString();
  5. //mFixedClass是一个Map<String, Class<?>>,并被实例化为ConcurrentHashMap<>();
  6. Class<?> clazz = mFixedClass.get(key);
  7. if (clazz == null) {// class not load
  8. Class<?> clzz = classLoader.loadClass(clz);
  9. // initialize target class and modify access flag of class’ fields to public
  10. clazz = AndFix.initTargetClass(clzz);
  11. }
  12. if (clazz != null) {// initialize class OK
  13. mFixedClass.put(key, clazz);
  14. //获取名为meth的方法
  15. Method src = clazz.getDeclaredMethod(meth,
  16. method.getParameterTypes());
  17. AndFix.addReplaceMethod(src, method);
  18. }
  19. }

这里源代码中的英文注释(作者注释)已经很清楚了,去看看调用的那个静态方法AndFix.addReplaceMethod(src, method);

  1. public static void addReplaceMethod(Method src, Method dest) {
  2. replaceMethod(src, dest);
  3. initFields(dest.getDeclaringClass());
  4. }

replaceMethod是一个native方法,声明如下: private static native void replaceMethod(Method dest, Method src); 由于暂时我对JNI不是很熟悉,所以这里就不分析了。 看看另外一个方法initFields(Class<?>)

  1. /**
  2. * modify access flag of class’ fields to public
  3. *
  4. * @param clazz
  5. * class
  6. */
  7. private static void initFields(Class<?> clazz) {
  8. Field[] srcFields = clazz.getDeclaredFields();
  9. for (Field srcField : srcFields) {
  10. Log.d(TAG, "modify " + clazz.getName() + "." + srcField.getName()
  11. + " flag:");
  12. setFieldFlag(srcField);
  13. }
  14. }

这里英文注释也很清楚了,只是其中调用了setFieldFlag(srcField);这个我们没见过的方法, 声明为private static native void setFieldFlag(Field field); 又是个JNI方法,暂时不进行分析。

到这里,对阿里AndFix库Java层面上的代码的分析就结束了,有兴趣的同学可以进一步了解Native层的具体实现,这里不再给出。

附注: 此文章转载并微修改自AndFix解析,特此说明。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注