ClassLoader:Java Android 总结

前言

无论是 Java 还是 Android,学习它们的类加载机制都非常重要的。本文统一记录两个平台下 ClassLoader 的实现。

一、Java 中的 ClassLoader

1.1 Bootstrap ClassLoader 引导类加载器

  • 作用:
    加载 Java 系统类,如 java.lang.*、java.uti.*等。JVM 的启动也是由它创建的初始类来完成的。
  • 特点:
    C/C++ 实现;
    不继承于 java.lang.ClassLoader

1.2 Extensions ClassLoader 扩展类加载器

  • 作用:
    加载 Java 扩展类,比如 swing 系列(图形化)、内置的 js 引擎、xml 解析器等等。

1.3 AppClassLoader 应用程序加载器

  • 作用:
    加载当前应用程序 Classpath 目录下的所有 jar 和 Class 文件。可指定目录。

1.4 CustomClassLoader 自定义加载器

  • 作用:
    自己实现的加载器。
  • 特点:
    继承于 java.lang.ClassLoader。
    Extensions ClassLoader 和 App ClassLoader 也继承了java.lang.ClassLoader 类。

Java 类加载器继承关系

继承关系
  • ClassLoader:是抽象类,定义类加载器主要功能;
  • SecureClassLoader:继承了 ClassLoader,加入权限相关功能,加强安全性;
  • URLClassLoader:通过 Uri 路径从 jar 文件和文件夹中加载类和资源;
  • ExtClassLoader 和 AppClassLoader 都继承自 URLClassLoader,都是 Launcher 的内部类。
    Launcher 是 Java 虚拟机的入口应用,ExtClassLoader 和 AppClassLoader 都是在 Launcher 中进行初始化的。

双亲委托(Parents Delegation Model)

  • 定义:
    所谓的双亲委托,也就是这些 ClassLoader 在加载类或接口时,首先判断该 Class 是否已经加载。如果没有加载则去委托给父类去加载,这样依次查找。
    到最后会找到最顶层的 Bootstrap ClassLoader,如果找到或成功加载该 Class 则返回。这样就完成了一个所谓的 "Parents Delegation" 的过程。
    如果最顶层没有返回,则只能自己加载了。

package java.lang ClassLoader # loadClass 方法

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        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) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }
  • 作用:
    至于为什么采用这种模式,是有原因的。Java 中判断两个类是否相等,也包括匹配两者的类加载器。如果两个类的加载器不同,就算他们名字一样、路径一样,依旧会被认为不是同一个类。
    这里的相等包含:equals() 方法、isAssignableFrom() 方法、isInstantce() 方法。

  • 优点:
    避免重复加载;
    更加安全,不容易被随意篡改。

1.5 自定义 ClassLoader

  1. 重写 findClass() 方法,调用 loadClass() 时如果父加载器不为空或不能加载,会调用该方法进行加载。
  2. 加载 Class 文件,这里是使用文件流加载。
public class MyClassLoader extends ClassLoader {

    // 1. 重写 findClass 方法
    @Override
    protected Class<?> findClass(String name) {
        Class clazz = null;
        byte[] classData = loadData();
        if (null != classData) {
            clazz = defineClass(name, classData, 0, classData.length);
        }
        return clazz;
    }
    // 2.从磁盘加载文件
    private byte[] loadData() {
        File file = new File("D:\\Test.class");
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = new FileInputStream(file);
            out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int length = 0;
            while ((length = in.read(buffer)) != -1) {
                out.write(buffer, 0, length);
            }
            return out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != in) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (null != out) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
  1. 进行调试,最后打印出来的类名为 TestClass。
public static void main(String[] args) {
    MyClassLoader myClassLoader = new MyClassLoader();
    try {
         // 指定类名加载,也可以扩展一下遍历加载文件夹下所有文件
         Class c = myClassLoader.loadClass("com.sky.test.Test");
         System.out.print(c.getSimpleName());//TestClass
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

二、Android 中的 ClassLoader

Android 中的 ClassLoader 以及继承结构:

ClassLoader:顶级父类。

  • \hookrightarrow BootClassLoader:Android 系统启动时预加载常用类。
  • \hookrightarrow BaseDexClassLoader
    • \hookrightarrow PathClassLoader:加载指定目录下的类;
    • \hookrightarrow DexClassLoader:加载指定目录下的类;
    • \hookrightarrow InMemoryDexClassLoader:加载内存中的类;
  • \hookrightarrow SecureClassLoader:扩展权限功能,增加安全性。
    • URLClassLoader:加载 URL 路径的类和资源。

2.1 BootClassLoader

java.lang.ClassLoader 内部类 BootClassLoader

class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

    public BootClassLoader() {
        super(null);
    }
}
  • 作用:
    Android 系统启动时,预加载常用类。
  • 特点:
    java 实现;
    ClassLoader 内部类,同包访问;
    功能大多使用 VMClassLoader(虚拟机 ClassLoader) 调用 native 方法实现。很好理解,因为 VM 是 C/C++ 实现的,所以会调用 native 方法去加载类或资源。

2.2 PathClassLoader

dalvik.system.PathClassLoader

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);
    }
}
  • 作用:
    加载 dex 文件,各种形式的(dex/apk/jar) 。
  • 构造器:
    dexPath:文件路径;
    librarySearchPath:包含 C、C++库的路径集合;
    parent:父加载器。

