第四章 Android动态加载、热更新、热修复、插件化系列文章 之 ClassLoader

第四章 android热更新系列文章 之 ClassLoader





目录

1. 前言

2. java中的classLoader

3. android 中的classLoader种类

4. android中的classLoader的特点

   4.1 双亲代理模型的特点

5. classLoader 的源码分析

6. Adnroid 动态加载的难点




1. 前言

热更新中最关键的部分就是classLoader




2. java中的classLoader

java中的classLoader种类和作用




3. android 中的classLoader种类



  • BootClassLoader
  • PathClassLoader
  • DexClassLoader
  • BaseDexClassLoader
名称作用备注
BootClassLoader类似于java中的BootStrap ClassLoader,加载androidFrameWork层的字节码文件
PathClassLoader类似于java中的APP ClassLoader,加载已经安装到系统中的APP的字节码文件
DexClassLoader类似于java中的Custom ClassLoader,用来加载指定目录下的字节码文件
BaseDexClassLoader是BootClassLoader、PathClassLoader、DexClassLoader的父类



4. android中的classLoader的特点



4.1 双亲代理模型的特点

classLoader在加载一个字节码文件的时候,首先会判断该类有没有被当前classLoader加载过,如果有直接使用,如果没有找到自己的父类(classLoader)判断有没有加载过,如果没有,就去找找父类的父类有没有加载过该文件,知道所有classLoader均为加载过该文件才会进行加载。

  • 带来两个作用:
  • a 类加载的共享功能

一些framework层架的类一但被我们的顶层的classLoader加载过,就会被缓存在内存里面,以后任何地方使用都不需要重新加载

  • b 类加载的隔离功能

保证不同继承路线classLoadder加载的类不是同一个类,避免用户冒充系统写一个类,冒充系统的类。

怎样的类被认为是同一个类

必须是包名相同、类名相同、是被同一个classLoader加载的三个条件缺一不可

  • 双亲代理模型的特点

一段APP代码的的运行至少需要多少个classLoader

需要两个,分别是BootClassLoader和PathClassLoader,一个加载framework层字节码,一个加载来自APP中的字节码文件

测试代码:

ClassLoader classLoader = getClassLoader();
            if (classLoader != null) {
                LogUtil.e("currentClassLoader'Name:____" + classLoader.toString());
                while (classLoader.getParent() != null) {
                    LogUtil.e(classLoader.toString() + "parrentClassLoader'Name:____" + classLoader.getParent());
                    classLoader = classLoader.getParent();
                }
            }

文件输出:

dalvik.system.PathClassLoader[...]

parrentClassLoader'Name:____java.lang.BootClassLoader@23c1dc5



5. classLoader 的源码分析

【注意】本文所参考的源码基于Android 7.1.2_r36 及以前版本,在Android 8.0.0_r4 之后,BaseDexClassLoader 、DexClassLoader 源码有所变动

  • PathClassLoader和DexClassLoader的区别

  • 1、DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk

  • 2、PathClassLoader只能加载系统中已经安装过的apk

PathClassLoader 源码
以下源码全部来自Android6.0.1

	package dalvik.system;
	
	public class PathClassLoader extends BaseDexClassLoader {
	
	    /** 有兴趣的可以看看注释,故意没删
	    * Creates a {@code PathClassLoader} that operates on a given list of files
	    * and directories. This method is equivalent to calling
	    * {@link #PathClassLoader(String, String, ClassLoader)} with a
	    * {@code null} value for the second argument (see description there).
	    *
	    * @param dexPath the list of jar/apk files containing classes and
	    * resources, delimited by {@code File.pathSeparator}, which
	    * defaults to {@code ":"} on Android
	    * @param parent the parent class loader
	    */
	    public PathClassLoader(String dexPath, ClassLoader parent) {
	        super(dexPath, null, null, parent);
	    }
	    /**
	    * Creates a {@code PathClassLoader} that operates on two given
	    * lists of files and directories. The entries of the first list
	    * should be one of the following:
	JAR/ZIP/APK files, possibly containing a "classes.dex" file as
	    * well as arbitrary resources.
	    *
	Raw ".dex" files (not inside a zip file).
	    *
	    *
	    * The entries of the second list should be directories containing
	    * native library files.
	    *
	    * @param dexPath the list of jar/apk files containing classes and
	    * resources, delimited by {@code File.pathSeparator}, which
	    * defaults to {@code ":"} on Android
	    * @param libraryPath the list of directories containing native
	    * libraries, delimited by {@code File.pathSeparator}; may be
	    * {@code null}
	    * @param parent the parent class loader
	    */
	    public PathClassLoader(String dexPath, String libraryPath,
	            ClassLoader parent) {
	        super(dexPath, null, libraryPath, parent);
	    }
	}

DexClassLoader 源码

package dalvik.system;

import java.io.File;

