概述
上一篇文章我們了解了
Java
的
ClassLoader
,上一篇文章傳送門JVM 類加載機制
其實
Android
中的
ClassLoader
和
java
中的是不一樣的,因為
java
中的
CalssLoader
主要加載
Class
檔案,但是
Android
中的
ClassLoader
主要加載
dex
檔案
Android中的ClassLoader
Android
中的
ClassLoader
分為倆種類型,
系統類加載器
,
自定義類加載器
。其中
系統的類加載器
分為三種,
BootClassLoader
,
PathClassLoader
,
DexClassLoader
BootClassLoader
Android 系統啟動時,會用BootClassLoader來預加載常用類,與java中的ClassLoader不同,他不是用C++實作,而是用java實作的,如下:
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
@FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
public static synchronized BootClassLoader getInstance() {
if (instance == null) {
instance = new BootClassLoader();
}
return instance;
}
...
BootClassLoader
是
ClassLoader
的内部類,并繼承自
ClassLoader
,
BootClassLoader
是一個單例類,需要注意的是
BootClassLoader
是預設修飾符,隻能包内通路,我們是無法使用的
DexClassLoader
DexClassLoader可以加載dex檔案和包含dex檔案的壓縮檔案(比如jar和apk檔案),不管加載那種檔案,最終都是加載dex檔案,我們看一下代碼
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
DexClassLoader有四個參數
- dexPath:dex相關檔案的路徑集合,多個檔案用路徑分割符分割,預設的檔案分割符為 “:”
- optimizedDirectory:解壓的dex檔案儲存的路徑,這個路徑必須是一個内部儲存路徑,一般情況下使用當錢應用程式的私有路徑
/data/data/<Package Name>/...
- librarySearchPath:包含C++庫的路徑集合,多個路徑用檔案分割符分割,可以為null
- parent:父加載器
DexClassLoade
繼承自
BaseDexClassLoader
,所有的實作都是在
BaseDexClassLoader
中
PathClassLoader
Android
用
PathClassLoader
來加載系統類和應用程式類,代碼如下:
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
PathClassLoader
繼承自
BaseDexClassLoader
,所有的實作都是在
BaseDexClassLoader
中
PathClassLoader
構造方法沒有
optimizedDirectory
參數,因為
PathClassLoader
預設
optimizedDirectory
參數是
/data/dalvik-cache
,很顯然
PathClassLoader
無法定義解壓的
dex
儲存的位置,是以
PathClassLoader
通常用來加載已經安裝的
apk
的
dex
檔案(安裝的
apk的dex
檔案在
/data/dalvik-cache
中)
ClassLoder的繼承關系
運作一個應用程式需要幾個ClassLoader呢?
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader classLoader = MainActivity.class.getClassLoader();
while (classLoader != null) {
Log.d("mmmClassLoader", classLoader.toString()+"\n");
classLoader = classLoader.getParent();
}
}
看下log
mmmClassLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.baidu.bpit.aibaidu.idl3-2/base.apk"],
nativeLibraryDirectories=[/data/app/com.baidu.bpit.aibaidu.idl3-2/lib/arm64, /data/app/com.baidu.bpit.aibaidu.idl3-2/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64, /system/vendor/lib64, /product/lib64]]]
java.lang.BootClassLoader@fcb14c9
我們看到主要用了倆個
ClassLoader
,分别是
PathClassLoader
和
BootClassLoader
,其中
DexPathList
包含了很多
apk
的路徑,其中
/data/app/com.baidu.bpit.aibaidu.idl3-2/base.apk
就是執行個體應用安裝在手機上的位置,
DexPathList
是在
BaseDexClassLoder
中建立的,裡面儲存
dex
相關檔案的路徑
除了上方的3中ClassLoader,Android還提供了其他的類加載器和ClassLoader相關類,繼承關系如下:

分别介紹一下上方的8種ClassLoader
-
是一個抽象類,其中定義了ClassLoader
的主要功能,ClassLoder
是他的内部類BootClassLoader
-
和JDK8中的SecureClassLoader
代碼是一樣的,他繼承抽象類SecureClassLoader
,ClassLoader
并不是SecureClassLoader
的實作類,而是擴充了ClassLoader
權限方面的功能,加強了ClassLoader
的安全性Classloader
-
和JDK8中的URLClassLoader是一樣的,他繼承了URLClassLoader
通能過SecureClassLoader
路徑從jar檔案中加載類和資源URL
-
他是Android8.0新加的類加載器,繼承自InMemoryDexClassLoader
,用于加載記憶體中的dex檔案BaseDexClassLoader
-
繼承自BaseDexClassLoader
,是抽象類ClassLoader
的具體實作類ClassLoader
ClassLoader的加載過程
Android
中的
ClassLoader
同樣遵循雙親委托模型,
ClassLoader
的加載方法為
loadClass
,方法定義在ClassLoader中
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
// 首先檢查類是否被加載過
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//如果父類抛出ClassNotFoundException異常
//則說明父類不能加載該類
}
if (c == null) {
//如果父類無法加載,則調用自身的findClass進行加
c = findClass(name);
}
}
return c;
}
上方邏輯很清楚,首先檢查類是否被加載過,如果沒有被加載過,就調用父類的加載器加載,如果父類加載器為空就調用啟動加載器加載,如果父類加載失敗,就調用自己的
findClass
加載,我們看一下這個方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
直接抛出異常,這說明findClass需要子類BaseDexClassLoader實作,如下:
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
if (reporter != null) {
reporter.report(this.pathList.getDexPaths());
}
}
...
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
//注釋1
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;
}
首先在構造方法内建立了
DexPathList
,然後再注釋1處調用
DexPathList
的
findClass
方法
public Class<?> findClass(String name, List<Throwable> suppressed) { //注釋1
for (Element element : dexElements) {
//注釋2
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
在注釋1處周遊
dexElements
數組,在注釋2處調用
Element
的
findClass
方法
public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
System.err.println("Warning: Using deprecated Element constructor. Do not use internal"
+ " APIs, this constructor will be removed in the future.");
if (dir != null && (zip != null || dexFile != null)) {
throw new IllegalArgumentException("Using dir and zip|dexFile no longer"
+ " supported.");
}
if (isDirectory && (zip != null || dexFile != null)) {
throw new IllegalArgumentException("Unsupported argument combination.");
}
if (dir != null) {
this.path = dir;
this.dexFile = null;
} else {
this.path = zip;
this.dexFile = dexFile;
}
}
...
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
//注釋1
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
我們從構造方法可以看出,它内部封裝了
DexFile
,他用于加載
dex
,注釋1處如果
dexFile
不為
null
則調用
DexFile
的
loadClassBinaryName
方法
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
又調用了
defineClass
方法
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> suppressed) {
Class result = null;
try {
//注釋1
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;
}
注釋1處調用了
defineClassNative
方法來加載
dex
相關檔案,這個是
native
方法不在向下分析
參考: 《Android進階解密》