插件化框架實(shí)現(xiàn):基于kotlin的插件化框架
Java類加載
- 我們知道Java代碼通過編譯成class文件后旋圆,需要通過類加載機(jī)制加載到虛擬機(jī)后才能運(yùn)行
類加載機(jī)制
加載階段
- 通過類的全限定名獲取二進(jìn)制字節(jié)流(可以來自磁盤,網(wǎng)絡(luò)等)笔呀,將字節(jié)流裝換為方法區(qū)的數(shù)據(jù)結(jié)構(gòu)镜粤,生成Class對(duì)象作為該類訪問入口
連接階段
- 驗(yàn)證Class的字節(jié)流符合虛擬機(jī)規(guī)范捏题,為類變量分配內(nèi)存初始化默認(rèn)值,將常量池符號(hào)引用轉(zhuǎn)化為直接引用
初始化階段
- 執(zhí)行類構(gòu)造器:靜態(tài)語句塊和類變量賦值動(dòng)作
- 初始化的觸發(fā)時(shí)機(jī)是在遇到new肉渴、invokestatic公荧、反射、父類還沒初始化等操作時(shí)進(jìn)行
Java 類加載器
-
啟動(dòng)類加載器(Bootstrap ClassLoader)
負(fù)責(zé)將
<JAVA_HOME>/lib
目錄下的類庫(kù)加載到虛擬機(jī)內(nèi)存中 -
擴(kuò)展類加載器(Extension ClassLoader)
負(fù)責(zé)加載
<JAVA_HOME>\lib\ext
目錄下的類庫(kù) -
應(yīng)用程序類加載器(Application ClassLoader):一般程序的默認(rèn)類加載器
? 負(fù)責(zé)加載用戶類路徑(
ClassPath
)的類庫(kù)
類加載器的雙親委托模型
- 如果一個(gè)類加載器收到了類的加載的請(qǐng)求同规,它首先不會(huì)自己去加載這個(gè)類循狰,而是把這個(gè)請(qǐng)求委托給父類加載器去完成,每一層都是如此券勺;因此所有的加載請(qǐng)求最終都應(yīng)該傳遞到頂層的啟動(dòng)類加載器中绪钥,只有當(dāng)父類加載器反饋無法完成這個(gè)加載請(qǐng)求(它的搜索范圍內(nèi)沒有找到所需的類)時(shí),子加載器才會(huì)嘗試去加載
- 同時(shí)類加載方式也分為隱式加載(new等方式)和顯示加載
Class.forname(xxx)
Android類加載
- Android不是基于jvm虛擬機(jī)关炼,不能直接加載class字節(jié)碼程腹,需要將class字節(jié)碼轉(zhuǎn)換為dex字節(jié)碼
Android 類加載器
Android類加載器主要是DexClassLoader和PathClassLoader,兩者的區(qū)別是:
PathClassLoader是系統(tǒng)類加載器儒拂,同時(shí)也是默認(rèn)類加載寸潦,只能加載系統(tǒng)中已經(jīng)安裝過的apk
DexClassLoader可以加載apk/dex缀去,可以加載未安裝的apk
DexClassLoader版本差異
- Android在API 9-13 和API 14以上DexClassLoader內(nèi)部持有dex文件的數(shù)據(jù)結(jié)構(gòu)不同,如果需要設(shè)配API 9-13則需要做不同處理甸祭,先來看一下數(shù)據(jù)結(jié)構(gòu)不同的地方:
API 9 - 13
public class DexClassLoader extends ClassLoader {
private static final boolean VERBOSE_DEBUG = false;
/* constructor args, held for init */
private final String mRawDexPath;
private final String mRawLibPath;
private final String mDexOutputPath;
/*
* Parallel arrays for jar/apk files.
*
* (could stuff these into an object and have a single array;
* improves clarity but adds overhead)
*/
private final File[] mFiles; // source file Files, for rsrc URLs
private final ZipFile[] mZips; // source zip files, with resources
private final DexFile[] mDexs; // opened, prepped DEX files
// ....
}
- 這里可以看到DexFile是直接以數(shù)組結(jié)構(gòu)存放在DexClassLoader類中
API > 13
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
// ...
}
final class DexPathList {
/** class definition context */
private final ClassLoader definingContext;
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private Element[] dexElements;
// ...
static class Element {
private final File dir;
private final boolean isDirectory;
private final File zip;
private final DexFile dexFile;
// ...
}
}
- 這里可以看到DexFile被層層封裝存放在BaseDexClassLoader的DexPathList中
ODEX過程
- android 虛擬機(jī)并不是直接讀取dex文件的缕碎,在安裝apk的時(shí)候會(huì)做一次優(yōu)化,在這一過程池户,由虛擬機(jī)控制的一個(gè)verify選項(xiàng)咏雌,如果開啟會(huì)進(jìn)行一次校驗(yàn),如果某個(gè)類沒有引用其他dex中的類校焦,這個(gè)類會(huì)被打上CLASS_ISPREVERIFIED 的標(biāo)志赊抖。一旦被打上這個(gè)標(biāo)志,就無法再?gòu)钠渌?dex 中加載這個(gè)類了
- 這個(gè)問題的比較簡(jiǎn)單的解決辦法是引用其他dex的類
App ClassLoader Hook點(diǎn)
- 我們知道App啟動(dòng)會(huì)初始化Application并且調(diào)用onCreate寨典,這其實(shí)是在接受AMS啟動(dòng)信息后調(diào)用ActivityThread的handleBindApplication氛雪,函數(shù)過長(zhǎng),下面截取關(guān)鍵代碼:
private void handleBindApplication(AppBindData data) {
// ...
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
// ...
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
// 下面代碼中也創(chuàng)建ContextImpl耸成,這里應(yīng)該需要先用到ContextImpl的信息报亩,這里
// 創(chuàng)建ApplicationInfo
ApplicationInfo instrApp = new ApplicationInfo();
instrApp.packageName = ii.packageName;
instrApp.sourceDir = ii.sourceDir;
instrApp.publicSourceDir = ii.publicSourceDir;
instrApp.splitSourceDirs = ii.splitSourceDirs;
instrApp.splitPublicSourceDirs = ii.splitPublicSourceDirs;
instrApp.dataDir = ii.dataDir;
instrApp.nativeLibraryDir = ii.nativeLibraryDir;
// 獲取或創(chuàng)建LoadedApk
LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
appContext.getClassLoader(), false, true, false);
ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
try {
java.lang.ClassLoader cl = instrContext.getClassLoader();
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate instrumentation "
+ data.instrumentationName + ": " + e.toString(), e);
}
mInstrumentation.init(this, instrContext, appContext,
new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
data.instrumentationUiAutomationConnection);
// ...
// 創(chuàng)建Application
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
// 調(diào)用Instrumentation的onCreate()方法
mInstrumentation.onCreate(data.instrumentationArgs);
// 調(diào)用Application的onCreate()方法
mInstrumentation.callApplicationOnCreate(app);
}
- 上面代碼有兩個(gè)LoadedApk對(duì)象,分別是
data.info
和pi
井氢,由下面獲取LoadedApk代碼可以知道都是同一個(gè)LoadedApk對(duì)象
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
// 根據(jù)包名加載緩存中的LoadedApk 或 創(chuàng)建LoadedApk
}
- 到這里我們知道ClassLoader來自
appContext.getClassLoader()
弦追,代碼跟蹤最終調(diào)用LoadedApk的getClassLoader()方法
public ClassLoader getClassLoader() {
synchronized (this) {
if (mClassLoader != null) {
return mClassLoader;
}
if (mIncludeCode && !mPackageName.equals("android")) {
// ...
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,
mBaseClassLoader);
} else {
if (mBaseClassLoader == null) {
mClassLoader = ClassLoader.getSystemClassLoader();
} else {
mClassLoader = mBaseClassLoader;
}
}
return mClassLoader;
}
}
- 可以看到LoadedApk屬性mClassLoader就是整個(gè)App的使用的ClassLoader
ZenusPlugin 類加載
- 通過替換系統(tǒng)LoadedApk的ClassLoader為ZeusClassLoader,利用ZeusClassLoader優(yōu)先查找補(bǔ)丁中的類花竞,若存在就返回劲件,不存在則再查找宿主中的類
- 優(yōu)先查找補(bǔ)丁中的類是先通過反射宿主ClassLoader的parent來完成
ZeusClassLoader
- 空ClassLoader,容器作用
- ZeusPluginClassLoader[]约急,每個(gè)插件對(duì)應(yīng)一個(gè)ZeusPluginClassLoader
ZeusHotfixClassLoader
- 補(bǔ)丁包類加載器零远,加載補(bǔ)丁包的時(shí)候會(huì)替換插件包的parent ClassLoader
加載插件代碼
/**
* 啟動(dòng)插件
*
*/
public void startPlugin() {
PluginManager.loadLastVersionPlugin(MyApplication.PLUGIN_TEST);
try {
Class cl = PluginManager.mNowClassLoader.loadClass(PluginManager.getPlugin(MyApplication.PLUGIN_TEST).getPluginMeta().mainClass);
Intent intent = new Intent(this, cl);
//這種方式為通過在宿主AndroidManifest.xml中預(yù)埋activity實(shí)現(xiàn)
// startActivity(intent);
//這種方式為通過欺騙android系統(tǒng)的activity存在性校驗(yàn)的方式實(shí)現(xiàn)
PluginManager.startActivity(this,intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
Small 類加載
- 通過單ClassLoader模式,通過反射Application的ClassLoader將插件包的dex文件添加到DexClassLoader中厌蔽,通過添加到DexFile Array Head實(shí)現(xiàn)插件化
VirtualAPK
- 具備單ClassLoader和多ClassLoader模式牵辣,具體是可配置的,默認(rèn)是單ClassLoader結(jié)合多ClassLoader
- apk直接作為插件躺枕,在VirtualAPK中被封裝成LoadedPlugin服猪,LoadedPlugin具有自己的DexClassLoader,同時(shí)根據(jù)配置判斷是否將LoadedPlugin的DexClassLoader中DexPathList合并到宿主ClassLoader
單ClassLoader vs 多ClassLoader
- 當(dāng)加載某個(gè)類的時(shí)候拐云,如果不知道在哪個(gè)插件罢猪,通過單ClassLoader直接查找比較方便,但是查找過程比多ClassLoader小范圍查找會(huì)比較慢
- 多ClassLoader需要管理多個(gè)ClassLoader叉瘩,單新的補(bǔ)丁插件來時(shí)需要替換等操作膳帕,不像單ClassLoader模式,直接將新插件dex文件置于DexPathList頭部即可
- VirtualAPK采用兩個(gè)模式,其實(shí)是一種中和危彩,當(dāng)不知道要啟動(dòng)的類在哪個(gè)插件則可以直接通過Class.forName查找攒磨,當(dāng)知道要啟動(dòng)的類在哪個(gè)插件則可以通過對(duì)應(yīng)LoadedPlugin的API查找