了解Android ClassLoader

QuibblerQuibbler 2020-08-19 约 40 分钟 1796 次阅读

了解Android ClassLoader

Java中通过ClassLoader将.jar或class类加载进JVM虚拟机。在Android中也有ClassLoader,因为安卓ART虚拟机执行的是.dex字节码,所以安卓中的ClassLoader和Java中的有所不同。来了解一下Android的类加载器。

1、ClassLoader

ClassLoader有两种类型:系统加载器和自定义加载器。系统加载器有三种:BootClassLoader、DexClassLoader、PathClassLoader。

1.1、BootClassLoader

Android系统启动是使用BootClassLoader加载器来加载常用的类。是Java实现,并非JVM中的BootClassLoader用C++实现。

源码路径:libcore/ojluni/src/main/java/java/lang/ClassLoader.java

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);
        }
        
        ...
    }

BootClassLoader是单例模式,而且该类定义在ClassLoader中,访问修饰符默认是包访问权限,普通应用程序无法调用。

1.2、DexClassLoader

DexClassLoader加载器用来加载.dex文件、包含dex的文件(Apk、Jar包)等。

源码路径:/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

public class DexClassLoader extends BaseDexClassLoader {
        /**
         * @param dexPath dex相关文件路径集合,多个路径用文件分隔符分割,默认文件分隔符为“:”
         * @param optimizedDirectory 解压dex文件存储路径,这个路径必须是一个内部存储路径,
         *                           一般情况下是当前应用程序的私有路径:/data/data/<PackageName>/...
         *                           自API级别26起,此参数已弃用,并且无效.
         * @param librarySearchPath C/C++库的路径集合,多个文件用文件分隔符分割,可为null
         * @param parent 父加载器
         */
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String librarySearchPath, ClassLoader parent) {
            super(dexPath, null, librarySearchPath, parent);
        }
    }

1.3、PathClassLoader

Android用PathClassLoader加载系统类和应用程序的类。

源码路径:/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java

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);
        }
        /**
         * @hide
         */
        @libcore.api.CorePlatformApi
        public PathClassLoader(
                String dexPath, String librarySearchPath, ClassLoader parent,
                ClassLoader[] sharedLibraryLoaders) {
            super(dexPath, librarySearchPath, parent, sharedLibraryLoaders);
        }
    }

2、ClassLoader关系

粗略的更具源码继承关系捋了一下各个ClassLoader之间的继承关系。



3、ClassLoader初始化

了解一下Android系统各个ClassLoader加载器的初始化过程。

3.1、BootClassLoader加载

在《Zyoget进程的启动》这篇中讲到Zygote进程的启动,在ZygoteInit的main()方法中有一行代码执行预加载:

@UnsupportedAppUsage
    public static void main(String argv[]) {
            ...
            // In some configurations, we avoid preloading resources and classes eagerly.
            // In such cases, we will preload things prior to our first fork.
            if (!enableLazyPreload) {
                ...
                preload(bootTimingsTraceLog);
                ...
            } else {
                Zygote.resetNicePriority();
            }
            ...
    }

preload(TimingsTraceLog bootTimingsTraceLog)中又调用了preloadClasses()方法:

static void preload(TimingsTraceLog bootTimingsTraceLog) {
        Log.d(TAG, "begin preload");
        ...
        preloadClasses();
        ...
        sPreloadComplete = true;
    }

preloadClasses()方法加载系统常用的类

