Android类加载机制学习

来源:互联网 发布:bugs软件 编辑:程序博客网 时间:2024/06/07 01:03

Android类加载机制学习

1、Android类加载机制

Android系统的Dalvik虚拟机如同其他Java虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。

在Java标准的虚拟机中,类加载可以从.class文件中读取,也可以是其他形式的二进制流,因此,Java程序开发常常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的。

然而Dalvik虚拟机毕竟不算是标准的Java虚拟机,因此在类加载机制上,他们是不同的。例如,Android的类加载不能直接从.class文件中读取,而是从.dex文件中读取;此外,在使用标准Java虚拟机时,我们经常自定义继承自ClassLoader的类加载器。然后通过defineClass()方法来从一个二进制流中加载Class。然而,这在Android里是行不通的,参看源码我们知道,Android中ClassLoader的defineClass()方法除了抛出一个“UnsupportedOperationException”之外,什么都没做。

• Dalvik虚拟机类加载机制

在Dalvik虚拟机里,defineClass()方法被弃用,我们如何实现动态加载类呢?

Android为我们从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader,并且在ClassLoader中定义了loadClass()方法。

DexClassLoader和PathClassLoader这两个继承自BaseDexClassLoader,BaseDexClassLoader继承自ClassLoader,BaseDexClassLoader本质上是覆写了ClassLoader的findClass()方法。Android中,ClassLoader用loadClass()方法来加载我们需要的类,我们可以参看ClassLoader部分源码:

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {    //检查本ClassLoader是否已经加载    Class<?> clazz = findLoadedClass(className);    //如果本ClassLoader没有加载    if (clazz == null) {        ClassNotFoundException suppressed = null;        //从父ClassLoader中查找        try {            clazz = parent.loadClass(className, false);        } catch (ClassNotFoundException e) {            suppressed = e;        }        if (clazz == null) {            //调用findClass来加载            try {                clazz = findClass(className);            } catch (ClassNotFoundException e) {                e.addSuppressed(suppressed);                throw e;            }        }    }    return clazz;}

DexClassLoader和PathClassLoader都属于符合双亲委派模型的类加载器(因为它们没有重载loadClass()方法)。

其特点如下:

  • 会先查询当前ClassLoader实例是否加载过此类,有就返回

  • 如果没有,查询Parent是否已经加载过此类,如果已经加载过,就直接返回Parent加载的类

  • 如果parent路线上的ClassLoader都没有加载,才调用findClass执行类的加载工作

ClassLoader与PathClassLoader和DexClassLoader的类图关系如下图所示:

• BaseDexClassLoader的findClass加载类过程

findClass加载类过程示意图如下:

findClass方法类加载的过程如下:

1、生成Element数组dexElements,生成pathList

BaseDexClassLoader的构造方法如下:

public BaseDexClassLoader(String dexPath, File optimizedDirectory,        String libraryPath, ClassLoader parent) {    super(parent);    this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);}

BaseDexClassLoader中的构造方法包含4个参数,他们分别是:

(1) dexPath : 指目标类所在的APK或jar文件的路径.类装载器将从该路径中寻找指定的目标类,该类必须是APK或jar的全路径.如果要包含多个路径,路径之间必须使用特定的分割符分隔,分隔符为File.pathSeparator(“:”).

(2) optimizedDirectory : 由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是指定解压出的dex文件存放的路径.

(3) libraryPath : 指目标类中所使用的C/C++库存放的路径

(4) parent : 是指该装载器的父装载器

在BaseDexClassLoader中生成DexPathList类的对象pathList,在DexPathList的构造方法中调用DexPathList类中的makePathElements()方法,在makePathElements()方法中会调用splitDexPath()方法,如果dexPath包含多个路径,splitDexPath()方法能够提取出File.pathSeparator分隔的多个路径,并将多个路径放入List中返回,然后makePathElements()方法会调用loadDexFile()方法遍历List中每个包含dex的文件生成DexFile,用生成的DexFile生成Element对象,用这些Element对象构成Element数组dexElements。

