作者:郭孝星
微博:郭孝星的新浪微博
邮箱:[email protected]
博客:http://blog.csdn.net/allenwells
github:https://github.com/AllenWell
在介绍Android的类加载机制之前,我们需要先了解一下Java的类加载机制。
【Java 安全技术探索之路系列:J2SE安全架构】之五:类加载器
说到Dalvik虚拟机,我们首先可能想到的是Java虚拟机,伴随着Java语言的发展,我们也一直在接触它,那么两者有什么区别呢?
- Java虚拟机基于栈,Dalvik虚拟机基于寄存器。
- Java虚拟机运行的是Java字节码,Java虚拟机运行的是Dex字节码。
由于本篇文章主要讨论的是Dalvik虚拟机的类的加载机制,所以其他区别不再展开,需要了解的可以参见我的其他文章,这里着重提一下Dalvik虚拟机和Java虚拟机加载类机制上的区别。
Dalvik虚拟机如同其他Java虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。而在Java标准的虚拟机中,类加载可以从class文件中读取,也可以是其他形式的二进制流。因此,我们常常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的。
然而Dalvik虚拟机毕竟不算是标准的Java虚拟机,因此在类加载机制上,Dalvik虚拟机与Java虚拟机有许多不同之处,例如,在使用标准Java虚拟机时,我们经常自定义继承自ClassLoader的类加载器。然后通过defineClass方法来从一个二进制流中加载Class。然而,这在Dalvik虚拟机上是行不通的。
一 Dalvik虚拟机类加载结构
Dalvik虚拟机类加载流程如下图所示:
1.1 类加载器
1.1.1 系统类加载器
举例
Context.class.getClassLoader();
上述代码得到的结果表明系统类的加载器是BootClassLoader。
ClassLoader.getSystemClassLoader().getParent();
上述代码表明系统加载器的父类加载器还是
1.1.2 应用程序加载器
举例
getClassLoader();
上述代码得到的结果表明应用程序的加载器是PathClassLoader
getClassLoader().getParent();
上述代码得到的结果表明应用程序的家在启动饿父类加载器是BootClassLoader。
二 Dalvik虚拟机类加载器源码分析
Android的类加载器主要有两个PathClassLoader和DexClassLoader,其中PathClassLoader是默认的类加载器,下面我们就来说说两者的区别与联系。
- PathClassLoader:支持加载DEX或者已经安装的APK(因为存在缓存的DEX)。
- DexClassLoader:支持加载APK、DEX和JAR,也可以从SD卡进行加载。
DexClassLoader和PathClassLoader都属于符合双亲委派模型的类加载器(因为它们没有重载loadClass方法)。也就是说,它们在加载一个类之前,回去检查自己以及自己以上的类加载器是否已经加载了这个类。如果已经加载过了,就会直接将之返回,而不会重复加载。
PathClassLoader还是DexClassLoader继承于BaseDexClassLoader,BaseDexClassLoader继承鱼ClassLoader,下面我们就以一个类的加载流程来分析各个加载器的源码实现,详细的源码在下方附录中给出。
要加载一个类,必须先初始化一个类加载器实例,我们拿DexClassLoader来举例,它的构造方法如下所示:
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
该函数中的参数含义如下所示:
- String dexPath:加载APK、DEX和JAR的路径。这个类可以用于Android动态加载DEX/JAR。
- String optimizedDirectory:是DEX的输出路径。
- String libraryPath:加载DEX的时候需要用到的lib库,libraryPath一般包括/vendor/lib和/system/lib。
- ClassLoader parent:DEXClassLoader指定的父类加载器
关于DexClassLoader,除了它的构造函数以外,它的源码注释里还提到以下三点:
- 这个类加载器加载的文件是.jar或者.apk文件,并且这个.jar或.apk中是包含classes.dex这个入口文件的,
主要是用来执行那些没有被安装的一些可执行文件的。 - 这个类加载器需要一个属于应用的私有的,可以的目录作为它自己的缓存优化目录,其实这个目录也就作为下面,这个构造函数的第二个参数,至于怎么实现,注释中也已经给出了答案;
- 不要把上面第二点中提到的这个缓存目录设为外部存储,因为外部存储容易收到代码注入的攻击。
通过DexClassLoader的构造函数,我们可以发现DexClassLoader的构造函数会调用父类的构造函数进行初始化,DexClassLoader的父类就是BaseDexXClassLoader,我们继续来看一下BaseDexClassLoader的构造函数:
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
我们可以发现在执行BaseDexClassLoader的构造函数时,会先调用父类ClassLoader的构造方法:
/**
* Constructs a new instance of this class with the system class loader as
* its parent.
*/
protected ClassLoader() {
this(getSystemClassLoader(), false);
}
/**
* Constructs a new instance of this class with the specified class loader
* as its parent.
*
* @param parentLoader
* The {@code ClassLoader} to use as the new class loader's
* parent.
*/
protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}
/*
* constructor for the BootClassLoader which needs parent to be null.
*/
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
if (parentLoader == null && !nullAllowed) {
throw new NullPointerException("parentLoader == null && !nullAllowed");
}
parent = parentLoader;
}
通过ClassLoader的构造函数源码可以发现,BaseDexClassLoader里的parentLoader对象经过层层传递,传递给了parent对象,parent对象是ClassLoader类里的私有变量,如下所示:
/**
* The parent ClassLoader.
*/
private ClassLoader parent;
这一步做完以后,BaseDexClassLoader的构造函数紧接着就初始化了一个DexPathList对象,这是一个描述DEX文相关资源文件的条目列表。
附录
附录一:【Lollipop 5.1.1_r6】ClassLoader源码
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/*
* Copyright (C) 2008 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.
*/
package java.lang;
import dalvik.system.PathClassLoader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.security.ProtectionDomain;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Loads classes and resources from a repository. One or more class loaders are
* installed at runtime. These are consulted whenever the runtime system needs a
* specific class that is not yet available in-memory. Typically, class loaders
* are grouped into a tree where child class loaders delegate all requests to
* parent class loaders. Only if the parent class loader cannot satisfy the
* request, the child class loader itself tries to handle it.
* <p>
* {@code ClassLoader} is an abstract class that implements the common
* infrastructure required by all class loaders. Android provides several
* concrete implementations of the class, with
* {@link dalvik.system.PathClassLoader} being the one typically used. Other
* applications may implement subclasses of {@code ClassLoader} to provide
* special ways for loading classes.
* </p>
* @see Class
*/
public abstract class ClassLoader {
/**
* The 'System' ClassLoader - the one that is responsible for loading
* classes from the classpath. It is not equal to the bootstrap class loader -
* that one handles the built-in classes.
*
* Because of a potential class initialization race between ClassLoader and
* java.lang.System, reproducible when using JDWP with "suspend=y", we defer
* creation of the system class loader until first use. We use a static
* inner class to get synchronization at init time without having to sync on
* every access.
*
* @see #getSystemClassLoader()
*/
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
/**
* The parent ClassLoader.
*/
private ClassLoader parent;
/**
* The packages known to the class loader.
*/
private Map<String, Package> packages = new HashMap<String, Package>();
/**
* To avoid unloading individual classes, {@link java.lang.reflect.Proxy}
* only generates one class for each set of interfaces. This maps sets of
* interfaces to the proxy class that implements all of them. It is declared
* here so that these generated classes can be unloaded with their class
* loader.
*
* @hide
*/
public final Map<List<Class<?>>, Class<?>> proxyCache =
new HashMap<List<Class<?>>, Class<?>>();
/**
* 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", ".");
// String[] paths = classPath.split(":");
// URL[] urls = new URL[paths.length];
// for (int i = 0; i < paths.length; i++) {
// try {
// urls[i] = new URL("file://" + paths[i]);
// }
// catch (Exception ex) {
// ex.printStackTrace();
// }
// }
//
// return new java.net.URLClassLoader(urls, null);
// TODO Make this a java.net.URLClassLoader once we have those?
return new PathClassLoader(classPath, BootClassLoader.getInstance());
}
/**
* 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;
}
/**
* Finds the URL of the resource with the specified name. The system class
* loader's resource lookup algorithm is used to find the resource.
*
* @return the {@code URL} object for the requested resource or {@code null}
* if the resource can not be found.
* @param resName
* the name of the resource to find.
* @see Class#getResource
*/
public static URL getSystemResource(String resName) {
return SystemClassLoader.loader.getResource(resName);
}
/**
* Returns an enumeration of URLs for the resource with the specified name.
* The system class loader's resource lookup algorithm is used to find the
* resource.
*
* @return an enumeration of {@code URL} objects containing the requested
* resources.
* @param resName
* the name of the resource to find.
* @throws IOException
* if an I/O error occurs.
*/
public static Enumeration<URL> getSystemResources(String resName) throws IOException {
return SystemClassLoader.loader.getResources(resName);
}
/**
* Returns a stream for the resource with the specified name. The system
* class loader's resource lookup algorithm is used to find the resource.
* Basically, the contents of the java.class.path are searched in order,
* looking for a path which matches the specified resource.
*
* @return a stream for the resource or {@code null}.
* @param resName
* the name of the resource to find.
* @see Class#getResourceAsStream
*/
public static InputStream getSystemResourceAsStream(String resName) {
return SystemClassLoader.loader.getResourceAsStream(resName);
}
/**
* Constructs a new instance of this class with the system class loader as
* its parent.
*/
protected ClassLoader() {
this(getSystemClassLoader(), false);
}
/**
* Constructs a new instance of this class with the specified class loader
* as its parent.
*
* @param parentLoader
* The {@code ClassLoader} to use as the new class loader's
* parent.
*/
protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}
/*
* constructor for the BootClassLoader which needs parent to be null.
*/
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
if (parentLoader == null && !nullAllowed) {
throw new NullPointerException("parentLoader == null && !nullAllowed");
}
parent = parentLoader;
}
/**
* Constructs a new class from an array of bytes containing a class
* definition in class file format.
*
* @param classRep
* the memory image of a class file.
* @param offset
* the offset into {@code classRep}.
* @param length
* the length of the class file.
* @return the {@code Class} object created from the specified subset of
* data in {@code classRep}.
* @throws ClassFormatError
* if {@code classRep} does not contain a valid class.
* @throws IndexOutOfBoundsException
* if {@code offset < 0}, {@code length < 0} or if
* {@code offset + length} is greater than the length of
* {@code classRep}.
* @deprecated Use {@link #defineClass(String, byte[], int, int)}
*/
@Deprecated
protected final Class<?> defineClass(byte[] classRep, int offset, int length)
throws ClassFormatError {
throw new UnsupportedOperationException("can't load this type of class file");
}
/**
* Constructs a new class from an array of bytes containing a class
* definition in class file format.
*
* @param className
* the expected name of the new class, may be {@code null} if not
* known.
* @param classRep
* the memory image of a class file.
* @param offset
* the offset into {@code classRep}.
* @param length
* the length of the class file.
* @return the {@code Class} object created from the specified subset of
* data in {@code classRep}.
* @throws ClassFormatError
* if {@code classRep} does not contain a valid class.
* @throws IndexOutOfBoundsException
* if {@code offset < 0}, {@code length < 0} or if
* {@code offset + length} is greater than the length of
* {@code classRep}.
*/
protected final Class<?> defineClass(String className, byte[] classRep, int offset, int length)
throws ClassFormatError {
throw new UnsupportedOperationException("can't load this type of class file");
}
/**
* Constructs a new class from an array of bytes containing a class
* definition in class file format and assigns the specified protection
* domain to the new class. If the provided protection domain is
* {@code null} then a default protection domain is assigned to the class.
*
* @param className
* the expected name of the new class, may be {@code null} if not
* known.
* @param classRep
* the memory image of a class file.
* @param offset
* the offset into {@code classRep}.
* @param length
* the length of the class file.
* @param protectionDomain
* the protection domain to assign to the loaded class, may be
* {@code null}.
* @return the {@code Class} object created from the specified subset of
* data in {@code classRep}.
* @throws ClassFormatError
* if {@code classRep} does not contain a valid class.
* @throws IndexOutOfBoundsException
* if {@code offset < 0}, {@code length < 0} or if
* {@code offset + length} is greater than the length of
* {@code classRep}.
* @throws NoClassDefFoundError
* if {@code className} is not equal to the name of the class
* contained in {@code classRep}.
*/
protected final Class<?> defineClass(String className, byte[] classRep, int offset, int length,
ProtectionDom