android加载dex方法,android Dex文件的加载

android加载dex方法,android Dex文件的加载上篇文章讲到了apk的分包,通过multidex构建出包含多个dex文件的apk,从而解决65536的方法数限制问题《AndroidDex分包》。在dalvik虚拟机上,应用启动时只会加载主dex文件,而从dex需要我们手动去加载,那么问题来了,如何手动加载一个dex文件?前面也提到了,使用DexClassLoader和PathClassLoader。DexClassLoader和PathCla…

大家好,又见面了,我是你们的朋友全栈君。

上篇文章讲到了apk的分包,通过multidex构建出包含多个dex文件的apk,从而解决65536的方法数限制问题《Android Dex分包》。

在dalvik虚拟机上,应用启动时只会加载主dex文件,而从dex需要我们手动去加载,那么问题来了,如何手动加载一个dex文件?前面也提到了,使用DexClassLoader和PathClassLoader。

DexClassLoader和PathClassLoader

android加载dex、jar、apk主要是通过DexClassLoader或者PathClassLoader来实现

下面先看一下DexClassLoader的实现

/**

* A class loader that loads classes from {@code .jar} and {@code .apk} files

* containing a {@code classes.dex} entry. This can be used to execute code not

* installed as part of an application.

*

*

This class loader requires an application-private, writable directory to

* cache optimized classes. Use {@code Context.getDir(String, int)} to create

* such a directory:

   {@code

* File dexOutputDir = context.getDir(“dex”, 0);

* }

*

*

Do not cache optimized classes on external storage.

* External storage does not provide access controls necessary to protect your

* application from code injection attacks.

*/

public class DexClassLoader extends BaseDexClassLoader {

/**

* Creates a {@code DexClassLoader} that finds interpreted and native

* code. Interpreted classes are found in a set of DEX files contained

* in Jar or APK files.

*

*

The path lists are separated using the character specified by the

* {@code path.separator} system property, which defaults to {@code :}.

*

* @param dexPath the list of jar/apk files containing classes and

* resources, delimited by {@code File.pathSeparator}, which

* defaults to {@code “:”} on Android

* @param optimizedDirectory directory where optimized dex files

* should be written; must not be {@code null}

* @param libraryPath the list of directories containing native

* libraries, delimited by {@code File.pathSeparator}; may be

* {@code null}

* @param parent the parent class loader

*/

public DexClassLoader(String dexPath, String optimizedDirectory,

String libraryPath, ClassLoader parent) {

super(dexPath, new File(optimizedDirectory), libraryPath, parent);

}

}

代码很简单,只有一个构造方法,调用了父类的构造方法,

参数dexPath为dex、jar、apk文件的路径,多个路径之间用:分隔

optimizedDirectory: dex文件首次加载时会进行dexopt操作,optimizedDirectory即为优化后的odex文件的存放目录,不允许为空,官方推荐使用应用私有目录来缓存优化后的dex文件,dexOutputDir = context.getDir(“dex”, 0);

libraryPath:动态库的路径,可以为空

parent:ClassLoader类型的参数,当前类加载器的父加载器

再来看看PathClassLoader的源码实现

/**

* Provides a simple {@link ClassLoader} implementation that operates on a list

* of files and directories in the local file system, but does not attempt to

* load classes from the network. Android uses this class for its system class

* loader and for its application class loader(s).

*/

public class PathClassLoader extends BaseDexClassLoader {

public PathClassLoader(String dexPath, ClassLoader parent) {

super(dexPath, null, null, parent);

}

public PathClassLoader(String dexPath, String libraryPath,

ClassLoader parent) {

super(dexPath, null, libraryPath, parent);

}

}

可以看到android系统采用PathClassLoader作为其系统加载器以及应用加载器.PathClassLoader 和DexClassLoader的区别就在于optimizedDirectory参数是否为空,关于optimizedDirectory的作用,接着往下看.

DexClassLoader、PathClassLoader都是继承自BaseDexClassLoader,而BaseDexClassLoader又继承自ClassLoader

/**

* Base class for common functionality between various dex-based

* {@link ClassLoader} implementations.

*/

