插件化篇 - 热修复原理分析

Android 热修复技术主要可以分为两类:
一类是利用 Java hook 的技术来替换要修复的方法。代表有阿里的 DeXposed、Andfix。
一类是利用 Java 类加载机制优先返回修复的类。代表有 Tinker、HotFix、Nuwa、RocooFix、Robust。

这两类都有着自己的优缺点,事实上从来都没有最好的方案,只有最适合自己的。
 

热修复原理

热修复实现的利用了 Java 的类加载机制,关键点是 dexElments,代码如下:

/**
     * Finds the named class in one of the dex files pointed at by
     * this instance. This will find the one in the earliest listed
     * path element. If the class is found but has not yet been
     * defined, then this method will define it in the defining
     * context that this instance was constructed with.
     *
     * @param name of class to find
     * @param suppressed exceptions encountered whilst finding the class
     * @return the named class or {@code null} if the class is not
     * found in any of the dex files
     */
    public Class findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;
 
            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }


寻找大致过程 PathClassLoader ->BaseDexClassLoader.findClass(String name)->PathList.findClass(name, suppressedExceptions)。

大致相关的 5 个类:
PathClassLoader 用来加载应用程序的 dex。

/**
 * Provides a simple {@link ClassLoader} implementation that operates on a list
 * of files and directories in the local file system, but does not attempt to
 * load classes from the network. Android uses this class for its system class
 * loader and for its application class loader(s).
 */
public class PathClassLoader extends BaseDexClassLoader {
}

DexClassLoader  这个类是可以用来从 .jar 文件和 .apk 文件中加载 classes.dex。 (必须要在应用程序的目录下面。最终是加载jar、apk 文件里的 dex 文件)。

/**
 * A class loader that loads classes from {@code .jar} and {@code .apk} files
 * containing a {@code classes.dex} entry. This can be used to execute code not
 * installed as part of an application.
 *
 * <p>This class loader requires an application-private, writable directory to
 * cache optimized classes. Use {@code Context.getCodeCacheDir()} to create
 * such a directory: <pre>   {@code
 *   File dexOutputDir = context.getCodeCacheDir();
 * }</pre>
 *
 * <p><strong>Do not cache optimized classes on external storage.</strong>
 * External storage does not provide access controls necessary to protect your
 * application from code injection attacks.
 */
public class DexClassLoader extends BaseDexClassLoader {

BaseDexClassLoader PathClassLoader和DexClassLoader继承这个类。BaseDexClassLoader查找类的方法:
/**
 * Base class for common functionality between various dex-based
 * {@link ClassLoader} implementations.
 */
public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;
    //.... code
    @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;
    }
}

PathClassLoader:只能加载已经安装到 Android 系统中的 apk 文件(/data/app 目录),是 Android 默认使用的类加载器。
DexClassLoader:可以加载任意目录下的 dex/jar/apk/zip 文件,比 PathClassLoader 更灵活,是实现热修复的重点。

  • PathClassLoader 与 DexClassLoader 都继承于 BaseDexClassLoader。
  • PathClassLoader 与 DexClassLoader 在构造函数中都调用了父类的构造函数,但 DexClassLoader 多传了一个optimizedDirectory (optimizedDirectory 是 dex 文件的输出目录(因为在加载 jar/apk/zip 等压缩格式的程序文件时会解压出其中的 dex 文件,该目录就是专门用于存放这些被解压出来的 dex 文件的)。

findClass() 方法中用到了 pathList.findClass(name),附 DexPathList 类代码:

final class DexPathList {
    
    /**
     * 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;
 
   /**
     * Finds the named class in one of the dex files pointed at by
     * this instance. This will find the one in the earliest listed
     * path element. If the class is found but has not yet been
     * defined, then this method will define it in the defining
     * context that this instance was constructed with.
     *
     * @param name of class to find
     * @param suppressed exceptions encountered whilst finding the class
     * @return the named class or {@code null} if the class is not
     * found in any of the dex files
     */
    public Class findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;
 
            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
}

DexFile.loadClassBinaryName() 方法:

public final class DexFile {
    /**
     * See {@link #loadClass(String, ClassLoader)}.
     *
     * This takes a "binary" class name to better match ClassLoader semantics.
     *
     * @hide
     */
    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }
 
    private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            result = defineClassNative(name, loader, cookie, dexFile);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }
    private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
                                                  DexFile dexFile)
            throws ClassNotFoundException, NoClassDefFoundError;
}

总结:

一个 ClassLoader 可以包含多个 dex 文件,每个 dex 文件是一个 Element,多个 dex 文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历 dex 文件,然后从当前遍历的 dex 文件中找类,如果找类则返回,如果找不到从下一个 dex 文件继续查找。

热修复原理:
ClassLoader 加载类会遍历 dexElments 数组,从 dex 文件中不断寻找你要的 class,找到后立即返回 class,后面的 dex 文件不再读取。热修复就是把有问题 class 打包成 fixDex 文件,插入 dexElements 靠前的位置,让修复的 class 优先被找到。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值