2.3 DexClassLoader

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
        }
}
  • 作用:
    加载 dex 文件,各种形式的(dex/apk/jar )。
  • 构造器
    dexPath :dex 文件路径集合,多个文件用分隔符分割,默认分隔符 “:”;
    optimizedDirectory:已解压 dex 文件路径,该路径必须为内部存储路径。一般为:/data/data/<Package Name>/...,作为缓存路径使用。
    librarySearchPath:库的路径集合,可以为 null;
    parent:父加载器。

PathClassLoader 和 DexClassLoader 异同

DexClassLoader 和 PathDexClassLoader 都能加载各处的 dex 文件。

API26 之前版本传递 optimizedDirectory 参数有不同区别:

  • DexClassLoader 传参 optimizedDirectory 可以自定义 dex 优化后存放的路径。
  • PathDexClassLoader 传 null,缓存到默认 data/dalvik-cache/ 路径中。

API26 之后统一了缓存 dex 文件的路径,optimizedDirectory 参数已弃用。统一存放到了 dex 文件同级目录下的 oat/< isa > 文件作为缓存文件的存储目录。

2.4 BootClassLoader 创建过程

Zygote 创建时,调用 ZygoteInit 方法,这个过程中创建了 BootClassLoader :

  1. ZygoteInit 调用 preload() 方法进行预加载;

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

public static void main(String argv[]) {
    ...
    // 预加载方法
    preload(bootTimingsTraceLog);
    ...
}
  1. preload() 方法调用 preloadClasses(); 用来加载 Class
static void preload(TimingsTraceLog bootTimingsTraceLog) {
    ...
    preloadClasses();
    ...
}
  1. preloadClasses() 方法以流的方式,从系统指定目录预加载 Class。
private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";

private static void preloadClasses() {
        final VMRuntime runtime = VMRuntime.getRuntime();
        InputStream is;
        try {
            // 从目录创建文件输入流
            is = new FileInputStream(PRELOADED_CLASSES);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
            return;
        }
    
        try {
            // 使用 BufferedReader 读取流,带缓存 读取快
            BufferedReader br
                = new BufferedReader(new InputStreamReader(is), 256);

            int count = 0;
            String line;
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.startsWith("#") || line.equals("")) {
                    continue;
                }
                Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
                try {
                    if (false) {
                        Log.v(TAG, "Preloading " + line + "...");
                    }
                    // 读取到的数据转换为 Class
                    Class.forName(line, true, null);
                    count++;
                } catch (ClassNotFoundException e) {
                    Log.w(TAG, "Class not found for preloading: " + line);
                } 
        ...
        } catch (IOException e) {
            Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
        } finally {
            ...
        }
}

  • PRELOADED_CLASSES 路径下的文件,描述了要预加载的类。这里 Zygote 加载了一些通用类,应用程序进程在创建时就无需再次加载了。

frameworks/base/config/preloaded-classes

...
android.app.Dialog
android.app.Dialog$ListenersHandler
android.app.DialogFragment
...
android.app.Fragment
android.app.Fragment$1
android.app.Fragment$AnimationInfo
android.app.Fragment$OnStartEnterTransitionListener
...
android.app.Activity
android.app.Activity$HostCallbacks
android.app.ActivityManager
android.app.ActivityManager$1
android.app.ActivityManager$AppTask
android.app.ActivityManager$MemoryInfo
  1. forName 方法根据类名加载 Class。并且如果 loader 为null,就创建了 BootClassLoader。
    @CallerSensitive
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        if (loader == null) {
            // 创建 BootClassLoader
            loader = BootClassLoader.getInstance();
        }
        Class<?> result;
        try {
            // 调用 native 方法加载 Class
            result = classForName(name, initialize, loader);
        } catch (ClassNotFoundException e) {
            Throwable cause = e.getCause();
            if (cause instanceof LinkageError) {
                throw (LinkageError) cause;
            }
            throw e;
        }
        return result;
    }

