知识星球-玉刚说 #第十周# 作业

410 阅读4分钟

1.Android中有哪几种ClassLoader?他们的作用和区别是什么?

  • 先看看ClassLoader的继承关系。

ClassLoader

public abstract class ClassLoader
extends Object
java.lang.Object
↳ java.lang.ClassLoader
Known Direct Subclasses
BaseDexClassLoader,SecureClassLoader
Known Indirect Subclasses
DelegateLastClassLoader,DexClassLoader,InMemoryDexClassLoader,PathClassLoader,URLClassLoader
  • 通过上面的继承关系我们知道,ClassLoader继承自Object,直接子类有BaseDexClassLoader,SecureClassLoader。间接子类有DelegateLastClassLoader,DexClassLoader,InMemoryDexClassLoader,PathClassLoader,URLClassLoader。
  • 我们经常用到的两个子类就是DexClassLoader和PathClassLoader。他们各自的作用:
  1. DexClassLoader:A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application. Prior to API level 26, this class loader requires an application-private, writable directory to cache optimized classes. Use Context.getCodeCacheDir() to create such a directory:

    File dexOutputDir = context.getCodeCacheDir();
    

    Do not cache optimized classes on external storage.External storage does not provide access controls necessary to protect your application from code injection attacks.

    DexClassLoader是一个能从包含classes.dex入口的.jar或者.apk文件加载classes。他可以执行未安装应用里面的代码,作为当前应用的一部分。在API 26以前,这个类加载器需要一个应用私有的、可写目录来缓存经过优化的classes,可以用context.getCodeCacheDir()方法来创建一个这样的目录。API 26以后,这个参数已经废弃,没有任何影响。

    加粗部分 注意!!!不要把优化的代码放在外部存储,防止注入攻击

  2. PathClassLoader:Provides a simple 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).

    提供了一个简单的类加载器,来加载本地文件系统里面的目录或者文件,但是不能从网络上 加载classes。

2.简述ClassLoader的双亲委托模型。

  • ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象。

3.简述双亲委托模型在热修复领域的作用。

  • 热修复简单的讲就是应用不需要发版,通过下载补丁,在用户没有察觉的状态下,修复线上bug或者做一些更改,快速高效。

假设现在我们线上的应用出现了bug,A.class。我们的目的就是修复A.class,怎么操作呢?这里就用到我们之前说过的DexClassLoader和ClassLoader的双亲委托机制了。首先我们要了解应用是如何把A.class加载到应用内存中去的。

  1. 在A加载到内存中之前,DexClassLoader会调用findClass方法,通过一个DexPathList对象findClass()方法来获取class

BaseDexClassLoader.java(科学上网)

    ...

    private final DexPathList pathList;
    
    ...
    
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
        if (reporter != null) {
            reportClassLoaderChain();
        }
    }
    ...
    @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;
    }
    
    ...

  1. 在DexPathList的findClass方法中,对之前构造好dexElements数组集合进行遍历,一旦找到类名与A相同的类时,就直接返回这个class,找不到则返回null。也就是说,通过DexClassLoader查找一个类,最终就是就是在一个数组中查找特定值的操作。 DexPathList.java(科学上网)
/** java
    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;
    }

所以,我们似乎找到了一种简单的实现热修复的方案,我们可以见要修复的类文件打包成一个补丁,封装一个Element对象,并且将这个Element对象插到原有dexElements数组的最前端,这样当DexClassLoader去加载类时,优先会从我们插入的这个Element中找到相应的类,虽然那个有bug的类还存在于数组中后面的Element中,但由于双亲加载机制的特点,这个有bug的类已经没有机会被加载了,这样一个bug就在没有重新安装应用的情况下修复了。