超级苦工
阅读 31
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文件这样就可以达到修复的效果。

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

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

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

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

[Kotlin Tutorials 11] Kotlin和Java的双向互操作

来源: 简书 原文: [Kotlin Tutorials 11] Kotlin和Java的双向互操作 Kotlin和Java的双向互操作 Kotlin和Java是有互操作性的(Interoperabi...

Android跳转到获取应用通知权限

来源: 简书 原文: Android跳转到获取应用通知权限 1.判断是否有通知权限 官方只最低支持到API 19(4.4),低于19的只会返回true,目前暂时没有办法获取19以下的系统是否开启了某个...

Android--PathMeasure基本用法

来源: 简书 原文: Android--PathMeasure基本用法 PathMeasure是一个用来测量Path的类 构造方法 //创建一个空的PathMeasure public PathMea...

Android屏幕适配的总结

来源: 简书 原文: Android屏幕适配的总结 屏幕适配的核心:其一,就是适配的效率,即把设计图转化为App界面的过程是否高效,其二如何保证实现UI界面在不同尺寸和分辨率的手机中UI的一致性。 背...

Android热修复之-Frameworks层修复原理分析

来源: 简书 原文: Android热修复之-Frameworks层修复原理分析 说到热修复主要有两种修复方案一种是通过dex替换的方式来达到修复效果、一种是基本native层的修复。dex替换的方式...

[译文]MongoDB WiredTiger引擎调优技巧

来源: 简书 原文: [译文]MongoDB WiredTiger引擎调优技巧 MongoDB从3.0开始引入可插拔存储引擎的概念。当前,有不少存储引擎可供选择:MMAPV1、WiredTiger、M...

知道了这些 MongoDB设计技巧,提升效率50%

来源: 掘金 原文: 知道了这些 MongoDB设计技巧,提升效率50% 范式化设计还是反范式 考虑下这样的场景,我们的订单数据是这样的 商品: { &quot;_id&quot;: productI...

Mongo实时聚合千万文档数据

来源: 掘金 原文: Mongo实时聚合千万文档数据 1.前言 大数据的聚合分析在企业中非常有用,有过大数据开发经验的人都知道ES、Mongo都提供了专门的聚合方案来解决这个问题。但是大量数据的实时聚...

历时七天,史上最强MySQL优化总结,从此优化So Easy!

来源: 掘金 原文: 历时七天,史上最强MySQL优化总结,从此优化So Easy! 一、概述 1. 为什么要优化 一个应用吞吐量瓶颈往往出现在数据库的处理速度上 随着应用程序的使用,数据库数据逐渐增...

Mysql 百问系列:B+Tree 到底是什么

来源: 掘金 原文: Mysql 百问系列:B+Tree 到底是什么 前言: 以前看过许多关于B+ Tree的文章,当时看了总觉得明白了,可是没过多久就又要忘了。直到我看了掘金小册:Mysql是怎么运...

LRU算法及其优化策略——Mysql篇

来源: 掘金 原文: LRU算法及其优化策略——Mysql篇 在上一篇文章中,介绍了LRU算法在Redis之中的应用,本篇继续给各位道友介绍在Mysql的InnobDB引擎中,是如何使用LRU算法的。...

mysql order by 优化

来源: 掘金 原文: mysql order by 优化 version : 5.7, from 8.2.1.14 ORDER BY Optimization 本节描述MySQL何时可以使用索引来满足...

MYSQL-多表查询

来源: 掘金 原文: MYSQL-多表查询 首先创建数据表tb_departments create table tb_departments (dept_id INT PRIMARY KEY, de...

MySQL索引和SQL调优

来源: 掘金 原文: MySQL索引和SQL调优 [TOC] MySQL索引和SQL调优 本文有参考网上其他相关文章,本文最后有附参考的链接 MySQL索引 MySQL支持诸多存储引擎,而各种存储引擎...

iOS 底层拾遗:AutoreleasePool

来源: 掘金 原文: iOS 底层拾遗:AutoreleasePool 前言 在阳神的 黑幕背后的Autorelease 文章中已经把 AutoreleasePool 核心逻辑讲明白了,不过多是结论性...

iOS app秒开H5优化探索

来源: 掘金 原文: iOS app秒开H5优化探索 背景 为了快递迭代、更新,公司app有一大模块功能使用H5实现,但是体验比原生差,这就衍生了如何提高H5加载速度,优化体验的问题。此文,记录一下自...

iOS常用宏 定义

来源: 简书 原文: iOS常用宏 定义 iOS开发过程中,使用的一些常用宏定义 字符串是否为空 #define kStringIsEmpty(str) ([str isKindOfClass:[NS...

比较一下iOS中的三种定时器

来源: 掘金 原文: 比较一下iOS中的三种定时器 NSTimer NSTimer是iOS开发中的最常见的定时器。 Timers work in conjunction with run loops....