超级苦工
阅读 156
Android热修复之-Frameworks层修复原理分析

说到热修复主要有两种修复方案一种是通过dex替换的方式来达到修复效果、一种是基本native层的修复。dex替换的方式来修复主要的微信团队的Tinker方案,native层修复有阿里的Andfix方案。其中Andfix是实时修复的,而Tinker方案则需要冷启动才能修复。

Native层修复

native层的修复主要是通过替换Method结构体的字段来实现修复,但是国内很多手机厂商都会修改开源代码,这就造成有在部分手机的修复会失效。

Frameworks层修复

frameworks层修复主要是通过替换DexPathList类中的Elemets数组实现修复,这种修复方式会比native层的修复会稳定,一般手机厂商也不会去修改这部分源码,唯一的缺点就是不能做到native层的实现时修复的效果。

Framework层修复原理分析

通过在MainActivity中打印getClassLoder.toString(),可以看到android的类加载器是PathClassLoader这个类,通过查看源码可以看到这个类只有一个构造方法就没有其它的方法了。

package dalvik.system;


public class PathClassLoader extends BaseDexClassLoader {
  PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

DexClassLoader是BaseDexClassLoader的子类,具体的实现都在它的父类中,如果感兴趣的也可以通过查看App的启动流程来分析,具体在ActivityThread.java中的performLaunchActivity方法中开始跟下去就可以看到有一个ClassLoderFactory中看到PathClassLoader的实例过程

    /**
     * Same as {@code createClassLoader} below, except that no associated namespace
     * is created.
     */
    public static ClassLoader createClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, String classloaderName) {
        if (isPathClassLoaderName(classloaderName)) {
            return new PathClassLoader(dexPath, librarySearchPath, parent);
        } else if (isDelegateLastClassLoaderName(classloaderName)) {
            return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
        }

        throw new AssertionError("Invalid classLoaderName: " + classloaderName);
    }

平时使用ClassLoader时通常都是调用loadClasss方法进行dex的加载,所以通过这个方法去查找里面的实现过程

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }

从源码中可以看到loadClasss的方法调用了findLoadClass方法进行加载。刚才已经知道了系统的类加载器是PathClassLoader,PathClassLoader中只有构造方法,没有其它的方法所以得到BaseDexClassLoader中查看findClass的具体实现。

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }

源码中可以看到BaseDexClassLoader中的findClass又调用了pathList中的findClass方法,这个pathList的原型是DexPathList。

  private final DexPathList pathList;

具体的实现都在DexPathList里面

    public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

从上面可以看到是遍历一个dexElements的数组来查找对应的dex文件

  /**
     * List of dex/resource (class path) elements.
     * Should be called pathElements, but the Facebook app uses reflection
     * to modify 'dexElements' (http://b/7726934).
     */
    private Element[] dexElements;

这个dexElements是在DexPathList里面的一个叫makeDexElements方法创建出来的

/**
     * Makes an array of dex/resource path elements, one per element of
     * the given array.
     */
    private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader) {
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;
      /*
       * Open all files and load the (direct or contained) dex files up front.
       */
      for (File file : files) {
          if (file.isDirectory()) {
              // We support directories for looking up resources. Looking up resources in
              // directories is useful for running libcore tests.
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) {
              String name = file.getName();

              if (name.endsWith(DEX_SUFFIX)) {
                  // Raw dex file (not inside a zip/jar).
                  try {
                      DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      if (dex != null) {
                          elements[elementsPos++] = new Element(dex, null);
                      }
                  } catch (IOException suppressed) {
                      System.logE("Unable to load dex file: " + file, suppressed);
                      suppressedExceptions.add(suppressed);
                  }
              } else {
                  DexFile dex = null;
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                  } catch (IOException suppressed) {
                      /*
                       * IOException might get thrown "legitimately" by the DexFile constructor if
                       * the zip file turns out to be resource-only (that is, no classes.dex file
                       * in it).
                       * Let dex == null and hang on to the exception to add to the tea-leaves for
                       * when findClass returns null.
                       */
                      suppressedExceptions.add(suppressed);
                  }

                  if (dex == null) {
                      elements[elementsPos++] = new Element(file);
                  } else {
                      elements[elementsPos++] = new Element(dex, file);
                  }
              }
          } else {
              System.logW("ClassLoader referenced unknown path: " + file);
          }
      }
      if (elementsPos != elements.length) {
          elements = Arrays.copyOf(elements, elementsPos);
      }
      return elements;
    }

