Android 插件化之类加载器 ClassLoader

原创 2016年08月31日 10:45:13

Android 插件化之类加载器 ClassLoader

类加载器

用来加载 Java 类到 Java 虚拟机
根据一个指定的类名,找到或者生产对应的字节代码,生成一个Java类,
除此之外,ClassLoader还负责加载Java应用所需的资源,如图像文件和配置文件等。

Java虚拟机使用Java类的方式

java源程序(.java文件)经过编译器转换成Java字节代码(.class文件)。
类加载器负责读取.class文件,并生成一个Java类的实例。每个这样的实例用来表示一个java类。通过此实例的newInstance()方法就可以创建出该类的一个对象。

一个Android App有几个ClassLoader实例 ?

一个运行的Android应用至少有2个ClassLoader。

在Android系统启动的时候回创建一个Boot类型的ClassLoader实例,用于加载一些系统Framework层级需要的类,我们的Android应用里也需要用到一些系统的类,所以App启动时也会把这个Boot类型的ClassLoader传进来。

getClassLoader().getParent();  //BootClassLoader

此外,App也有自己的类,这些类保存在APK的dex文件里面,所以App启动的时候,也会创建一个自己的ClassLoader实例,用于加载自己dex文件里的类。

getClassLoader(); //PathClassLoader(应用启动时创建的,用于加载“/data/app/me.kaede.anroidclassloadersample-1/base.apk”里面的类)  

创建自己的ClassLoader实例

动态加载外部的dex文件的时候,我们也可以使用自己创建的ClassLoader实例来加载dex里面的Class。

创建一个ClassLoader实例时,需要一个现有的ClassLoader实例作为新创建的实例的Parent。
这样一来,整个Android系统里所有的ClassLoader实例都会被一颗树关联起来,这也就是ClassLoader的双亲代理模型

ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
    if (parentLoader == null && !nullAllowed) {
        throw new NullPointerException("parentLoader == null && !nullAllowed");
    }
    parent = parentLoader;
}  

ClassLoader双亲代理模型加载类的特点和作用

JVM中ClassLoader通过defineClass()加载jar里面的Class,而在Android中这个方法被弃用了。取而代之的是loadClass()。

public Class<?> loadClass(String className) throws ClassNotFoundException {
    return loadClass(className, false);
}

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
    Class<?> clazz = findLoadedClass(className);

    if (clazz == null) {
        ClassNotFoundException suppressed = null;
        try {
            clazz = parent.loadClass(className, false);
        } catch (ClassNotFoundException e) {
            suppressed = e;
        }

        if (clazz == null) {
            try {
                clazz = findClass(className);
            } catch (ClassNotFoundException e) {
                e.addSuppressed(suppressed);
                throw e;
            }
        }
    }

    return clazz;
}

loadClass方法在加载一个类的实例时

  1. 会先查询当前ClassLoader实例是否加载过此类,有就直接返回
  2. 如果没有,查询Parent是否已经加载过此类,如果已经加载过,就返回Parent加载的类
  3. 如果继承线路上的ClassLoader都没有加载,再由child执行类的加载工作

作用

  1. 如果一个类被位于树根的ClassLoader加载过,那么在以后整个系统的生命周期内,这个类永远不会被重新加载。
  2. 不同继承路线上的ClassLoader加载的类肯定不是同一个类,这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。

使用ClassLoader需要注意的问题

如果你希望通过动态加载的方式,加载一个新版本的dex文件,使用里面的新类替换原有的旧类,从而修复原有类的BUG,那么你必须保证在加载新类的时候,旧类还没有被加载,因为如果已经加载过旧类,那么ClassLoader会一直优先使用旧类。

如果旧类总是优先于新类被加载,我们也可以使用一个与加载旧类的ClassLoader没有树的继承关系的另一个ClassLoader来加载新类,因为ClassLoader只会检查其Parent有没有加载过当前要加载的类,如果两个ClassLoader没有继承关系,那么旧类和新类都能被加载。

不过这样一来又有另一个问题了,在Java中,只有当两个实例的类名、包名以及加载其的ClassLoader都相同,才会被认为是同一种类型。上面分别加载的新类和旧类,虽然包名和类名都完全一样,但是由于加载的ClassLoader不同,所以并不是同一种类型,在实际使用中可能会出现类型不符异常。

同一个Class = 相同的 ClassName + PackageName + ClassLoader

DexClassLoader和PathClassLoader

在Android中,ClassLoader是一个抽象类,在实际开发中我们一般使用其子类DexClassLoader、PathClassLoader来。

  • DexClassLoader 可以加载jar、apk、dex,可以从SD卡中加载未安装的apk
  • PathClassLoader 只能加载系统中已经安装过的apk

optimizedDirectory

optimizedDirectory是用来缓存我们需要加载的dex文件的,并创建一个DexFile对象,如果它为null,那么会直接使用dex文件原有的路径来创建DexFile对象象。

optimizedDirectory必须是一个内部存储路径,无论哪种动态加载,加载的可执行文件一定要存放在内部存储。DexClassLoader可以指定自己的optimizedDirectory,所以它可以加载外部的dex,因为这个dex会被复制到内部路径的optimizedDirectory;而PathClassLoader没有optimizedDirectory,所以它只能加载内部的dex,这些大都是存在系统中已经安装过的apk里面的。

加载类的过程

ClassLoader.loadClass() -> ClassLoader.findClass() -> BaseDexClassLoader.findClass() ->

public Class findClass(String name) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;
        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    return null;
}