public class BaseDexClassLoader extends ClassLoader {

private final DexPathList pathList;

public BaseDexClassLoader(String dexPath, File optimizedDirectory,

String libraryPath, ClassLoader parent) {

super(parent);

this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

}

//省略其他代码…

}

BaseDexClassLoader继承自ClassLoader,构造方法中先调用父类ClassLoader的构造方法,然后初始化了DexPathList对象,再来看看DexPathList的构造方法

public DexPathList(ClassLoader definingContext, String dexPath,

String libraryPath, File optimizedDirectory) {

if (definingContext == null) {

throw new NullPointerException(“definingContext == null”);

}

if (dexPath == null) {

throw new NullPointerException(“dexPath == null”);

}

if (optimizedDirectory != null) {

if (!optimizedDirectory.exists()) {

throw new IllegalArgumentException(

“optimizedDirectory doesn’t exist: “

+ optimizedDirectory);

}

if (!(optimizedDirectory.canRead()

&& optimizedDirectory.canWrite())) {

throw new IllegalArgumentException(

“optimizedDirectory not readable/writable: “

+ optimizedDirectory);

}

}

this.definingContext = definingContext;

ArrayList suppressedExceptions = new ArrayList();

this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,

suppressedExceptions);

if (suppressedExceptions.size() > 0) {

this.dexElementsSuppressedExceptions =

suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);

} else {

dexElementsSuppressedExceptions = null;

}

this.nativeLibraryDirectories = splitLibraryPath(libraryPath);

}

DexPathList的构造方法先对传入的参数进行校验,然后调用makeDexElements解析出dex相关参数,并保存到dexElements成员变量中,再来看makeDexElements方法

/**

* Makes an array of dex/resource path elements, one per element of

* the given array.

*/

private static Element[] makeDexElements(ArrayList files, File optimizedDirectory,

ArrayList suppressedExceptions) {

ArrayList elements = new ArrayList();

/*

* Open all files and load the (direct or contained) dex files

* up front.

*/

for (File file : files) {

File zip = null;

DexFile dex = null;

String name = file.getName();

if (name.endsWith(DEX_SUFFIX)) {

// Raw dex file (not inside a zip/jar).

try {

dex = loadDexFile(file, optimizedDirectory);

} catch (IOException ex) {

System.logE(“Unable to load dex file: ” + file, ex);

}

} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)

|| name.endsWith(ZIP_SUFFIX)) {

zip = file;

try {

dex = loadDexFile(file, optimizedDirectory);

} catch (IOException suppressed) {

/*

* IOException might get thrown “legitimately” by the DexFile constructor if the

* zip file turns out to be resource-only (that is, no classes.dex file in it).

* Let dex == null and hang on to the exception to add to the tea-leaves for

* when findClass returns null.

*/

suppressedExceptions.add(suppressed);

}

} else if (file.isDirectory()) {

// We support directories for looking up resources.

// This is only useful for running libcore tests.

elements.add(new Element(file, true, null, null));

} else {

System.logW(“Unknown file type for: ” + file);

}

if ((zip != null) || (dex != null)) {

elements.add(new Element(file, false, zip, dex));

}

}

return elements.toArray(new Element[elements.size()]);

}

files为dex文件的file对象list,判断是dex文件之后调用loadDexFile方法加载dex文件,返回DexFile对象。

/**

* Constructs a {@code DexFile} instance, as appropriate depending

* on whether {@code optimizedDirectory} is {@code null}.

*/

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

}

}

该方法对optimizedDirectory参数有区分处理,即DexClassLoader和PathClassLoader的区别就在这,若optimizedDirectory为空(采用PathClassLoader),直接返回DexdFile对象,若不为空(采用DexClassLoader),则先调用optimizedPathFor方法获取dex文件优化后存放的目录,如果不是dex文件则将后缀替换为 .dex结尾,最后又调用了DexFile.loadDex静态方法返回了DexFile对象。

/**

* Converts a dex/jar file path and an output directory to an

* output file path for an associated optimized dex file.

*/

