一、概述
我們知道Java中的 ClassLoader
可以加載 jar 文件和Class文件(本質時加載Class文件)。在Android中,它們加載到是dex文件。
Android中的ClassLoader類型分別是系統類加載器和自定義加載器。其中系統類加載器主要包括3種,分別是 BootClassLoader 、PathClassLoader 和 DexClassLoader 。
概覽
- BootClassLoader
Android 系統啓動時會使用BootClassLoader
來預加載常用類。- DexClassLoader
DexClassLoader 可以加載dex文件以及包含dex的壓縮文件(apk和jar文件)- PathClassLoader
Android 系統使用PathClassLoader來加載系統類和應用程序的類。
framework 源碼使用 android10 release 分支
platform/libcore/dalvik/src/main/java/dalvik/system
- PathClassLoader.java
- DexClassLoader.java
- BaseDexClassLoader.java
- DexPathList.java
- DexFile.java
platform/art/runtime/native
- dalvik_system_DexFile.cc
platform/ojluni/src/main/java/java/lang/
- ClassLoader.java
二、五種類加載構造函數
2.1 PathClassLoader
提供了一個簡單的{@link ClassLoader}實現,它對列表進行操作的文件和目錄,但沒有嘗試從網絡加載類。Android將這個類用於它的系統類加載器和它的應用程序類加載器。
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);
}
@libcore.api.CorePlatformApi
public PathClassLoader(
String dexPath, String librarySearchPath, ClassLoader parent,
ClassLoader[] sharedLibraryLoaders) {
super(dexPath, librarySearchPath, parent, sharedLibraryLoaders);
}
}
2.2 DexClassLoader
一個類裝入器,從包含{@code classes.dex}條目的{@code .jar}和{@code .apk}文件中裝入類。這可用於執行未作爲應用程序的一部分安裝的代碼。 在API級別26之前,這個類裝入器需要一個應用專用的可寫目錄來緩存優化後的類。使用
File dexOutputDir = context.getCodeCacheDir()
創建這樣一個目錄 。不要在外部存儲上緩存優化的類。外部存儲不提供必要的訪問控制來保護應用程序免受代碼注入攻擊。
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
2.3 BaseDexClassLoader
用於定製如何報告dex文件加載的鉤子。
這使框架能夠監視dex文件的使用。其目標是簡化優化外部dex文件的機制,並允許進一步優化輔助dex文件。只有當BaseDexClassLoader的新實例被構造時,報告纔會發生,並且只有在這個字段被設置爲{@link BaseDexClassLoader#setReporter}後纔會被激活。
public class BaseDexClassLoader extends ClassLoader {
@UnsupportedAppUsage
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
boolean isTrusted) {
super(parent);
//在創建路徑列表之前設置共享庫。ART依賴於類加載器層次結構在加載dex文件之前完成。
this.sharedLibraryLoaders = sharedLibraryLoaders == null
? null
: Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
if (reporter != null) {
reportClassLoaderChain();
}
}
//設置dex加載通知的報告器。一旦設置完畢,BaseDexClassLoader的所有新實例都將在構建已加載的dex文件時報告。
@libcore.api.CorePlatformApi
public static void setReporter(Reporter newReporter) {
reporter = newReporter;
}
}
BaseDexClassLoader構造函數, 有一個非常重要的過程, 那就是初始化DexPathList對象.
另外該構造函數的參數說明:
- dexPath: 包含目標類或資源的apk/jar列表;當有多個路徑則採用:分割;
- optimizedDirectory: 優化後dex文件存在的目錄, 可以爲null;
- libraryPath: native庫所在路徑列表;當有多個路徑則採用:分割;
- ClassLoader:父類的類加載器.
2.4 ClassLoader
類加載器是負責裝入類的對象。類是一個抽象類。給定類的屬性,類加載器應該嘗試定位或生成構成類定義的數據。應用程序實現的子類是爲了擴展Java虛擬機動態加載類的方式。安全管理器通常可以使用類裝入器來指示安全域。
public abstract class ClassLoader {
private ClassLoader parent; //記錄父類加載器
protected ClassLoader() {
this(getSystemClassLoader(), false); //見下文
}
protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
if (parentLoader == null && !nullAllowed) {
//父類的類加載器爲空,則拋出異常
throw new NullPointerException("parentLoader == null && !nullAllowed");
}
parent = parentLoader;
}
}
再來看看 SystemClassLoader
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
2.5 BootClassLoader
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
public static synchronized BootClassLoader getInstance() {
if (instance == null) {
instance = new BootClassLoader();
}
return instance;
}
public BootClassLoader() {
super(null, true);
} }
三、PathClassLoader加載類的過程
此處以PathClassLoader爲例來說明類的加載過程,先初始化,然後執行loadClass()方法來加載相應的類。
3.1 PathClassLoader構造方法
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent); //見下文
}
}
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent); //見下文
//收集dex文件和Native動態庫
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
}
public abstract class ClassLoader {
private ClassLoader parent; //父類加載器
protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
parent = parentLoader;
}
}
3.2 DexPathList
類路徑條目可以是任何一個:包含可選的頂級{@code classes.dex}文件和任意資源的{@code .dex}文件,或者是普通的{@code .dex}文件(不可能有關聯的資源)。這個類還包含使用這些列表查找類和資源的方法
public final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private static final String zipSeparator = "!/";
@UnsupportedAppUsage
private final ClassLoader definingContext;
@UnsupportedAppUsage
private Element[] dexElements;//dex/資源(類路徑)元素的列表。應該被稱爲pathElements,但是Facebook應用程序使用反射來修改“dexelement”
@UnsupportedAppUsage
NativeLibraryElement[] nativeLibraryPathElements; //本地庫路徑元素列表
@UnsupportedAppUsage
private final List<File> nativeLibraryDirectories;//應用程序本機庫目錄的列表。
@UnsupportedAppUsage
private final List<File> systemNativeLibraryDirectories;
@UnsupportedAppUsage
private IOException[] dexElementsSuppressedExceptions;//創建dexElements列表時拋出的異常。
@UnsupportedAppUsage
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
this(definingContext, dexPath, librarySearchPath, optimizedDirectory, false);
}
DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
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<IOException> suppressedExceptions = new ArrayList<IOException>();
//記錄所有的dexFile文件
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);
//app目錄的native庫
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
//記錄所有的Native動態庫
this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}
}
DexPathList初始化過程,主要功能是收集以下兩個變量信息:
- dexElements: 根據多路徑的分隔符“;”將dexPath轉換成File列表,記錄所有的dexFile
- nativeLibraryPathElements: 記錄所有的Native動態庫, 包括app目錄的native庫和系統目錄的native庫。
3.2.1 DexPathList::makePathElements
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}
該方法的主要功能是創建Element數組
3.2.2 DexPathList::makeDexElements
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
//打開所有文件並預先加載(直接或包含的)dex文件。
for (File file : files) {
if (file.isDirectory()) {
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();
DexFile dex = null;
//匹配以.dex爲後綴的文件
if (name.endsWith(DEX_SUFFIX)) {
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
elements[elementsPos++] = new Element(dex, null);
}
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else {
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
suppressedExceptions.add(suppressed);
}
if (dex == null) {
elements[elementsPos++] = new Element(file);
} else {
elements[elementsPos++] = new Element(dex, file);
}
}
if (dex != null && isTrusted) {
dex.setTrusted();
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}
3.2.3 DexPathList::loadDexFile
@UnsupportedAppUsage
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
Element[] elements)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file, loader, elements);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
}
}
3.2.4 創建DexFile對象
應用程序不應該直接使用這個類。在大多數情況下,這會損害性能,在最壞的情況下會導致不正確的字節碼執行。應用程序應該使用一個標準的類加載器,比如{@link dalvik.system。PathClassLoader}。這個API將在未來的Android版本中被刪除。
DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
throws IOException {
this(file.getPath(), loader, elements);
}
private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
DexPathList.Element[] elements) 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'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
mInternalCookie = mCookie;
mFileName = sourceName;
}
參數介紹
- sourceName : 帶有class .dex的Jar或APK文件
- outputName : 保存DEX數據的優化格式的文件。
- elements : makeDexElements()過程生成的臨時Element數組;
3.2.5 DexFile::openDexFile
private static Object openDexFile(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements) throws IOException {
// Use absolute paths to enable the use of relative paths when testing on host.
return openDexFileNative(new File(sourceName).getAbsolutePath(),
(outputName == null)
? null
: new File(outputName).getAbsolutePath(),
flags,
loader,
elements);
}
3.2.6 dalvik_system_DexFile.cc::openDexFileNative
static jobject DexFile_openDexFileNative(JNIEnv* env,
jclass,
jstring javaSourceName,
jstring javaOutputName ATTRIBUTE_UNUSED,
jint flags ATTRIBUTE_UNUSED,
jobject class_loader,
jobjectArray dex_elements) {
ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() == nullptr) {
return nullptr;
}
std::vector<std::string> error_msgs;
const OatFile* oat_file = nullptr;
std::vector<std::unique_ptr<const DexFile>> dex_files =
Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
class_loader,
dex_elements,
/*out*/ &oat_file,
/*out*/ &error_msgs);
return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
}
PathClassLoader創建完成後,就已經擁有了目標程序的文件路徑,native lib路徑,以及parent類加載器對象。接下來開始執行loadClass()來加載相應的類。
3.3 ClassLoader::loadClass
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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;
}
該方法的加載流程如下:
- 判斷當前類加載器是否已經加載過指定類,若已加載則直接返回,否則繼續執行
- 調用parent的類加載遞歸加載該類,檢測是否加載,若已加載則直接返回,否則繼續執行;
- 調用當前類加載器,通過findClass加載
3.3 ClassLoader::findLoadedClass
protected final Class<?> findLoadedClass(String name) {
ClassLoader loader;
if (this == BootClassLoader.getInstance())
loader = null;
else
loader = this;
return VMClassLoader.findLoadedClass(loader, name);
}
3.4 BaseDexClassLoader:: findClass
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
...
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
...
return c;
}
3.5 DexPathList.findClass
在此實例指向的dex文件中查找已命名類。這將在最早列出的path元素中找到一個。如果找到了類,但還沒有定義,則此方法將在構造此實例時使用的定義上下文中定義它。
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;
}
這裏是核心邏輯,一個ClassLoader可以包含多個dex文件,每個dex文件被封裝到一個Element對象,這些Element對象排列成有序的數組 dexElements 。當查找某個類時,會遍歷所有的dex文件,如果找到則直接返回,不再繼續遍歷dexElements。也就是說當兩個類不同的dex中出現,會優先處理排在前面的dex文件,這便是熱修復的核心,將需要修復的類所打包的dex文件插入到dexElements前面。
DexFile::loadClassBinaryName
element.findClass
最後走到 dexFile.loadClassBinaryName
@UnsupportedAppUsage
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;
}
dalvik_system_DexFile.cc:: defineClassNative
static jclass DexFile_defineClassNative(JNIEnv* env,
jclass,
jstring javaName,
jobject javaLoader,
jobject cookie,
jobject dexFile) {
std::vector<const DexFile*> dex_files;
const OatFile* oat_file;
if (!ConvertJavaArrayToDexFiles(env, cookie, /*out*/ dex_files, /*out*/ oat_file)) {
VLOG(class_linker) << "Failed to find dex_file";
DCHECK(env->ExceptionCheck());
return nullptr; //dex文件爲空, 則直接返回
}
ScopedUtfChars class_name(env, javaName);
if (class_name.c_str() == nullptr) {
VLOG(class_linker) << "Failed to find class_name";
return nullptr; //類名爲空, 則直接返回
}
const std::string descriptor(DotToDescriptor(class_name.c_str()));
const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str()));
for (auto& dex_file : dex_files) {
const dex::ClassDef* dex_class_def =
OatDexFile::FindClassDef(*dex_file, descriptor.c_str(), hash); //將類名轉換爲hash碼
if (dex_class_def != nullptr) {
ScopedObjectAccess soa(env);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader(
hs.NewHandle(soa.Decode<mirror::ClassLoader>(javaLoader)));
ObjPtr<mirror::DexCache> dex_cache =
class_linker->RegisterDexFile(*dex_file, class_loader.Get());
if (dex_cache == nullptr) {
// OOME or InternalError (dexFile already registered with a different class loader).
soa.Self()->AssertPendingException();
return nullptr;
}
ObjPtr<mirror::Class> result = class_linker->DefineClass(soa.Self(),
descriptor.c_str(),
hash,
class_loader,
*dex_file,
*dex_class_def);
//添加使用過的dex文件。這隻對DexFile是必需的。因爲通常的類加載器已經保持它們的dex文件的活動。>InsertDexFileInToClassLoader(soa.Decode<mirror::Object>(dexFile),
class_loader.Get());
if (result != nullptr) {
// 找到目標對象
return soa.AddLocalReference<jclass>(result);
}
}
}
return nullptr;
}
在native層創建目標類的對象並添加到虛擬機列表。
總結
這篇文章我們要理解Android常用的classLoader,以及通過將補丁dex文件插入到替換dex文件的Elements的熱修復原理。
可以嘗試着回答以下問題來鞏固
- 1.虛擬機如何加載這些class文件?
- 2.Class文件中的信息進入到虛擬機後會發生什麼變化?
- 3.如何打破雙親委派模型
參考 :
http://gityuan.com/2017/03/19/android-classloader/