DexPathList.java中的loadDexFile()方法源码如下:

private static DexFile loadDexFile(File file, File optimizedDirectory)        throws IOException {    if (optimizedDirectory == null) {        return new DexFile(file);    } else {        String optimizedPath = optimizedPathFor(file, optimizedDirectory);        return DexFile.loadDex(file.getPath(), optimizedPath, 0);    }}

一个应用程序中所有类加载到内存中,就是在makePathElements执行loadDexFile()方法来实现的。

optimizedDirectory是一个内部存储路径,用来缓存我们需要加载的dex文件的。DexClassLoader可以指定自己的optimizedDirectory,所以它可以加载外部的dex,因为这个dex会被复制到内部路径的optimizedDirectory;而PathClassLoader没有optimizedDirectory,所以它只能加载内部的dex,这些大都是存在系统中已经安装过的apk里面的。

它们的不同之处总结如下是:

  • DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;

  • PathClassLoader只能加载系统中已经安装过的apk,Android中大部分类的加载默认采用此类

2、pathList调用BaseDexClassLoader中的findClass()方法

调用BaseDexClassLoader的findClass()方法,在此方法,pathList调用DexPathList中的findClass()方法,BaseDexClassLoader的findClass()方法如下:

@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {    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;}

3、调用DexPathList中的findClass()方法

pathList是DexPathList类型的一个对象,DexPathList中的findClass()方法源码如下:

public Class findClass(String name, List<Throwable> suppressed) {    for (Element element : dexElements) {        DexFile dex = element.dexFile;        if (dex != null) {            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);            if (clazz != null) {                return clazz;            }        }    }    if (dexElementsSuppressedExceptions != null) {        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));    }    return null;}

DexPathList的findClass()方法,遍历dexElements元素的DexFile实例,也就是遍历所有加载过的dex文件,一个个调用loadClassBinaryName()方法,看能不能找到我们想要的类,如果可以,返回这个类。

DexFile.java中的loadClassBinaryName()方法:

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

4、返回

将加载的类返回ClassLoader类的loadClass()方法。

总结

当加载一个类的时候,需要首先获取类加载器,调用类加载器的loadClass方法,在loadClass方法中先查询当前ClassLoader实例是否加载过此类,有就返回;如果没有,查询parent指定的类加载器是否已经加载过此类,如果已经加载过,就直接返回parent加载的类;如果parent路线上的ClassLoader都没有加载,才调用findClass执行类的加载工作。BaseDexClassLoader的findClass方法调用之前,首先在BaseDexClassLoader的构造方法中生成了DexPathList类的对象pathList,在DexPathList类的构造方法中,利用loadDexFile()方法将dex文件中的类加载到内存中,并生成对应的DexFile对象,进而生成Element对象,用这些Element对象构成Element数组dexElements。在BaseDexClassLoader的findClass方法中,调用了DexPathList类的findClass方法,在DexPathList类的findClass方法里,遍历dexElements元素的DexFile实例,一个个调用loadClassBinaryName()方法,看能不能找到我们想要的类,如果可以,返回这个类。

2、类加载器的生成

在应用程序第一次启动时,都会首先创建Application对象。会调用ActivityThread.java中的handleBindApplication()方法,handleBindApplication()方法调用getPackageInfoNoCheck()方法生成LoadedApk对象data.info,它们的源代码如下:

ActivityThread.java中的handleBindApplication()方法:

private void handleBindApplication(AppBindData data) {    LoadedApk data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);    ......       //调用LoadedApk.java中makeApplication()方法    Application app = data.info.makeApplication(data.restrictedBackupMode, null);    ......         }

ActivityThread.java中getPackageInfoNoCheck()方法:

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) {    return getPackageInfo(ai, compatInfo, null, false, true, false);}

