插件化的基本概念
我們在第一篇文章中就介紹過插件化的基本概念,這里再強調一次。
隨著下面這些問題的出現:
- APP的體積越來越大,功能模塊越來越多
- 模塊之間的耦合度高,協同開發溝通成本越來越大
- 方法數目可能超過65535,APP占用的內存過大
相應的解決辦法:
- 將一個大的APK按照業務劃分為多個小的APK
- 每個小的APK又可以獨立運行、又可以依附于宿主APK運行
那么,就會有如下優勢:
- 業務模塊之間基本完全解偶
- 協同并行開發成為可能,提高了Gradle的編譯速度
- 業務模塊可以按照需要進行加載,降低內存的占用
于是乎,插件化的技術油然而生。
在插件化的技術中,有幾個常見的屬于:
- 宿主:主APP,也叫作Host,它可以獨立運行,也可以加載插件。宿主必須安裝到用戶手機上,提供基本的類庫與功能
- 插件:也叫作Plugin,跟普通APP一樣,可以獨立運行,也可以由宿主進行加載
- 插件化:將一個應用按照宿主&插件的方式改造的過程就叫做插件化
以Small為例,應用插件化改造后的項目架構如下(Small框架的插件以so為后綴):
插件化與組件化、熱修復的對比
插件化與組件化的對比:
- 組件化是一種編程思想,插件化是一種技術
- 組件化是為了提高代碼的高度可復用性;插件化是為了解決應用越來越龐大等問題而出現的
插件化與熱修復(動態更新)對比:
- 兩者都是動態加載技術的應用
- 使用場景不同,熱修復是為了解決線上的不過或者小功能的更新而出現的;插件化是為了解決應用越來越龐大等問題而出現的
具體的區分可以用下圖來展示:
插件化原理
插件化的核心原理有一下幾點:
- Class文件加載Dex原理
- Android資源加載與管理
- 四大組件的加載與管理
- Java反射原理
- so庫的加載原理
- Android系統服務的運行原理
- Gradle打包原理
- 清單文件的合并處理
不同的框架,原理都有差異,鑒于文章篇幅,在這里只介紹最核心、最重要的部分。
- Dex(APK)的加載原理
首先我們需要掌握Android Classloader的分類和基本原理,這在前面的文章中已經有所介紹。
Dex(APK)的加載相關的核心代碼如下:
private void loadApk(String apkPath) {
File optDir = getDir("opt", MODE_PRIVATE);
//初始化classLoader,optDir是Dex文件的解壓目錄
DexClassLoader classLoader = new DexClassLoader(apkPath,
optDir.getAbsolutePath(), null, this.getClassLoader());
try {
Class cls = classLoader.loadClass("具體的一個類");
if (cls != null) {
//通過反射創建對象、調用方法
Object instacne = cls.newInstance();
Method method = cls.getMethod("方法名");
method.invoke(instacne);
}
} catch (Exception e) {
e.printStackTrace();
}
}
一般來說,插件化框架都會進行自定義ClassLoader,以便于更好地管理并維護ClassLoader:
public class CustomClassLoader extends DexClassLoader {
public CustomClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData != null) {
return defineClass(name, classData, 0, classData.length);
} else {
throw new ClassNotFoundException();
}
}
private byte[] getClassData(String name) {
try {
InputStream inputStream = new FileInputStream(name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int buffersize = 4096;
byte[] buffer = new byte[buffersize];
int bytesNumRead = -1;
while ((bytesNumRead = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
可以看到這里改寫了findClass方法,先通過自己的getClassData方法去查找類,防止了加載不同Dex文件的同一個類不會被重復加載的問題。
- Android資源加載與管理
AssetManager、resoures等類與資源的加載與管理息息相關,無論是加載有ID或者沒有ID的文件,如下圖所示:
插件化框架需要為每個插件創建對應的加載器、AssetManager、resoures。代碼如下所示:
//為插件apk創建對應的classLoader
private static DexClassLoader createPluginDexClassLoader(String apkPath) {
DexClassLoader classLoader = new DexClassLoader(apkPath,
mOptFile.getAbsolutePath(), null, null);
return classLoader;
}
//為對應的插件創建AssetManager
private static AssetManager createPluginAssetManager(String apkPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath",
String.class);
addAssetPath.invoke(assetManager, apkPath);
return assetManager;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//為對應的插件創建resoures
private static Resources createPluginResources(String apkPath) {
AssetManager assetManager = createPluginAssetManager(apkPath);
Resources superResources = mContext.getResources();
Resources pluginResources = new Resources(assetManager,
superResources.getDisplayMetrics(), superResources.getConfiguration());
return pluginResources;
}
AssetManager、Resources是插件資源加載與管理的最核心的類,通過反射調用AssetManager的addAssetPath把插件中的資源加載進來,并且會進行相應的管理。
注:如果是安裝過的APK,系統會自動幫我們創建AssetManager,我們直接通過Context就可以獲取AssetManager;如果是自己加載插件APK,就需要自己創建AssetManager了。
- 四大組件的加載與管理
四大組件最常用就是Activity,因此這里以Activity為例進行介紹。
四大組件需要解決是否需要在清單文件中注冊的難題,目前流行的插件化加載與管理Activity原理有兩種:
- 通過ProxyActivity進行代理
- 通過Hook系統服務Activity Manager Service繞過清單文件的注冊
相應的資料可以參考任玉剛大神以及LooperJing大神的文章:
http://blog.csdn.net/singwhatiwanna/article/details/22597587