【Android Tip 01】ClassLoader、Android Framework ClassLoader、App ClassLoader

       注:基础知识学习积累所用,Java部分参考网上资料,转载请注明来自Nemo, http://blog.csdn.net/nemo__

        包名:java.lang.ClassLoader
        类加载器(class loader)用来加载 Java类到Java虚拟机中。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里运行。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
        类加载器是Java语言的一个创新,也是 Java 语言流行的重要原因之一。它使得Java类可以被动态加载到Java虚拟机中并执行。Java应用的开发人员不需要直接同类加载器进行交互。Java 虚拟机默认的行为就已经足够满足大多数情况的需求了。不过如果遇到了需要与类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就很容易花大量的时间去调试 ClassNotFoundException和 NoClassDefFoundError等异常。
        JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,BootstrapClassLoader是用本地代码实现的,它负责加载核心JavaClass(即所有java.*开头的类)。另外JVM还会提供两个ClassLoader,它们都是用Java语言编写的,由BootstrapClassLoader加载;其中Extension ClassLoader负责加载扩展的Javaclass(例如所有javax.*开头的类和存放在JRE的ext目录下的类),ApplicationClassLoader负责加载应用程序自身的类。   
        当运行一个程序的时候,JVM启动,运行bootstrapclassloader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。

1. java.lang.ClassLoader类

        java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。

方法 说明
getParent() 返回该类加载器的父类加载器。
loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
resolveClass(Class<?> c) 链接指定的 Java 类。
        上面的方法中,表示类名称的 name参数的值是类的二进制名称。需要注意的是内部类的表示,如 com.example.Sample$1和 com.example.Sample$Inner等表示方式。


2. 类加载器的树状组织结构

       Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:

    - 引导类加载器(bootstrap class loader): 它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader
    - 扩展类加载器(extensions class loader): 它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类, (sun.misc.Launcher$ExtClassLoader)。
    - 系统类加载器(system class loader): 它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它, (sun.misc.Launcher$AppClassLoader)。

       除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

       除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过getParent()方 法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加 载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根 节点就是引导类加载器。一个典型的类加载器树状组织结构示意图,其中的箭头指向的是父类加载器。

         


3. 类加载器的代理模式

        类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如何判定两个Java类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加 载器加载之后所得到的类,也是不同的。比如一个 Java 类 com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器 ClassLoaderA和 ClassLoaderB分别读取了这个 Sample.class文件,并定义出两个 java.lang.Class类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastException。

        了解了这一点之后,就可以理解代理模式的设计动机了。代理模式是为了保证 Java 核心库的类型安全。所有Java应用都至少需要引用java.lang.Object类,也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。如果这个加载过程由Java应用自己的类加载器来完成的话,很可能就存在多个版本的java.lang.Object类,而且这些类之间是不兼容的。通过代理模式,对于Java核心库的类的加载工作由引导类加载器来统一完成,保证了Java应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。

        不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在Java虚拟机内部创建了一个个相互隔离的Java类空间。这种技术在许多框架中都被用到。


4. 加载类的过程

        在前面介绍类加载器的代理模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用defineClass()来实现的(定义加载器);而启动类的加载过程是通过调用loadClass()来实现的(初始加载器)。前者称为一个类的定义加载器(defining loader), 后者称为初始加载器(initiating loader)。在Java虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类 com.example.Outer引用了类 com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是 java.lang.NoClassDefFoundError异常。类加载器在成载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用。

        Java装载类使用“全盘负责委托机制”。“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入;“委托机制”是指先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。这一点是从安全方面考虑的,试想如果一个人写了一个恶意的基础类(如java.lang.String)并加载到JVM将会引起严重的后果,但有了全盘负责制,java.lang.String永远是由根装载器来装载,避免以上情况发生 除了JVM默认的三个ClassLoder以外,第三方可以编写自己的类装载器,以实现一些特殊的需求。类文件被装载解析后,在JVM中都有一个对应的java.lang.Class对象,提供了类结构信息的描述。数组,枚举及基本数据类型,甚至void都拥有对应的Class对象。Class类没有public的构造方法,Class对象是在装载类时由JVM通过调用类装载器中的defineClass()方法自动构造的。

        类java.lang.Thread中的方法getContextClassLoader()和setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl) 方法进行设置的话,线程将继承其父线程的上下文类加载器。Java应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。

       类装载方式,有两种:

        1.隐式装载, 程序在运行过程中当碰到通过new等方式生成对象时,隐式调用类装载器加载对应的类到jvm中。 

        2.显式装载, 通过class.forname()等方法,显式加载需要的类。

        类加载的动态性:

        一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的 基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵, 这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现。


5. Framework ClassLoader

BOOTCLASSPATH简介
        1.BOOTCLASSPATH是Android Linux的一个环境变量,可以在adb shell下用$BOOTCLASSPATH看到。
        2.BOOTCLASSPATH于/init.rc文件中export,如果没有找到的话,可以在init.rc中import的文件里找到(如import /init.environ.rc)。

        手机的BOOTCLASSPATH:

export BOOTCLASSPATH /system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar: /system/framework/core-junit.jar :/system/framework/bouncycastle.jar :/system/framework/ext.jar :/system/framework/framework.jar......
export SYSTEMSERVERCLASSPATH /system/framework/services.jar :/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar

        Zygote会在启动后创建Dalvik虚拟机实例(android/dalvik/vm/Init.cpp),通过setCommandLineDefaults()读取环境变量BOOTCLASSPATH,紧接着通过dvmClassStartup()初始化bootstrap classloader,形成基本的Android开发环境。


