Android ClassLoader要点总结

1.java classloader要点

1.1 类的加载流程

类的加载流程

1.2 类的加载机制之双亲委派模型

Java双亲委派模型

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

  1. 避免重复加载:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
  2. 安全:其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

2.android中的classloader分类

2.1 关系类图

Android中的Classloader

2.2 五种类加载器

2.2.1 PathClassLoader

public class PathClassLoader extends BaseDexClassLoader {

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

    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
}
  • 继承自BaseDexClassLoader;
  • PathClassLoader 只能加载/data/app中的apk,也就是已经安装到手机中的apk;

2.2.2 DexClassLoader

public class DexClassLoader extends BaseDexClassLoader {

    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}
  • 继承自BaseDexClassLoader;
  • DexClassLoader 可以加载任何路径的apk/dex/jar

2.2.3 BaseDexClassLoader

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;  //记录dex文件路径信息

    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
}
  • PathClassLoader与DexClassLoader的公共父类。
  • dexPath: 包含目标类或资源的apk/jar列表;当有多个路径则采用“:”分割
  • File optimizedDirectory:优化后dex文件存在的目录, 可以为nul。由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是制定解压出的dex 文件存放的路径。这也是对apk中dex根据平台进行ODEX优化的过程。其实APK是一个程序压缩包,里面包含dex文件,ODEX优化就是把包里面的执行程序提取出来,就变成ODEX文件。
  • libraryPath: native库所在路径列表;当有多个路径则采用:分割;

2.2.4 BootClassLoader

class BootClassLoader extends ClassLoader {
    private static BootClassLoader instance;

    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

    public BootClassLoader() {
        super(null, true);
    } }
  • Android平台上所有ClassLoader的最终parent。
  • ClassLoader的内部类,仅包内可见,所以我们没法使用。

2.2.5 URLClassLoader

  • 只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器

2.2.6 ClassLoader

public abstract class ClassLoader {
    private ClassLoader parent;  //记录父类加载器

    protected ClassLoader() {
        this(getSystemClassLoader(), false); //见下文
    }

    protected ClassLoader(ClassLoader parentLoader) {
        this(parentLoader, false);
    }

    ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
        if (parentLoader == null && !nullAllowed) {
            //父类的类加载器为空,则抛出异常
            throw new NullPointerException("parentLoader == null && !nullAllowed");
        }
        parent = parentLoader;
    }
}

2.3 Context#getClassLoader与Classloader#getSystemClassLoader异同

首先,我们看下面一段代码,在Application的onCreate()中添加如下代码:

PathClassLoader classLoader = (PathClassLoader) getApplicationContext().getClassLoader();
Log.d("mytest", "classLoader : " + classLoader + "\n" +
            "parent : " + classLoader.getParent() + "\n" +
            "grandParent : " + classLoader.getParent().getParent() + "\n" +
            "system classloader : " + ClassLoader.getSystemClassLoader() + "\n" +
            "system parent : " + ClassLoader.getSystemClassLoader().getParent());

代码的执行结果,打印内容如下:

classLoader : dalvik.system.PathClassLoader[dexPath=/data/app/com.gavin.demo2application-1.apk,libraryPath=/data/app-lib/com.gavin.demo2application-1]
 parent : java.lang.BootClassLoader@41099128
 grandParent : null
 system classloader : dalvik.system.PathClassLoader[dexPath=.,libraryPath=null]
 system parent : java.lang.BootClassLoader@41099128
  • Context#getClassLoader获取到的是以当前apk安装路径为dexpath的PathClassLoader,其父classloader为BootClassLoader
  • Classloader.getSystemClassLoader获取到的是PathClassLoader,其父classloader为同一个 BootClassLoader

再做如下测试

        Log.i("LiaBin", "Context的类加载加载器:" + Context.class.getClassLoader());//系统库的class,所以是BootClassLoader
        Log.i("LiaBin", "String的类加载加载器:" + String.class.getClassLoader());//系统库的class,所以是BootClassLoader,不同于java吧,java应用程序的话打印的是null
        Log.i("LiaBin", "MainActivity的类加载器:" + MainActivity.class.getClassLoader());//本地的class,所以是PathClassLoader
        Log.i("LiaBin", "StringRequest的类加载器:" + StringRequest.class.getClassLoader());//第三方库class,所以也是PathClassLoader。。v7,v4,recycleview中的也是第三方库哟
        Log.i("LiaBin", "应用程序默认加载器:" + getClassLoader()); //这才是默认的加载器DexPathList[[zip file "/data/app/demo.lbb.mytest-1.apk"]..
        Log.i("LiaBin", "系统类加载器:" + ClassLoader.getSystemClassLoader()); //上面代码可以知道此时是PathClassLoader,同时DexPathList[[directory "."]..

代码执行结果如下:

I/LiaBin: Context的类加载加载器:java.lang.BootClassLoader@41cb45f0 
I/LiaBin: String的类加载加载器:java.lang.BootClassLoader@41cb45f0 
I/LiaBin: MainActivity的类加载器:dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/demo.lbb.mytest-1.apk”],nativeLibraryDirectories=[/data/app-lib/demo.lbb.mytest-1, /vendor/lib, /system/lib]]] 
I/LiaBin: StringRequest的类加载器:dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/demo.lbb.mytest-1.apk”],nativeLibraryDirectories=[/data/app-lib/demo.lbb.mytest-1, /vendor/lib, /system/lib]]] 
I/LiaBin: 应用程序默认加载器:dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/demo.lbb.mytest-1.apk”],nativeLibraryDirectories=[/data/app-lib/demo.lbb.mytest-1, /vendor/lib, /system/lib]]] 
I/LiaBin: 系统类加载器:dalvik.system.PathClassLoader[DexPathList[[directory “.”],nativeLibraryDirectories=[/vendor/lib, /system/lib]]] 
  • Context#getClassLoader获取到的PathClassLoader是用来加载自定义类如MainActivity、StringRequest的应用程序默认类加载器。
  • Classloader.getSystemClassLoader获取到的PathClassLoader是用来加载系统类如String,Context的系统类加载器。

3.android中classloader的初始化时机

classloader的初始化时机可以参考https://juejin.im/entry/5819992867f356005879f6de

  • classloader保存在LoadedApk对象中,一般通过LoadedApk#getClassLoader方法获取;
  • ActivityThread.handleBindApplication
    ==> Application app = data.info.makeApplication(data.restrictedBackupMode, null);
    ==> app = mActivityThread.mInstrumentation.newApplication( LoadedApk#getClassLoader(), appClass, appContext);
  • 四大组件启动时都经历了LoadedApk#getClassLoader获取classloader

你可能感兴趣的:(Android ClassLoader要点总结)