private static String optimizedPathFor(File path,

File optimizedDirectory) {

String fileName = path.getName();

if (!fileName.endsWith(DEX_SUFFIX)) {

int lastDot = fileName.lastIndexOf(“.”);

if (lastDot < 0) {

fileName += DEX_SUFFIX;

} else {

StringBuilder sb = new StringBuilder(lastDot + 4);

sb.append(fileName, 0, lastDot);

sb.append(DEX_SUFFIX);

fileName = sb.toString();

}

}

File result = new File(optimizedDirectory, fileName);

return result.getPath();

}

static public DexFile loadDex(String sourcePathName, String outputPathName,

int flags) throws IOException {

/*

* TODO: we may want to cache previously-opened DexFile objects.

* The cache would be synchronized with close(). This would help

* us avoid mapping the same DEX more than once when an app

* decided to open it multiple times. In practice this may not

* be a real issue.

*/

return new DexFile(sourcePathName, outputPathName, flags);

}

可以看到DexFile.loadDex方法直接调用了DexFile的构造方法

//PathClassLoader调用

public DexFile(File file) throws IOException {

this(file.getPath());

}

public DexFile(String fileName) throws IOException {

mCookie = openDexFile(fileName, null, 0);

mFileName = fileName;

guard.open(“close”);

//System.out.println(“DEX FILE cookie is ” + mCookie);

}

//DexClassLoader调用

private DexFile(String sourceName, String outputName, int flags) throws IOException {

if (outputName != null) {

try {

String parent = new File(outputName).getParent();

if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {

throw new IllegalArgumentException(“Optimized data directory ” + parent

+ ” is not owned by the current user. Shared storage cannot protect”

+ ” your application from code injection attacks.”);

}

} catch (ErrnoException ignored) {

// assume we will fail with a more contextual error later

}

}

mCookie = openDexFile(sourceName, outputName, flags);

mFileName = sourceName;

guard.open(“close”);

//System.out.println(“DEX FILE cookie is ” + mCookie);

}

所以BaseDexClassLoader和PathClassLoader最终都是调用了openDexFile方法,唯一的区别就是outputName是否为空,即优化后dex保存的路径,

/*

* Open a DEX file. The value returned is a magic VM cookie. On

* failure, an IOException is thrown.

*/

private static int openDexFile(String sourceName, String outputName,

int flags) throws IOException {

return openDexFileNative(new File(sourceName).getCanonicalPath(),

(outputName == null) ? null : new File(outputName).getCanonicalPath(),

flags);

}

native private static int openDexFileNative(String sourceName, String outputName,

int flags) throws IOException;

在native方法中对其进行了判断,如果outputName为空,则自动生成一个缓存目录,即/data/dalvik-cache/xxx@classes.dex。所以DexClassLoader与PathClassLoader的本质区别,就是DexClassLoader可以指定odex的路径,而PathClassLoader则采用系统默认的缓存路径。

所以一般PathDexClassLoader只能加载已安装的apk的dex,而DexClassLoader则可以加载指定路径的apk、dex和jar,也可以从sd卡中进行加载。

openDexFileNative代码中主要是对dex文件进行了优化操作,并将优将优化后得dex文件(odex文件)通过mmap映射到内存中。

类的加载

上述我们得到DexClassLoader或者PathClassLoader对象后,就可以调用其loadClass方法来动态加载某个类

DexClassLoader、PathClassLoader以及BaseDexClassLoader都没有实现这个方法,接着再去其父类ClassLoader中找,

/**

* Loads the class with the specified name, optionally linking it after

* loading. The following steps are performed:

*

*

Call {@link #findLoadedClass(String)} to determine if the requested

* class has already been loaded.

*

If the class has not yet been loaded: Invoke this method on the

* parent class loader.

*

If the class has still not been loaded: Call

* {@link #findClass(String)} to find the class.

*

*

* Note: In the Android reference implementation, the

* {@code resolve} parameter is ignored; classes are never linked.

*

*

* @return the {@code Class} object.

* @param className

* the name of the class to look for.

* @param resolve

* Indicates if the class should be resolved after loading. This

* parameter is ignored on the Android reference implementation;

* classes are not resolved.

* @throws ClassNotFoundException

* if the class can not be found.

*/

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;

}

