DynamicLoadApk 應該算是 Android 插件化諸多框架中資歷比較老的一個了。它的項目地址在:dynamic-load-apk。該項目運行以後的效果是,使用 Gradle 編譯出插件包和宿主包,都是以 APK 的形式。安裝宿主包以後,經過 ADB 將插件包 push 到手機中。啓動宿主包時,它會自動進行掃描將插件加載到應用中。點擊插件以後,進入到插件的應用界面。java
印象中最初接觸的插件都是以單獨安裝的形式存在的,好比我能夠作一個基礎的應用,而後在該應用的基礎上開發插件。用戶能夠對插件進行選擇,而後下載並安裝,以讓本身的應用具備更豐富的功能。插件化也算是一種比較實用的技術,畢竟咱們使用 Chrome 和 AS 的時候不是同樣要加載插件。只是比較反感的是去修改底層的代碼,容易給系統帶來不穩定因素不說,技術到了一些人手裏,你知道他用來幹什麼。插件化挺好,但真的要去推廣這項技術,仍是看好 Google 官方去進行規範。git
技術要服務於產品,好的產品不必定要高超的技術,技術並非最重要的,重要的是你究竟想要表達什麼。這就像國內不少人只注重數理化,不注重人文學科。相比於國內的技術精英,我仍是比較贊同 Google 站在整個生態的角度去考慮技術演進。前些日子社區裏對插件化的討論:移動開發的羅曼蒂克消亡史。好吧,我本身的理解是,這歷來就不是什麼羅曼蒂克。github
DynamicLoadApk 插件化的實現方式仍是挺有意思的,它使用純 Java 實現,沒有涉及 Native 層的代碼,下面我理了下 DynamicLoadApk 的 Demo 程序的整個執行過程。後續的文章咱們就圍繞這張圖進行,安全
首先是掃描文件路徑並加載 APK,這裏須要解析 APK 文件的信息,它是本質上是經過 PMS 實現的;框架
public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib) {
mFrom = DLConstants.FROM_EXTERNAL;
// 經過 PMS 獲取包信息,這裏獲取了 Activity 和 Service 的信息
PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
if (packageInfo == null) {
return null;
}
DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);
if (hasSoLib) {
copySoLib(dexPath);
}
return pluginPackage;
}
複製代碼
而後,它經過調用 preparePluginEnv()
方法來建立 AssetManager, DexClassLoader 和 Resource 等。咱們的插件類加載各類資源和類的時候使用的就是這哥仨:ui
private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {
DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName);
if (pluginPackage != null) {
return pluginPackage;
}
DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
AssetManager assetManager = createAssetManager(dexPath);
Resources resources = createResources(assetManager);
// create pluginPackage
pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
mPackagesHolder.put(packageInfo.packageName, pluginPackage);
return pluginPackage;
}
private DexClassLoader createDexClassLoader(String dexPath) {
File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);
dexOutputPath = dexOutputDir.getAbsolutePath();
DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, mNativeLibDir, mContext.getClassLoader());
return loader;
}
private AssetManager createAssetManager(String dexPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexPath);
return assetManager;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private Resources createResources(AssetManager assetManager) {
Resources superRes = mContext.getResources();
Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
return resources;
}
複製代碼
而後點擊插件的時候要啓動插件的 Activity,this
PluginItem item = mPluginItems.get(position);
DLPluginManager pluginManager = DLPluginManager.getInstance(this);
pluginManager.startPluginActivity(this, new DLIntent(item.packageInfo.packageName, item.launcherActivityName));
複製代碼
這裏會把要啓動的包名和啓動類名包裝到 DLIntent 中。DLIntent 是 Intent 的子類。啓動插件的進一步的邏輯在 DLPluginManager 的 startPluginActivity()
方法中。按照上文的描述,這裏主要作了如下四件事情:spa
Context.startActivity()
插件 Activity 須要調用代理 Activity 來執行。DLIntent.setClass()
啓動代理類:要啓動的代理類多是 DLProxyFragmentActivity 和 DLProxyActivity,因此這裏咱們先使用 getProxyActivityClass()
獲得代理類。該方法中使用了 Class 的 isAssignableFrom()
方法來判斷某個實例是不是指定類型的。好比 DLBasePluginActivity.class.isAssignableFrom(clazz)
表示 clazz 是不是 DLBasePluginActivity 類型的。@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
// 1.判斷要啓動的 Activity 是不是插件 Activity
if (mFrom == DLConstants.FROM_INTERNAL) {
dlIntent.setClassName(context, dlIntent.getPluginClass());
performStartActivityForResult(context, dlIntent, requestCode);
return DLPluginManager.START_RESULT_SUCCESS;
}
// 2.判斷包名,獲取插件相關信息
String packageName = dlIntent.getPluginPackage();
if (TextUtils.isEmpty(packageName)) {
throw new NullPointerException("disallow null packageName.");
}
DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
if (pluginPackage == null) {
return START_RESULT_NO_PKG;
}
// 3.使用插件的 DexClassLoader 加載啓動類
final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
if (clazz == null) {
return START_RESULT_NO_CLASS;
}
Class<? extends Activity> activityClass = getProxyActivityClass(clazz);
if (activityClass == null) {
return START_RESULT_TYPE_ERROR;
}
// 4.使用 DLIntent.setClass() 啓動代理類,並傳入插件類和包信息
dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
dlIntent.setClass(mContext, activityClass);
performStartActivityForResult(context, dlIntent, requestCode);
return START_RESULT_SUCCESS;
}
private Class<? extends Activity> getProxyActivityClass(Class<?> clazz) {
Class<? extends Activity> activityClass = null;
if (DLBasePluginActivity.class.isAssignableFrom(clazz)) {
activityClass = DLProxyActivity.class;
} else if (DLBasePluginFragmentActivity.class.isAssignableFrom(clazz)) {
activityClass = DLProxyFragmentActivity.class;
}
return activityClass;
}
private void performStartActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
if (context instanceof Activity) {
((Activity) context).startActivityForResult(dlIntent, requestCode);
} else {
context.startActivity(dlIntent);
}
}
複製代碼
這裏須要注意下,咱們的插件 Activity 是須要繼承 DLBasePluginActivity 或者 DLProxyFragmentActivity。這兩個類中重寫了 Activity 的許多生命週期方法。在代理 Activity 啓動以後,代理 Activity 會被傳遞到前面兩個基類中。好比,當插件類想要獲取 AssetsManager 的時候,會調用到這兩個基類的 getAssetsManager()
,而後基類經過代理類獲得以前咱們建立的 AssetsManager.插件
按照上述流程,代理類被正常啓動。啓動以後它會建立 DLProxyImpl 實例,並在 onCreate()
方法中調用 DLProxyImpl 的 onCreate()
方法:代理
public void onCreate(Intent intent) {
intent.setExtrasClassLoader(DLConfigs.sPluginClassloader);
mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE);
mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS);
mPluginManager = DLPluginManager.getInstance(mProxyActivity);
mPluginPackage = mPluginManager.getPackage(mPackageName);
mAssetManager = mPluginPackage.assetManager;
mResources = mPluginPackage.resources;
initializeActivityInfo();
handleActivityInfo();
launchTargetActivity();
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
protected void launchTargetActivity() {
try {
Class<?> localClass = getClassLoader().loadClass(mClass);
Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {});
mPluginActivity = (DLPlugin) instance;
((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
mPluginActivity.attach(mProxyActivity, mPluginPackage);
Bundle bundle = new Bundle();
bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
mPluginActivity.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
複製代碼
這裏的主要邏輯在上述兩個方法中。第一個方法中會根據包名從 DLPluginManager 中獲取包的類加載器,而後使用該加載其加載器加載插件類,反射觸發其構造方法,獲取實例。而後調用代理 Activity 的 attach()
方法將該插件類複製給代理類。而後當 AMS 回調代理類的各個生命週期的時候,代理類調用插件類的各個生命週期。(這裏會使用類加載器再次加載插件類,其實這是不必的,咱們能夠直接使用 Intent 將插件類的 Class 經過序列化的方式傳遞過來,而後直接觸發其構造方法便可,無需再次執行類加載邏輯。)
好了,以上就是 DynamicLoadApk 的原理,其實本質就是:插件類做爲一個普通的類被調用,它不歸 AMS 負責。當咱們啓動插件的時候,實際啓動的是代理類,當 AMS 回調代理類的生命週期的時候,代理類再調用插件類的各個生命週期方法。只是,對資源和類加載的部分須要注意下,由於咱們須要進行自定義配置來把它們的路徑指向咱們的插件包。