概述
上一篇文章我們瞭解了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
是他的內部類SecureClassLoader
和JDK8中的SecureClassLoader
代碼是一樣的,他繼承抽象類ClassLoader
,SecureClassLoader
並不是ClassLoader
的實現類,而是擴展了ClassLoader
權限方面的功能,加強了Classloader
的安全性URLClassLoader
和JDK8中的URLClassLoader是一樣的,他繼承了SecureClassLoader
通能過URL
路徑從jar文件中加載類和資源InMemoryDexClassLoader
他是Android8.0新加的類加載器,繼承自BaseDexClassLoader
,用於加載內存中的dex文件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進階解密》