getPackageInfoNoCheck()方法调用了getPackageInfo()方法,getPackageInfo()方法的源代码如下:

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,ClassLoader baseLoader,         boolean securityViolation, boolean includeCode,boolean registerPackage) {    final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));    synchronized (mResourcesManager) {        WeakReference<LoadedApk> ref;        if (differentUser) {            // Caching not supported across users            ref = null;        } else if (includeCode) {            //            ref = mPackages.get(aInfo.packageName);        } else {            ref = mResourcePackages.get(aInfo.packageName);        }        LoadedApk packageInfo = ref != null ? ref.get() : null;        if (packageInfo == null || (packageInfo.mResources != null                    && !packageInfo.mResources.getAssets().isUpToDate())) {            if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "                        : "Loading resource-only package ") + aInfo.packageName                        + " (in " + (mBoundApplication != null                                ? mBoundApplication.processName : null)                        + ")");            //生成LoadedApk类的对象packageInfo            packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader,                            securityViolation, includeCode &&                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);            if (mSystemThread && "android".equals(aInfo.packageName)) {                packageInfo.installSystemApplicationInfo(aInfo,                            getSystemContext().mPackageInfo.getClassLoader());            }            if (differentUser) {                // Caching not supported across users            } else if (includeCode) {                //将生成的packageInfo存入到WeakReference<LoadedApk> mPackages中                mPackages.put(aInfo.packageName,                            new WeakReference<LoadedApk>(packageInfo));            } else {                mResourcePackages.put(aInfo.packageName,                            new WeakReference<LoadedApk>(packageInfo));            }        }        return packageInfo;    }}

第一次调用getPackageInfo()方法,WeakReference mPackages为null,利用new LoadedApk()生成LoadedApk类的对象packageInfo,并将生成的packageInfo存入到WeakReference mPackages中。当第二次调用getPackageInfo()方法时,WeakReference mPackages不为null,直接返回WeakReference mPackages中存储的LoadedApk对象。可以看到同一个应用程序中,类加载所使用的LoadedApk对象都是同一个。

生成LoadedApk对象data.info后,在ActivityThread.java中的handleBindApplication()方法中调用了makeApplication()方法生成Application类的对象,makeApplication()方法在LoadedApk.java文件中定义的,其源码为:

public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {    try {        java.lang.ClassLoader cl = getClassLoader();        ......        //调用ContextImpl类的createAppContext()方法        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);        app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);        appContext.setOuterContext(app);    } catch (Exception e) {        if (!mActivityThread.mInstrumentation.onException(app, e)) {                throw new RuntimeException(                    "Unable to instantiate application " + appClass                    + ": " + e.toString(), e);        }    }    return app;}

Instrumentation.java中newApplication()方法:

public Application newApplication(ClassLoader cl, String className, Context context)            throws InstantiationException, IllegalAccessException,            ClassNotFoundException {    return newApplication(cl.loadClass(className), context);}static public Application newApplication(Class<?> clazz, Context context)            throws InstantiationException, IllegalAccessException,            ClassNotFoundException {    Application app = (Application)clazz.newInstance();    app.attach(context);     return app;}

可见,调用newApplication()方法之前,第一次调用了LoadedApk的getClassLoader()方法,其源码如下:

public ClassLoader getClassLoader() {    synchronized (this) {        if (mClassLoader != null) {            return mClassLoader;        }        if (mIncludeCode && !mPackageName.equals("android")) {            ......            //调用了ApplicationLoaders.java里面的getClassLoader()方法            mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,mBaseClassLoader);            StrictMode.setThreadPolicy(oldPolicy);        } else {            if (mBaseClassLoader == null) {                mClassLoader = ClassLoader.getSystemClassLoader();            } else {                mClassLoader = mBaseClassLoader;            }        }        return mClassLoader;    }}

当第一次调用LoadedApk的getClassLoader()方法,mClassLoader==null,会调用ApplicationLoaders.java里面的getClassLoader()方法生成mClassLoader;当再次调用LoadedApk的getClassLoader()方法,mClassLoader!=null,则将第一次生成的mClassLoader直接返回。可见,getClassLoader()返回的ClassLoader也是同一个。

调用了ApplicationLoaders.java里面的getClassLoader()方法,源码如下:

public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent){    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;            }            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);            PathClassLoader pathClassloader = new PathClassLoader(zip, libPath, parent);            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);            mLoaders.put(zip, pathClassloader);            return pathClassloader;        }        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);        PathClassLoader pathClassloader = new PathClassLoader(zip, parent);        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);        return pathClassloader;    }}