public Class loadClassBinaryName(String name, ClassLoader loader) {
    return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);

即遍历了之前存储的DexFile实例,也就是遍历了所有加载过的dex文件,再调用loadClassBinaryName方法一个个尝试能不能加载想要的类

自定义ClassLoader

平时进行动态加载开发的时候,使用DexClassLoader就够了。但我们也可以创建自己的类去继承ClassLoader,我们可以重载loadClass方法并改写类的加载逻辑。

ClassLoader双亲代理的实现很大一部分就是在loadClass方法里,我们可以通过重写loadClass方法避开双亲代理的框架,这样一来就可以重新加载已经加载过的类,也可以在加载类的时候注入一些代码。这是一种Hack的开发方式,采用这种开发方式的程序稳定性可能比较差,但是却可以实现一些“黑科技”的功能。

Android程序比起一般Java程序在使用动态加载时麻烦在哪里

  1. Android中许多组件类(如Activity、Service等)是需要在Manifest文件里面注册后才能工作的(系统会检查该组件有没有注册),所以即使动态加载了一个新的组件类进来,没有注册的话还是无法工作;
  2. Res资源是Android开发中经常用到的,而Android是把这些资源用对应的R.id注册好,运行时通过这些ID从Resource实例中获取对应的资源。如果是运行时动态加载进来的新类,那类里面用到R.id的地方将会抛出找不到资源或者用错资源的异常,因为新类的资源ID根本和现有的Resource实例中保存的资源ID对不上;

说到底,抛开虚拟机的差别不说,一个Android程序和标准的Java程序最大的区别就在于他们的上下文环境(Context)不同。Android中,这个环境可以给程序提供组件需要用到的功能,也可以提供一些主题、Res等资源,其实上面说到的两个问题都可以统一说是这个环境的问题,而现在的各种Android动态加载框架中,核心要解决的东西也正是“如何给外部的新类提供上下文环境”的问题。

参考
https://segmentfault.com/a/1190000004062880
https://www.ibm.com/developerworks/cn/java/j-lo-classloader/

版权声明:本文为博主原创文章,未经博主允许不得转载。

java之类加载器classLoader

类加载器的作用 将classpath中的.class字节码文件加载进虚拟机 类加载器之间的关系和管辖范围 java虚拟机中可以安装多个类加载器,系统默认主要三个类加载器,每个类加载器负责加载特定位置的...
  • u011082453
  • u011082453
  • 2014年12月21日 17:29
  • 265

Android插件化开发之动态加载基础之ClassLoader工作机制

类加载器ClassLoader 早期使用过Eclipse等Java编写的软件的同学可能比较熟悉,Eclipse可以加载许多第三方的插件(或者叫扩展),这就是动态加载。这些插件大多是一些Jar包,而使...
  • u011068702
  • u011068702
  • 2016年11月21日 00:46
  • 1281

Android 插件化原理解析——Service的插件化

在 Activity生命周期管理 以及 广播的管理 中我们详细探讨了Android系统中的Activity、BroadcastReceiver组件的工作原理以及它们的插件化方案,相信读者已经对Andr...
  • huangkun125
  • huangkun125
  • 2016年08月11日 16:43
  • 1715

JVM之类加载器(ClassLoader)基本介绍

类加载器用于将class文件加载到JVM中去执行。下面介绍类加载器涉及到的基本概念和加载基本过程。 一、Java虚拟机与程序的生命周期         在运行Java程序时,会启动JVM进程,该进程...
  • sunfeizhi
  • sunfeizhi
  • 2015年11月30日 10:27
  • 485

创建一个简单的web服务器(二):使用自定义的类加载器来替换URLClassLoader

在上一章中我们加载Servlet用的是URLClassLoader,在这一章中我们使用自定义的类加载器来替换URLClassLoader。关于类加载器的文章请参考这里:深入探讨 Java 类加载器。这...
  • zknxx
  • zknxx
  • 2017年01月04日 13:41
  • 744

ClassLoader and 插件化设计

ClassLoader一个经常出现又让很多人望而却步的词,本文将试图以最浅显易懂的方式来讲解 ClassLoader,希望能对不了解该机制的朋友起到一点点作用。 要深入了解ClassLoad...
  • pi9nc
  • pi9nc
  • 2013年11月04日 21:10
  • 2853

Android插件化完美实现代码资源加载及原理讲解 附可运行demo

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 。 我们通过前4篇的分解,分别将插件化设计到的知识点全部梳理了一遍,如果没有看过的,建议先看前面4篇 1. Binder机制...
  • yulong0809
  • yulong0809
  • 2017年03月01日 18:02
  • 8721

Android|插件化之ClassLoader

待完成
  • l491337898
  • l491337898
  • 2017年03月15日 12:10
  • 97

Android插件化开发基础之Java类加载器与双亲委派模型

类加载器 加载类的开放性 类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因。在类加载的第一阶段“加载”过程中,需要通过一个类的全限定名来获取定义此类的二进...
  • u011068702
  • u011068702
  • 2016年11月20日 23:48
  • 853

闲聊ClassLoader的父加载器

Java使用类加载器来装载字节码到内存,以便后续用来创建对象调用方法等。就目前的JVM,要说这个ClassLoader,先要说到它的委托模型(有人将parent译作双亲,双亲委派模型,窃以为,很不准确...
  • Hsuxu
  • Hsuxu
  • 2013年07月14日 12:25
  • 2777
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android 插件化之类加载器 ClassLoader
举报原因:
原因补充:

(最多只允许输入30个字)