安卓插件化技術(shù)已經(jīng)作為一個優(yōu)秀的合格研發(fā)必備要求,學(xué)習(xí)和掌握現(xiàn)有不同種類動態(tài)加載方案 是提升個人技術(shù)深度有效途徑。
插件化基礎(chǔ) ClassLoader
ClassLoader是什么酱鸭?
ClassLoader 是將java編譯后的字節(jié)碼加載到虛擬機(jī)內(nèi)存中的用到工具類宏娄。
Android平臺虛擬機(jī)Dalvik/ART可以運(yùn)行的字節(jié)碼為.dex文件毫玖,Java平臺JVM虛擬機(jī)可以被加載的字節(jié)碼為.class文件炼蹦。針對Android平臺羡宙,若我們用ClassLoader加載指定地方的.dex,并且可以擴(kuò)展ClassLoader實(shí)現(xiàn)定制加載器框弛,就可以實(shí)現(xiàn)動態(tài)加載代碼的目的辛辨。
dex文件也是由jar轉(zhuǎn)化而來捕捂,android提供了轉(zhuǎn)化命令:
dx --dex --output=target.dex origin.jar // target.dex就是我們要的了
ClassLoader實(shí)例
Java虛擬機(jī)中類加載器:
系統(tǒng)默認(rèn)三個主要的類加載器瑟枫,每個類負(fù)責(zé)加載特定位置的類:
BootStrap,ExtClassLoader,AppClassLoader
- BootStrap 引導(dǎo)類加載器斗搞,用來加載java核心庫(jre/lib/rt.jar),并非繼承ClassLoader慷妙,C代碼實(shí)現(xiàn)僻焚。
- ExtClassLoader 擴(kuò)展類加載器,用來加載java擴(kuò)展庫中的類(jre/lib/ext/*.jar)膝擂。
- AppClassLoader 系統(tǒng)類加載器虑啤,根據(jù)類路徑(ClassPath)來加載java類,java應(yīng)用層類都是該類加載架馋。
用IntelliJ 創(chuàng)建java虛擬機(jī)代碼
public class Run {
public static void main(String args[]){
StringBuilder stringBuilder = new StringBuilder();
ClassLoader loader = Run.class. getClassLoader();
while (loader != null){
stringBuilder.append(loader.toString());
stringBuilder.append("\n##");
loader = loader.getParent();
}
System.out.println("ClassLoader:"+stringBuilder);
}
}
打印信息中狞山,可以看到加載器類的層級關(guān)系。
##ClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
##sun.misc.Launcher$ExtClassLoader@511d50c0
Process finished with exit code 0
安卓虛擬機(jī)Dalvik/ART類加載器
安卓平臺虛擬機(jī)的類加載器實(shí)例和java不同叉寂,但都是繼承自ClassLoader的萍启。
BootClassLoader
和java虛擬機(jī)中不同的是BootClassLoader是ClassLoader內(nèi)部類,由java代碼實(shí)現(xiàn)而不是c++實(shí)現(xiàn),是Android平臺上所有ClassLoader的最終parent,這個內(nèi)部類是包內(nèi)可見,所以我們沒法使用。URLClassLoader
只能用于加載jar文件屏鳍,但是由于 dalvik 不能直接識別jar勘纯,所以在 Android 中無法使用這個加載器。BaseDexClassLoader
PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader,其中的主要邏輯都是在BaseDexClassLoader完成的钓瞭。這些源碼在java/dalvik/system中驳遵。DexClassLoader
DexClassLoader支持加載APK、DEX和JAR山涡,也可以從SD卡進(jìn)行加載堤结。動態(tài)加載主要用到了該類特性,來動態(tài)加載不同類型壓縮包里的或這直接dex文件代碼鸭丛。PathClassLoader
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);
}
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);
}
}
}
由于構(gòu)造參數(shù)optimizedDirectory指定為null霍殴,會直接使用dex文件原有的路徑來創(chuàng)建DexFile對象。也就是說在dalvik虛假機(jī)上PathClassLoader無法加載外部的動態(tài)代碼系吩。
類加載過程分析
JVM中ClassLoader通過defineClass方法加載jar里面的Class来庭,而Android平臺中這個方法被棄用了。取而代之的是loadClass方法穿挨。
@Deprecated
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError{
throw new UnsupportedOperationException("can't load this type of class file");
}
分析loadClass
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) {
long t0 = System.nanoTime();
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.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
}
}
return c;
}
loadClass代碼分析可以看出月弛,加載前會看緩存中是否已經(jīng)加載,沒有加載則委托為parent科盛,如果parent沒有加載到帽衙,這child加載器開始findClass加載。這種加載方式被稱為雙親代理模型加載贞绵。
特點(diǎn):如果一個類被位于樹根的ClassLoader加載過厉萝,那么在以后整個系統(tǒng)的生命周期內(nèi),這個類永遠(yuǎn)不會被重新加載。共享功能谴垫,一些Framework層級的類一旦被頂層的ClassLoader加載過就緩存在內(nèi)存里面章母,以后任何地方用到都不需要重新加載。
除此之外還有隔離功能翩剪,不同繼承路線上的ClassLoader加載的類肯定不是同一個類乳怎,這樣的限制避免了用戶自己的代碼冒充核心類庫的類訪問核心類庫包可見成員的情況。
層級關(guān)系中父類沒有加載到時前弯,子類開始findClass
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
public Class loadClassBinaryName(String name, ClassLoader loader) {
return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);
可以看出蚪缀,BaseDexClassLoader中有個pathList對象,pathList中包含一個DexFile的數(shù)組dexElements,熱修復(fù)原理也是修改該數(shù)組加載順序來實(shí)現(xiàn)恕出,dexPath傳入的原始dex(.apk,.zip,.jar等)文件在optimizedDirectory文件夾中生成相應(yīng)的優(yōu)化后的odex文件询枚,dexElements數(shù)組就是這些odex文件的集合,如果不分包一般這個數(shù)組只有一個Element元素浙巫,也就只有一個DexFile文件哩盲,而對于類加載呢,就是遍歷這個集合狈醉,通過DexFile去尋找廉油。最終調(diào)用native方法的defineClass。
擴(kuò)展ClassLoader實(shí)現(xiàn)定制化
可以重載loadClass方法并改寫類的加載邏輯苗傅,我們可以通過重寫loadClass方法避開雙親代理的框架抒线,這樣一來就可以在重新加載已經(jīng)加載過的類,也可以在加載類的時候注入一些代碼渣慕。
安卓平臺特有組件(activity嘶炭,service等)的加載是否可以直接加載呢?答案肯定是否的逊桦,這些組件都是需要在清單文件中注冊眨猎,然后才會被系統(tǒng)反射加載,并基于回調(diào)給予組件生命周期强经。
那我們想要加載一個安卓特有組件時睡陪,需要解決生命周期管理問題,才能正確的被系統(tǒng)調(diào)起來匿情。
——————
歡迎轉(zhuǎn)載兰迫,請標(biāo)明出處:常興E站 www.canking.win