通过上面的分析,可以知道一个apk的dex文件都是存放在DexPathList里面的一个dexElements数组里面让系统使用。apk在打包时可以使用分包技术把class.dex分成若干个,这样在apk有bug的时候可以通过替换掉有bug有dex文件这样就可以达到修复的效果。

关注下面的标签,发现更多相似文章
评论
相关推荐
mark

sdsdsdsd...

1231213213112313

123123...

优秀的Node.js第三方库推荐

axiosAxios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和 node.js 中 cheeriocheerio是一个nodejs实现的类似jquery核心功能的一个模块,用它可...

测试呀

广泛大使馆管道施工幅度顺丰大概岁的法国当时法国对```` 范德萨手动阀是的 范德萨士大夫是的****...

1234123412

我靠,怎么删除鸭?...

test

我们的共和国##...

test

i...

BookStack v2.6 发布,功能类似 GitBook 和看云的在线文档管理系统

程序介绍BookStack,分享知识,共享智慧!知识,因分享,传承久远! BookStack,基于 Mindoc、使用Go语言的Beego框架开发的功能类似GitBook和看云的在线文档管理系统,拥有...

联合索引在B+树上的存储结构及数据查找方式

来源: 掘金 原文: 联合索引在B+树上的存储结构及数据查找方式 能坚持别人不能坚持的,才能拥有别人未曾拥有的。 关注编程大道公众号,让我们一同坚持心中所想,一起成长!! 引言 上一篇文章《MySQL...

这是标题

正文在这里...

从0到1实现Promise

来源: SegmentFault 原文: 从0到1实现Promise 前言 Promise大家一定都不陌生了,JavaScript异步流程从最初的Callback,到Promise,到Generato...

测试标题

测试标题测试标题测试标题...

111

222大啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊...

好看

...

生产环境怎么部署呀

另外,貌似界面不支持移动端...

基于Redis的推荐系统开发

原文: 基于Redis的推荐系统开发 介绍推荐系统并不总是需要用到复杂的机器学习技术.只要手头上有足够的数据,你就可以花很少的功夫开发一个推荐系统.一个最简单的推荐系统可以只是从用户感兴趣的表中查找所...

MongoDB性能优化之准备测试数据

很多时候,我们本地的代码运行的好好的,上线后,刚开始没问题,运行一段时间后就有性能问题了,这是为什么呢? 出现这种情况,往往是本地测试数据量不够,上线后,刚开始,数据量很少,也不会有问题,运行一段时间...

MySQL性能优化之准备测试数据

很多时候,我们本地的代码运行的好好的,上线后,刚开始没问题,运行一段时间后就有性能问题了,这是为什么呢? 出现这种情况,往往是本地测试数据量不够,上线后,刚开始,数据量很少,也不会有问题,运行一段时间...

Android 使用 HTTPS

来源: 简书 原文: Android 使用 HTTPS 如果你的项目的网络框架是okhttp,那么使用https还是挺简单的,因为okhttp默认支持HTTPS。传送门 Android 使用 HTTP...

Android 适配一篇就够 - 编译版本?support?API 兼容?图片适配?

来源: 简书 原文: Android 适配一篇就够 - 编译版本?support?API 兼容?图片适配? 本文介绍 Android 不同系统及图片资源的常见适配问题。 compileSdkVersi...