Android 类加载机制源码分析

ClassLoader介绍

任何一个 Java 程序都是由一个或多个 class 文件组成,在程序运行时,需要将 class 文件加载到 JVM 中才可以使用,负责加载这些 class 文件的就是 Java 的类加载机制。ClassLoader 的作用简单来说就是加载 class 文件,提供给程序运行时使用。每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。

public final class Class<T>{
    //...
    private transient ClassLoader classLoader;
}

不过Android的虚拟机和JVM不同:

  • 1、JVM加载的是.class字节码文件,而Android虚拟机(DVM/ART)加载的是dex文件。
  • 2、一个.clsss文件对应一个类,而一个.dev文件里面包含了许多的类。

ClassLoader的继承结构

在这里插入图片描述
Androroid 中所有类加载器均为java.lang.ClassLoader的子类,可以分为系统类加载器和自定义类加载器。
系统ClassLoader包括BootClassLoader、PathClassLoader和DexClassLoader。

  • BootClassLoader:用于加载Android Framework的类文件,Android系统启动时(在ZygoteInit的main方法中)会使用BootClassLoader来预加载常用类,同时也会创建BootClassLoader

  • PathClassLoader:用于加载系统类和应用程序的类。(PathClassLoader在SystemServer进程的handleSystemServerProcess方法中创建)

  • DexClassLoader:用于加载指定的dex文件,或者包含*.dex文件的jar包或apk文件。

类加载过程

比如我们通常写的Activity就是通过PathClassLoader来加载的。
在这里插入图片描述
PathClassLoader 的源码如下,可以发现并没有loadClass方法,因此需要往父类找。这里需要注意PathClassLoader 的构造方法有个parent参数,这个参数也是一个ClassLoader,代表的是父加载器,这个父加载器不要理解成一种继承关系,它只是上一级的意思。

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

PathClassLoader 继承自BaseDexClassLoader,在PathClassLoader 中也没有loadClass,因此继续往上查找(BaseDexClassLoader extends ClassLoader)。
最终在ClassLoader中找到loadClass方法。

ClassLoader#loadClass    
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            //首先检查这个类是不是已经被加载了,如果被加载过了就直接返回。(缓存)
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                    //如果有上一级加载器,就调用上一级加载器的loadClass
                        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) {
                    //如果所有的父加载器都没有成功加载这个类,那就自己去加载
                    c = findClass(name);
                }
            }
            //返回加载的Class
            return c;
    }

这就是一种双亲委托机制,如果一个类加载器收到了类加载的请求,首先不会自己去尝试加载这个类,而是把加载委托给父加载器去完成,依次向上,直至没有父加载器。 因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到该类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

通过下面的方式可以打印PathClassLoader的父加载器:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
双亲委托的好处:
1、避免重复加载,当父加载器已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2、安全:防止核心API随意篡改。

理解了loadClass的双亲委托再来看看PathClassLoader最终是如何加载我们应用中的类的。通过上面分析知道通过findClass来加载。PathClassLoader继承自BaseDexClassLoader,它自己没有实现findClass,使用的是父类BaseDexClassLoader的findClass方法。

 //BaseDexClassLoader#findClass
@Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        //根据名称查找指定的class
        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;
    }

那么重点就到了pathList的findClass了,先来搞清楚pathList是什么。
pathList 是在BaseDexClassLoader的构造方法中创建。

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
        ...
}

DexPathList的构造方法如下:

public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {

        //....
        // splitDexPath(dexPath)返回的是List<File>
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext);
}

DexPathList#makeDexElements方法如下:

private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader) {
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;
     //打开所有文件并加载里面的dex文件。
      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();
              //加载dex 文件并创建DexFile ,根据DexFile 创建Element
              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);
                  } 
                  if (dex == null) {
                      elements[elementsPos++] = new Element(file);
                  } else {
                      elements[elementsPos++] = new Element(dex, file);
                  }
              }
          } 
      }
      if (elementsPos != elements.length) {
          elements = Arrays.copyOf(elements, elementsPos);
      }
      return elements;
    }

PathClassLoader 中存在一个Element数组,Element类中存在DexFile用于加载对应的dex文件,即:APK中有n个 dex,则Element数组就有n个元素。而我们的类就是在dex文件中的,一个dex文件中包含了多个类。
在这里插入图片描述
前面分析了BaseDexClassLoader的findClass调用了DexPathList的findClass。
很简单就是从一个dex文件中查找指定的类。

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

到此为止Android类加载的基本流程就分析完了。

热修复

热修复的有代码修复、资源修复、动态链接修复。而每一种修复方式都有一些解决思想。对于代码修复,上面的类加载就提供了一种解决思路。
通过DexPathList#findClass代码可以发现,系统在加载一个类的时候是遍历dexElements数组的每一个元素去调用findClass尝试找到指定的类,如果找到了类就直接返回。
而热修复的思想就在这里,当我们的APP某个类出现bug了如何在不打包新APP的情况下进行修复?
将修复好的类打包成一个补丁包(.dex文件)上传到服务器,APP下载这个补丁包,然后当用户重新启动APP的时候(类被加载了就不会被卸载,因此需要重启APP),尝试将这个补丁包对应的Element 添加到dexElements数组的开头(通过反射),这样APP再次运行的时候,如果需要加载这个类,就会在dexElements的开头找到已经修复好的类,直接返回,而不会去加载后面的由问题的类了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值