/**
     * 执行Zygote进程初始化。 加载并初始化常用的类。
     * 大多数类仅会分配几百个字节,但有些会分配十几个KB(在一种情况下为500 + K)。
     */
    private static void preloadClasses() {
        final VMRuntime runtime = VMRuntime.getRuntime();
        InputStream is;
        try {
            is = new FileInputStream(PRELOADED_CLASSES);//1
        }
        ...
        try {
            BufferedReader br =
                    new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
            int count = 0;
            String line;
            while ((line = br.readLine()) != null) {
                ...
                try {
                    // Load and explicitly initialize the given class. Use
                    // Class.forName(String, boolean, ClassLoader) to avoid repeated stack lookups
                    // (to derive the caller's class-loader). Use true to force initialization, and
                    // null for the boot classpath class-loader (could as well cache the
                    // class-loader of this class in a variable).
                    Class.forName(line, true, null);
                    count++;
                }
            ...
    }

PRELOADED_CLASSES定义的是预加载类文件的目录。

/**
     * The path of a file that contains classes to preload.
     */
    private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";

在源码中的路径为/frameworks/base/config/preloaded-classes,数千行类名称:

#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Preloaded-classes filter file for phones.
# This file has been derived for mainline phone (and tablet) usage.
#
android.R$styleable
android.accessibilityservice.AccessibilityServiceInfo$1
...
android.animation.TimeInterpolator
...
java.sql.Date
...
libcore.io.Os
...
org.json.JSON
...
sun.util.logging.PlatformLogger

每读取一行类名,就通过Class.forName()加载该类。源码在/libcore/ojluni/src/main/java/java/lang/目录下

public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        if (loader == null) {
            loader = BootClassLoader.getInstance();//获取BootClassLoader实例
        }
        Class<?> result;
        try {
            result = classForName(name, initialize, loader);
        } catch (ClassNotFoundException e) {
            Throwable cause = e.getCause();
            if (cause instanceof LinkageError) {
                throw (LinkageError) cause;
            }
            throw e;
        }
        return result;
    }

可以看到是通过BootClassLoader实例加载系统的类,不继续跟下去了,后面就是Native层。

3.2、PathClassLoader加载

和BootClassLoader启动会不同,PathClassLoader在ZygoteInit的forkSystemServer()方法中开始初始化,在《SystemServer系统服务启动》有提到过该方法,API 29源码和之前不大一样。

/**
     * 准备系统服务器进程的参数和派生。
     */
    private static Runnable forkSystemServer(String abiList, String socketName,
            ZygoteServer zygoteServer) {
        ...
        /* For child process */
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }
            zygoteServer.closeServerSocket();
            return handleSystemServerProcess(parsedArgs);
        }
        return null;
    }

fork自身创建新的进程,pid≠0,最后执行handleSystemServerProcess()方法

/**
     * 完成fork系统进程之后的剩余各种工作
     */
    private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {
        // 将umask设置为0077,以便新文件和目录将默认为仅所有者权限。
        Os.umask(S_IRWXG | S_IRWXO);
        ...
        if (parsedArgs.mInvokeWith != null) {
            ...
        } else {
            createSystemServerClassLoader();  //1
            ClassLoader cl = sCachedSystemServerClassLoader; //2
            if (cl != null) {
                Thread.currentThread().setContextClassLoader(cl); //3
            }
            /*
             * 将其余参数传递给SystemServer。
             */
            return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
                    parsedArgs.mRemainingArgs, cl);
        }
        /* should never reach here */
    }

其中在createSystemServerClassLoader()方法初始化sCachedSystemServerClassLoader实例:

private static void createSystemServerClassLoader() {
        if (sCachedSystemServerClassLoader != null) {
            return;
        }
        final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
        // TODO: Should we run optimization here?
        if (systemServerClasspath != null) {
            sCachedSystemServerClassLoader = createPathClassLoader(systemServerClasspath,
                    VMRuntime.SDK_VERSION_CUR_DEVELOPMENT);
        }
    }

createPathClassLoader()方法创建PathClassLoader,在该方法的最后是通过ClassLoaderFactory工厂模式来创建PathClassLoader加载器。

static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
        String libraryPath = System.getProperty("java.library.path");
        // 使用BootClassLoader加载器,正是运行时AOT所期望的。
        ClassLoader parent = ClassLoader.getSystemClassLoader().getParent();
        return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,
                parent, targetSdkVersion, true /* isNamespaceShared */, null /* classLoaderName */);
    }