6. App ClassLoader

    从Instrumentation一文可知,四大组件启动最后都会走到ActivityThread内,通过ClassLoader来找到App中真正要启动的组件Class, 然后把它加载到内存中,这里的ClassLoader都是通过LoadedApk的getClassLoader得到的

    而还在前面应用启动时,会查找Application对象,走到handleBindApplication()方法,其中data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo)会new出当前apk对应的LoadedApk, 最终是在LoadedApk中调用makeApplication()方法:

(1) [makeApplication(boolean, Instrumentation)]

public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
        Application app = null;
        ......
        try {
            java.lang.ClassLoader cl = getClassLoader();
            if (!mPackageName.equals("android")) {
                initializeJavaContextClassLoader();
            }
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
        }
        ......
}
再看LoadedApk类的getClassLoader()方法:
public ClassLoader getClassLoader() {
        synchronized (this) {
            if (mClassLoader != null) {
                return mClassLoader;
            }

            if (mIncludeCode && !mPackageName.equals("android")) {
                ......
                mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,
                        mBaseClassLoader);
            } else {
                if (mBaseClassLoader == null) {
                    mClassLoader = ClassLoader.getSystemClassLoader();
                } else {
                    mClassLoader = mBaseClassLoader;
                }
            }
            return mClassLoader;
        }
    }

应用第一次创建时,会走到第一个分支,通过ApplicationLoaders.getDefault().getClassLoader()方法创建当前apk的ClassLoader并保存在mClassLoader中,以后再要用ClassLoader时都会返回mClassLoader.

    ApplicationLoaders类只在LoadedApk这一个地方用到,现在看看这个类的实现:

a. 通过static方法getDefault()可获取:
private static final ApplicationLoaders gApplicationLoaders
b. 以apk路径为key, 以生成的ClassLoader为value的Map, 对一个apk路径,生成一次后,后面再使用直接从mLoaders中获取:
private final ArrayMap<String, ClassLoader> mLoaders;
c. 获取以zip为apk路径,libPath为so库路径的ClassLoader:
public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent) {
        //bootstrap class loader.
        ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();

        synchronized (mLoaders) {
            if (parent == null) {
                parent = baseParent;
            }

            if (parent == baseParent) {
                ClassLoader loader = mLoaders.get(zip);
                if (loader != null) {
                    return loader;
                }

                PathClassLoader pathClassloader = new PathClassLoader(zip, libPath, parent);
                
                mLoaders.put(zip, pathClassloader);
                return pathClassloader;
            }

            PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
            return pathClassloader;
        }
}

      可以看到,每个应用在启动时,走到handleBindApplication()方法调用的getPackageInfoNoCheck(), 而它传给private的6个参数的getPackageInfo()方法时,传入的baseClassLoder是null, 这样通过new生成的LoadedApk赋给 mBaseClassLoader的是null, 所以纵观所有给mBaseClassLoader赋值的情况,在LoadedApk中mBaseClassLoader总是null, 这样在LoadedApk中调用ApplicationLoaders.getDefault(). getClassLoader()方法时,第三个parent参数一直为null.

(2) [Activity启动]

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }
    ......
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
         } catch {
         }
    ......
}
        这里packageInfo是一个LoadedApk, 通过它的getClassLoader()方法,得到当前要启动的Activity的ClassLoader, 而getPackageInfo()方法会传递这个LoadedApk实例。

(3) [Service启动]

private void handleCreateService(CreateServiceData data) {
        ......
        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        Service service = null;
        try {
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            service = (Service) cl.loadClass(data.info.name).newInstance();
        } catch (Exception e) {
        }
        ......
}

        getPackageInfoNoCheck()最后调用的也是getPackageInfo方法,生成的packageInfo是LoadedApk实例。

(4) [sendBroadcast()方法]

private void handleReceiver(ReceiverData data) {
        ......
        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);

        IActivityManager mgr = ActivityManagerNative.getDefault();

        BroadcastReceiver receiver;
        try {
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
             receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();
        }
        ......
}
(5) [数据库查询最后installProvider()]
private IActivityManager.ContentProviderHolder installProvider(Context context,
            IActivityManager.ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
            ContentProvider localProvider = null;
            ......
            Context c = null;
            ApplicationInfo ai = info.applicationInfo;
            if (context.getPackageName().equals(ai.packageName)) {
                c = context;
            } else if (mInitialApplication != null &&
                    mInitialApplication.getPackageName().equals(ai.packageName)) {
                c = mInitialApplication;
            } else {
                try {
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }
            if (c == null) {
                return null;
            }
            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                localProvider = (ContentProvider)cl.loadClass(info.name).newInstance();
            }
        ......
}

结论:
1. 每个App进程可能有多个apk(ActivityThread中的成员 ArrayMap<String, WeakReference<LoadedApk>> mPackages),  每个apk对应一个LoadedApk实例, 每个 LoadedApk 有一个指向它对应apk路径的PathClassLoader.
2. 每个App进程有唯一的ApplicationLoaders实例用来管理所有的ClassLoader,一个LoadedApk的ClassLoader一经创建,会保存在ApplicationLoaders实例中,后续通过apk路径查询返回。
3. Android Framework ClassLoder是bootstrap classloader, App ClassLoader是以它为parent的PathClassLoader.
4. Android App开发可以看成编写Application及四大组件,和围绕着他们工作的工具类。五种组件最后在ActivityThread中均是通过ClassLoader以loadClass方式找到,由于ClassLoader的"全盘负责"机制,围绕着组件的类也由组件创建的ClassLoader查找到。




  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值