首先调用了findLoadedClass查找当前虚拟机是否已经加载过该类,是则直接返回该class,如果未加载过,则调用父加载器的loadClass方法,

这里采用了java的双亲委派模型,即当一个加载器被请求加载某个类时,它首先委托自己的父加载器去加载,一直向上查找,若顶级加载器(优先)或父类加载器能加载,则返回这个类所对应的Class对象,若不能加载,则最后再由请求发起者去加载该类。这种方式的优点就是能够保证类的加载按照一定的规则次序进行,越是基础的类,越是被上层的类加载器进行加载,从而保证程序的安全性。

/**

* Returns the class with the specified name if it has already been loaded

* by the VM or {@code null} if it has not yet been loaded.

*

* @param className

* the name of the class to look for.

* @return the {@code Class} object or {@code null} if the requested class

* has not been loaded.

*/

protected final Class> findLoadedClass(String className) {

ClassLoader loader;

if (this == BootClassLoader.getInstance())

loader = null;

else

loader = this;

return VMClassLoader.findLoadedClass(loader, className);

}

上述由于该类还未加载,所以findLoadedClass会返回null,所以会调用parent.loadClass,而DexClassLoader在使用时一般采用默认的类加载器作为其父类加载器

DexClassLoader dexClassLoader = new DexClassLoader(dexPath, getDir(“dex”, 0).getAbsolutePath(), dexPath, getClassLoader());

即直接调用Context的getClassLoader方法,该方法为抽象方法,实现是在ContextImpl中

//ContextImpl.java

@Override

public ClassLoader getClassLoader() {

return mPackageInfo != null ?

mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();

}

//ClassLoader.java

/**

* Returns the system class loader. This is the parent for new

* {@code ClassLoader} instances and is typically the class loader used to

* start the application.

*/

public static ClassLoader getSystemClassLoader() {

return SystemClassLoader.loader;

}

//SystemClassLoader为ClassLoader的内部类

static private class SystemClassLoader {

public static ClassLoader loader = ClassLoader.createSystemClassLoader();

}

/**

* Create the system class loader. Note this is NOT the bootstrap class

* loader (which is managed by the VM). We use a null value for the parent

* to indicate that the bootstrap loader is our parent.

*/

private static ClassLoader createSystemClassLoader() {

String classPath = System.getProperty(“java.class.path”, “.”);

//最终返回了PathClassLoader作为系统加载器SystemClassLoader,而其父类为根加载器BootClassLoader

return new PathClassLoader(classPath, BootClassLoader.getInstance());

}

所以ClassLoader的loadClass最终会调用根加载器BootClassLoader的loadClass方法,BootClassLoader也是ClassLoader的内部类,是android平台上所有ClassLoader的parent,其loadClass也是先调用findLoadedClass, 这里未加载过直接返回null,根加载器已经是顶级加载器,所以这里直接调用了findClass方法

@Override

protected Class> loadClass(String className, boolean resolve)

throws ClassNotFoundException {

Class> clazz = findLoadedClass(className);

if (clazz == null) {

clazz = findClass(className);

}

return clazz;

}

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

return Class.classForName(name, false, null);

}

findClass方法也是返回Class.classForName,这里第三个参数为null,采用的是根加载器,而根加载器是用来加载java核心类,无法加载用户定义的类,所以这里返回为空

所以又回到一开始ClassLoader的loadClass方法,调用findClass方法,该方法由其子类覆写,即BaseDexClassLoader中的findClass方法

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

List suppressedExceptions = new ArrayList();

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;

}

可以看到findClass直接调用pathList的findClass方法,如果为空,抛出ClassNotFoundExceptioin异常,如果不为空,则直接返回该Class

pathList即BaseDexClassLoader中的DexPathList成员变量,其中保存了dexFile的Elements集合,

public Class findClass(String name, List 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的loadClassBinaryName native方法来加载Class.

所以之前在dex分包的时候,我们通过PathClassLoader获取已加载的保存在pathList中的dex信息,然后利用DexClassLoadder加载我们指定的从dex文件,将dex信息合并到pathList的dexElements中,从而在app运行的时候能够将所有的dex中的类加载到内存中。

参考文章

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/153536.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号