回到handleSystemServerProcess()方法,将ClassLoader加载器实例通过Thread的setContextClassLoader(cl)方法设置给当前进程:

/**
     * Sets the context ClassLoader for this Thread. The context
     * ClassLoader can be set when a thread is created, and allows
     * the creator of the thread to provide the appropriate class loader,
     * through {@code getContextClassLoader}, to code running in the thread
     * when loading classes and resources.
     *
     * @param  cl 此线程的上下文ClassLoader,或者为null,指示系统类加载器 (or, failing that, the bootstrap class loader)
     *
     * @throws  SecurityException 如果当前线程无法设置上下文ClassLoader
     *
     * @since 1.2
     */
    public void setContextClassLoader(ClassLoader cl) {
        // Android-removed: SecurityManager stubbed out on Android
        // SecurityManager sm = System.getSecurityManager();
        // if (sm != null) {
        //     sm.checkPermission(new RuntimePermission("setContextClassLoader"));
        // }
        contextClassLoader = cl;
    }

4、ClassLoader加载过程

Java的ClassLoader加载使用的是双亲委派模式,从ClassLoader的loadClass()方法开始

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

调用protected服务权限的loadClass(String name, boolean resolve)方法

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;
    }

该方法中先调用findLoadedClass(String name)方法判断要查找的类是否存在

protected final Class<?> findLoadedClass(String name) {
        ClassLoader loader;
        if (this == BootClassLoader.getInstance())
            loader = null;
        else
            loader = this;
        return VMClassLoader.findLoadedClass(loader, name);
    }

最后还是通过Native的findLoadedClass()方法来查找。

@FastNative
    native static Class findLoadedClass(ClassLoader cl, String name);

回到loadClass()方法中,标准的类加载模式:

①先判断类是否已经被加载,如果已经加载就直接返回

②先判断父类加载器是否存在,调用父类加载器loadClass()方法

③否则,调用findBootstrapClassOrNull(name)方法,但是该方法直接返回null

* Returns a class loaded by the bootstrap class loader;
     * or return null if not found.
     */
    private Class<?> findBootstrapClassOrNull(String name)
    {
        return null;
    }

④整个委托流程执行完毕,最后判断该类是否为null,在调用findClass()方法查找

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

ClassLoader中的该方法直接抛出ClassNotFoundException异常,它的实现在BaseDexClassLoader类加载器中

@Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // First, check whether the class is present in our shared libraries.
        if (sharedLibraryLoaders != null) {
            for (ClassLoader loader : sharedLibraryLoaders) {
                try {
                    return loader.loadClass(name);
                } catch (ClassNotFoundException ignored) {
                }
            }
        }
        // Check whether the class in question is present in the dexPath that
        // this classloader operates on.
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        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;
    }

通过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;
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

又调用Element的findClass()方法,Element是DexPathList的静态内部类

/**
     * Element of the dex/resource path. Note: should be called DexElement, but apps reflect on
     * this.
     */
    static class Element {
        ...     
    }

Element中的findClass()方法定如下:

public Class<?> findClass(String name, ClassLoader definingContext,
            List<Throwable> suppressed) {
        return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                : null;
    }

Element内部封装了DexFile,当其不为空时,通过loadClassBinaryName()方法加载dex

/**
     * See {@link #loadClass(String, ClassLoader)}.
     *
     * This takes a "binary" class name to better match ClassLoader semantics.
     *
     * @hide
     */
    @UnsupportedAppUsage
    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }

defineClass()的定义如下:

public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }
    private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            result = defineClassNative(name, loader, cookie, dexFile);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }

发现最终调用Native方法defineClassNative()加载.dex文件。

参考资料:

        Android进阶解密

        AOSP

        Android ClassLoader机制

        Android解析ClassLoader

Android中的ClassLoader

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w