调用 native 方法加载 Class。

    @FastNative
    static native Class<?> classForName(String className, boolean shouldInitialize,
             ClassLoader classLoader) throws ClassNotFoundException;

2.5 PathClassLoader 创建过程

  1. ZygoteInit 中 main 方法会根据参数进行系统初始化

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

public static void main(String argv[]) {
    ... // preLoad 在之前,也就是 BootClassLoader 先创建预加载
    boolean startSystemServer = false;
    ...
    for (int i = 1; i < argv.length; i++) {
        ...
        if ("start-system-server".equals(argv[i])) {
            startSystemServer = true;
        }
        ...
    }
    if (startSystemServer) {
        // 看这里
        Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
        if (r != null) {
            r.run();
            return;
        }
    }
}
  1. forkSystemServer() 方法 fork 系统进程:
private static Runnable forkSystemServer(String abiList, String socketName,ZygoteServer zygoteServer) {
    ...
    int pid;
    /* Request to fork the system server process */
    // 2.1 创建系统进程,返回 pid
    pid = Zygote.forkSystemServer(
                parsedArgs.uid, parsedArgs.gid,
                parsedArgs.gids,
                parsedArgs.runtimeFlags,
                null,
                parsedArgs.permittedCapabilities,
                parsedArgs.effectiveCapabilities);
     ...
     if (pid == 0) {
        if (hasSecondZygote(abiList)) {
            waitForSecondaryZygote(socketName);
        }
        zygoteServer.closeServerSocket();
        // 2.2 处理系统进程
        return handleSystemServerProcess(parsedArgs);
     }
     return null;
}
  • 2.1 forkSystemServer() 调用 native 方法 fork 系统进程。
public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
    ...
    int pid = nativeForkSystemServer(
        uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities);
    ...
    return pid;
}

native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
        int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
  • 2.2 pid == 0,调用 handleSystemServerProcess() 方法处理系统进程逻辑。
private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
    ...
    ClassLoader cl = null;
    if (systemServerClasspath != null) {
        cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
        Thread.currentThread().setContextClassLoader(cl);
    }
    return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
}
  1. createPathClassLoader() 使用 ClassLoaderFactory 创建 PathClassLoader。

/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
    String libraryPath = System.getProperty("java.library.path");
    return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,
                ClassLoader.getSystemClassLoader(), targetSdkVersion, true /* isNamespaceShared */,
                null /* classLoaderName */);

frameworks/base/core/java/com/android/internal/os/ClassLoaderFactory.java

public static ClassLoader createClassLoader(String dexPath,
           String librarySearchPath, ClassLoader parent, String classloaderName) {
    // 进行判断,如果 classloaderName 为 null 或 PathClassLoader.class.getName() 则创建 PathClassLoader
    if (isPathClassLoaderName(classloaderName)) {
            return new PathClassLoader(dexPath, librarySearchPath, parent);
    } else if (isDelegateLastClassLoaderName(classloaderName)) {
            return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
    }
    throw new AssertionError("Invalid classLoaderName: " + classloaderName);

到这里就完成了 PathClassLoader 的创建过程。

总结

Java 中的 ClassLoader:

名称 作用
ClassLoader ClassLoader 的抽象
SecureClassLoader 加入权限相关功能,加强安全性
URLClassLoader 通过 Uri 读取文件
Bootstrap ClassLoader 加载 Java 系统类
Extensions ClassLoader 加载 Java 扩展类
AppClassLoader 加载当前应用程序文件
ExtClassLoader 加载 ext 目录下文件
自定义 ClassLoader 加载用户指定 Class 文件

Android 中的 ClassLoader:

名称 作用
ClassLoader ClassLoader 的抽象
BootClassLoader Android 系统启动时预加载常用类
PathClassLoader 加载指定目录下的类
DexClassLoader 加载指定目录下的类
InMemoryDexClassLoader 加载内存中的类
SecureClassLoader 扩展权限功能,增加安全性
URLClassLoader 加载 URL 路径的类和资源
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 229,460评论 6 538
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,067评论 3 423
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 177,467评论 0 382
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,468评论 1 316
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,184评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,582评论 1 325
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,616评论 3 444
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,794评论 0 289
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,343评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,096评论 3 356
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,291评论 1 371
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,863评论 5 362
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,513评论 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,941评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,190评论 1 291
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,026评论 3 396
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,253评论 2 375