在这个方法中调用了生成了PathClassLoader对象,根据前面的讲解,可以知道在PathClassLoader的构造方法中,调用了makePathElements()方法,完成了所有的类加载到内存中。这个PathClassLoader就是我们应用程序中类加载所用的类加载器。

总结

类加载器生成的流程图:

在应用程序启动时,会创建Application对象,在创建Application的过程中,完成了应用程序中所有类的加载到内存。当再使用这个程序中其他类的时候,再次调用ActivityThread.java中的getPackageInfo()方法,此方法不会再new一个新的LoadedApk对象,而是返回创建Application时的那个LoadedApk对象,即同一个LoadedApk对象,用这个对象调用getClassLoader获得的加载器也都是同一个,不会重复加载类,生成类的实例的形式一般为:

getClassLoader().loadClass(className).newInstance();

Android常用的类包括Activity、Service、BroadcastReceiver、Fragment,都采用了同样的方式用同一个类加载器生成类的实例。

比如:

① 使用Fragment时,在Fragment.java文件的instantiate()方法中调用了loadClass()方法。

/** * Create a new instance of a Fragment with the given class name.  This is * the same as calling its empty constructor. */public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {    try {        Class<?> clazz = sClassMap.get(fname);        if (clazz == null) {            // Class not found in the cache, see if it's real, and try to add it            clazz = context.getClassLoader().loadClass(fname);            if (!Fragment.class.isAssignableFrom(clazz)) {                throw new InstantiationException("Trying to instantiate a class "                 + fname + " that is not a Fragment", new ClassCastException());            }            sClassMap.put(fname, clazz);        }        //生成Fragment类的单例        Fragment f = (Fragment)clazz.newInstance();        if (args != null) {            args.setClassLoader(f.getClass().getClassLoader());            f.mArguments = args;        }        return f;    } catch (ClassNotFoundException e) {    } catch (java.lang.InstantiationException e) {    } catch (IllegalAccessException e) {    }}

② 在使用BroadcastReceiver时,在ActivityThread.java的handleReceiver()方法中调用了loadClass()方法。

private void handleReceiver(ReceiverData data) {    // If we are getting ready to gc after going to the background, well    // we are back active so skip it.    unscheduleGcIdler();    String component = data.intent.getComponent().getClassName();    LoadedApk packageInfo = getPackageInfoNoCheck(                data.info.applicationInfo, data.compatInfo);    IActivityManager mgr = ActivityManagerNative.getDefault();    BroadcastReceiver receiver;    try {        java.lang.ClassLoader cl = packageInfo.getClassLoader();        data.intent.setExtrasClassLoader(cl);        data.intent.prepareToEnterProcess();        data.setExtrasClassLoader(cl);        receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();    } catch (Exception e) {        ......    }    ......}

③ 在使用Service时,在ActivityThread.java的handleCreateService()方法中调用了loadClass()方法。

private void handleCreateService(CreateServiceData data) {    // If we are getting ready to gc after going to the background, well    // we are back active so skip it.    unscheduleGcIdler();    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) {        ......    }    ......}

④ 在Activity的使用过程中,在Instrumentation.java的newActivity()方法中调用了loadClass()方法。

public Activity newActivity(ClassLoader cl, String className,        Intent intent)throws InstantiationException, IllegalAccessException,        ClassNotFoundException {    return (Activity)cl.loadClass(className).newInstance();}

这些类调用getClassLoader方法所获得的类加载器是同一个,用loadClass获取到对应的Class,采用newInstance()生成实例。

2 0