/**
 * 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 {
    /**
     * Creates a {@code DexClassLoader} that finds interpreted and native
     * code.  Interpreted classes are found in a set of DEX files contained
     * in Jar or APK files.
     *
     * <p>The path lists are separated using the character specified by the
     * {@code path.separator} system property, which defaults to {@code :}.
     *
     * @param dexPath the list of jar/apk files containing classes and
     *     resources, delimited by {@code File.pathSeparator}, which
     *     defaults to {@code ":"} on Android
     * @param optimizedDirectory directory where optimized dex files
     *     should be written; must not be {@code null}
     * @param libraryPath the list of directories containing native
     *     libraries, delimited by {@code File.pathSeparator}; may be
     *     {@code null}
     * @param parent the parent class loader
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

原因
DexClassLoader构造函数

  //dexPath :dex路径
  //optimizedDirectory :指定输出dex优化后的odex文件,可以为null
  //libraryPath:动态库路径(将被添加到app动态库搜索路径列表中)
  //parent:制定父类加载器,以保证双亲委派机制从而实现每个类只加载一次。
  public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
      super(dexPath, new File(optimizedDirectory), libraryPath, parent);
  }

PathClassLoader构造函数

//dexPath :dex文件以及包含dex的apk文件或jar文件的路径集合,多个路径用文件分隔符分隔,默认文件分隔符为‘:’
//optimizedDirectory :指定输出dex优化后的odex文件,可以为null
//libraryPath:动态库路径包含 C/C++ 库的路径集合,多个路径用文件分隔符分隔分割,可以为null。(将被添加到app动态库搜索路径列表中)
//parent:制定父类加载器,以保证双亲委派机制从而实现每个类只加载一次。
public PathClassLoader(String dexPath, ClassLoader parent) {
    super(dexPath, null, null, parent);
}

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

DexClassLoader 与 PathClassLoader 构造函数区别就是多了个optimizedDirectory参数,指定odex文件的输出路径。
DexClassLoader可以加载dex文件以及包含dex的apk文件或jar文件,也支持从SD卡进行加载,这也就意味着DexClassLoader可以在应用未安装的情况下加载dex相关文件。因此,它是热修复和插件化技术的基础
来查看它的代码,如下所示。

libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
        }
}

DexClassLoader构造方法的参数要比PathClassLoader多一个optimizedDirectory参数,参数optimizedDirectory代表什么呢?我们知道应用程序第一次被加载的时候,为了提高以后的启动速度和执行效率,Android系统会对dex相关文件做一定程度的优化,并生成一个ODEX文件,此后再运行这个应用程序的时候,只要加载优化过的ODEX文件就行了,省去了每次都要优化的时间,而参数optimizedDirectory就是代表存储ODEX文件的路径,这个路径必须是一个内部存储路径。
PathClassLoader没有参数optimizedDirectory,这是因为PathClassLoader已经默认了参数optimizedDirectory的路径为:/data/dalvik-cache。DexClassLoader 也继承自BaseDexClassLoader ,方法实现也都在BaseDexClassLoader中。

BaseDexClassLoader 构造方法

//类路径: /libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}

最终我们得到如下的结论,当我们执行加载一个class文件时整个的执行流程是这样的:



  1. 加载一个class必须当前classLoader(PathClassLoader或者DexClassLoader)调用自己的loadClass(String className)方法

  2. loadClass方法是接口classLoader定义的,真正的实现是在 BaseDexClassLoader 中的,BaseDexClassLoaderloadClass() 方法做了如下事情,这里就是双亲委托模式最好的提现

2.1 首先调用自己的 findLoadedClass(String name) 方法,看自己有没有加载过这个class文件 ,没有执行2.2,如果加载过直接将加载过的字节码class文件返回,方法结束

2.2 查找自己父 classLoader 并调用其 findLoadedClass(String name) 方法,如果找到查看父类是否加载过这个class字节码文件,没有执行2.3,如果加载过直接将加载过的字节码class文件返回,方法结束

2.3 查看BootClassLoader 是否加载过这个class字节码文件,没有执行3,如果加载过直接将加载过的字节码class文件返回,方法结束

  1. 执行到这个过程,可以确认当前的class文件没有被加载过,当前classLoader调用 “BaseDexClassLoader” 的 findClass(String name) 方法查找对应class字节码文件,其中 findClass(String name) 方法的实现如下

3.1 BaseDexClassLoaderfindClass(name) 调用了DexPathList的 dexPathList.findClass(name,..) 方法,其中DexPathList 类的初始化是在BaseDexClassLoader的构造方法职工进行的,DexPathList是将de文件路径转化成文件的辅助类,可以将dex结尾的文件路径转换成文件,并生成Element数组

3.2 dexPathList 对象的创建是在BaseDexClassLoader 的构造方法中完成的,然后 dexPathList 调用了自己的 makeDexElements(splitDexPath(dexPath),optimizedDirectoryexcept,classLoader) 的将所有的dex路径转换成dexfile存到Element数组中,DexPathList类中 MakeDexElements()方法的内部实现如3.2.1

3.2.1 MakeDexElements() 方法的内部,将传入的文件路径分割遍历判断分割后的dex是一个文件还是文件夹;如果这个文件压缩文件进行解压缩,找到其中所有的dex文件存储到Elements 数组中,如果是文件夹,遍历获取其中所有的dex文件储到Elements 数组中,如果是dex文件,直接储到Elements 数组中,这一步调用完成,其实是获取到了一个载有dexFile文件的Elements数组

3.3 DexPathList的内部实现,dexPathList的findClass(Element[] elements)方法的内部,遍历Element数组拿到里面的每一个Element对象,获取对象中成员变量DexFile,并调用DexFile的 loadClassBinaryName(name,classLoader,exception)

3.4 dexFile类中 loadClassBinaryName(name,classLoader,exception) 方法调用了自己的 defineClass(name,classLoader,cookie,DexFile,exception)

3.5 DexFile类中的 defineClass(name,classLoader,cookie,DexFile,exception) 方法最终调用了DexFile类中的 defineClassNative(name,classLoader,cookie,DexFile,exception) 本地方法




6. Adnroid 动态加载的难点

许多组件需要注册才能使用(像activity、service等组件)

资源动态加载复杂

  • 资源在android中是用 id 索引的形式查找的,如果资源没有在清单文件中注册,报错资源找不到

总结:

android程序的运行需要一个上下文环境,这个正是第三方加载库需要解决的重点

文章参考:

Android解析ClassLoader(一)Java中的ClassLoader
热修复——深入浅出原理与实现
Android 插件化和热修复知识梳理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值