外掛化原理(以DL框架說明)
1. 外掛化原理
DL框架的原理很簡單:
在宿主apk中,有一個ProxyActivity,即代理Activity,這個Activity相當於一個空殼,外掛中的Activity依靠ProxyActivity來對生命週期回撥、資源載入以及啟動另一個Activity等等。總而言之,ProxyActivity提供Context,外掛Activity依靠ProxyActivity來做自己想做的事情。
2. DL的實現
首先看看ProxyActivity的實現:
public class ProxyActivity extends Activity {
private static final String TAG = "ProxyActivity";
public static final String FROM = "extra.from";
public static final int FROM_EXTERNAL = 0;
public static final int FROM_INTERNAL = 1;
public static final String EXTRA_DEX_PATH = "extra.dex.path";
public static final String EXTRA_CLASS = "extra.class" ;
private String mClass;
private String mDexPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDexPath = getIntent().getStringExtra(EXTRA_DEX_PATH);
mClass = getIntent().getStringExtra(EXTRA_CLASS);
Log.d(TAG, "mClass=" + mClass + " mDexPath=" + mDexPath);
if (mClass == null) {
launchTargetActivity();
} else {
launchTargetActivity(mClass);
}
}
@SuppressLint("NewApi")
protected void launchTargetActivity() {
PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(
mDexPath, 1);
if ((packageInfo.activities != null)
&& (packageInfo.activities.length > 0)) {
String activityName = packageInfo.activities[0].name;
mClass = activityName;
launchTargetActivity(mClass);
}
}
@SuppressLint("NewApi")
protected void launchTargetActivity(final String className) {
Log.d(TAG, "start launchTargetActivity, className=" + className);
File dexOutputDir = this.getDir("dex", 0);
final String dexOutputPath = dexOutputDir.getAbsolutePath();
ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
DexClassLoader dexClassLoader = new DexClassLoader(mDexPath,
dexOutputPath, null, localClassLoader);
try {
Class<?> localClass = dexClassLoader.loadClass(className);
Constructor<?> localConstructor = localClass
.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {});
Log.d(TAG, "instance = " + instance);
Method setProxy = localClass.getMethod("setProxy",
new Class[] { Activity.class });
setProxy.setAccessible(true);
setProxy.invoke(instance, new Object[] { this });
Method onCreate = localClass.getDeclaredMethod("onCreate",
new Class[] { Bundle.class });
onCreate.setAccessible(true);
Bundle bundle = new Bundle();
bundle.putInt(FROM, FROM_EXTERNAL);
onCreate.invoke(instance, new Object[] { bundle });
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以看到,這裡的ProxyActivity目前只是實現從外掛apk中載入類並例項化,然後再將這個ProxyActivity例項物件通過setProxy函式賦值給外掛apk中的類。再呼叫apk中類的onCreate函式。
也就是說,外掛apk中的Activity需要持有ProxyActivity的引用,即外掛apk的Activity有了Context物件的引用,以後外掛中的Activity啟動第二個Activity或者是呼叫其他需要Context引數的函式時候,就可以通過ProxyActivity來做到了。
因為每個外掛Activity都需要提供setProxy()函式,以及還需要判斷:
當前的外掛apk是使用者自己安裝的還是通過宿主apk加載出來的
所以,外掛apk中所有的Activity需要一系列的相同程式碼,我們只需建立一個BaseActivity,把這些工作放到BaseActivity,這樣就可以避免寫重複程式碼,而且也可以讓我們外掛中的Activity更“像”正常的Activity。
看看BaseActivity:
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
public class BaseActivity extends Activity {
private static final String TAG = "Client-BaseActivity";
public static final String FROM = "extra.from";
public static final int FROM_EXTERNAL = 0;
public static final int FROM_INTERNAL = 1;
public static final String EXTRA_DEX_PATH = "extra.dex.path";
public static final String EXTRA_CLASS = "extra.class";
public static final String PROXY_VIEW_ACTION = "com.ryg.dynamicloadhost.VIEW";
public static final String DEX_PATH = "/mnt/sdcard/DynamicLoadHost/plugin.apk";
protected Activity mProxyActivity;
protected int mFrom = FROM_INTERNAL;
public void setProxy(Activity proxyActivity) {
Log.d(TAG, "setProxy: proxyActivity= " + proxyActivity);
mProxyActivity = proxyActivity;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mFrom = savedInstanceState.getInt(FROM, FROM_INTERNAL);
}
if (mFrom == FROM_INTERNAL) {
super.onCreate(savedInstanceState);
mProxyActivity = this;
}
Log.d(TAG, "onCreate: from= " + mFrom);
}
protected void startActivityByProxy(String className) {
if (mProxyActivity == this) {
Intent intent = new Intent();
intent.setClassName(this, className);
this.startActivity(intent);
} else {
Intent intent = new Intent(PROXY_VIEW_ACTION);
intent.putExtra(EXTRA_DEX_PATH, DEX_PATH);
intent.putExtra(EXTRA_CLASS, className);
mProxyActivity.startActivity(intent);
}
}
@Override
public void setContentView(View view) {
if (mProxyActivity == this) {
super.setContentView(view);
} else {
mProxyActivity.setContentView(view);
}
}
@Override
public void setContentView(View view, LayoutParams params) {
if (mProxyActivity == this) {
super.setContentView(view, params);
} else {
mProxyActivity.setContentView(view, params);
}
}
@Deprecated
@Override
public void setContentView(int layoutResID) {
if (mProxyActivity == this) {
super.setContentView(layoutResID);
} else {
mProxyActivity.setContentView(layoutResID);
}
}
@Override
public void addContentView(View view, LayoutParams params) {
if (mProxyActivity == this) {
super.addContentView(view, params);
} else {
mProxyActivity.addContentView(view, params);
}
}
}
可以看到,在BaseActivity的onCreate函式中,通過判斷當前的Activity啟動時來自宿主Apk還是使用者自己安裝來設定mProxyActivity物件,如果是使用者自己安裝的,直接賦值this,如果是來自宿主apk的呼叫,則賦值為宿主中的ProxyActivity物件。
3. DL的資源管理和生命週期
前面我們的ProxyActivity只是簡單回調了onCreate函式,還有其他的函式根本沒有呼叫。另外,外掛APK中的Activity的佈局檔案是外掛APK中的資原始檔,並不存在於宿主的APK中,無法通過ProxyActivity的Context來載入,因此,如果外掛中的Activity使用類似R.layout.XXX的方式會出錯。接下來要解決兩個問題。
3.1 資源管理
既然外掛中的Activity無法直接通過ProxyActivity這個Context來載入,那麼我們可以通過反射機制,修改ProxyActivity中跟載入資源相關的api,使得能直接通過ProxyActivity載入資源。
Context中,跟資源相關的api就是如下兩個抽象函式:
/** Return an AssetManager instance for your application's package. */
public abstract AssetManager getAssets();
/** Return a Resources instance for your application's package. */
public abstract Resources getResources();
也就是說,只要我們重寫這兩個函式,使得返回的AssetManager和Resources物件在載入資源時,能夠去我們的外掛apk中尋找資源,這樣就可以解決問題啦。
首先,我們看看如何構造能夠從外掛apk中查詢資源的AssetManager和Resources物件。通過loadResources函式來構造這兩個物件,程式碼如下所示:
protected void loadResources() {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
//通過反射機制,呼叫AssetManager物件的addAssetPath函式
//傳入的String引數mDexPath就是我們的外掛apk路徑
//為assetManager物件新增一條資源查詢目錄
addAssetPath.invoke(assetManager, mDexPath);
//將我們構造出來的AssetManager物件儲存到成員變數
mAssetManager = assetManager;
} catch (Exception e) {
e.printStackTrace();
}
Resources superRes = super.getResources();
//構造Resources物件
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),
superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}
說明:載入的方法是通過反射.
通過呼叫AssetManager中的addAssetPath方法,我們可以將一個apk中的資源載入到Resources中.
由於addAssetPath是隱藏api我們無法直接呼叫,所以只能通過反射,下面是它的宣告,通過註釋我們可以看出,傳遞的路徑可以是zip檔案也可以是一個資源目錄,而apk就是一個zip,所以直接將apk的路徑傳給它,資源就載入到AssetManager中了,然後再通過AssetManager來建立一個新的Resources物件,這個物件就是我們可以使用的apk中的資源了,這樣我們的問題就解決了。
/**
* Add an additional set of assets to the asset manager. This can be
* either a directory or ZIP file. Not for use by applications. Returns
* the cookie of the added asset, or 0 on failure.
* {@hide}
*/
public final int addAssetPath(String path) {
int res = addAssetPathNative(path);
return res;
}
接下來就是,我們重寫那兩個抽象函式:
@Override
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
@Override
public Resources getResources() {
return mResources == null ? super.getResources() : mResources;
}
好啦,資源載入的問題解決了,我們可以在外掛中的Activity使用R開頭的資源啦!
3.2 生命週期
個人感覺,生命週期比較好管理,因為ProxyActivity本身就是有生命週期的,只需在ProxyActivity中回撥外掛Activty的對應的生命週期函式即可。
首先,把需要回調的生命週期函式通過反射的方法,儲存起來:
//傳入的localClass即為外掛中的Activty
protected void instantiateLifecircleMethods(Class<?> localClass) {
String[] methodNames = new String[] {
"onRestart",
"onStart",
"onResume",
"onPause",
"onStop",
"onDestory"
};
for (String methodName : methodNames) {
Method method = null;
try {
method = localClass.getDeclaredMethod(methodName, new Class[] { });
method.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put(methodName, method);
}
Method onCreate = null;
try {
onCreate = localClass.getDeclaredMethod("onCreate", new Class[] { Bundle.class });
onCreate.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put("onCreate", onCreate);
Method onActivityResult = null;
try {
onActivityResult = localClass.getDeclaredMethod("onActivityResult",
new Class[] { int.class, int.class, Intent.class });
onActivityResult.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put("onActivityResult", onActivityResult);
}
然後,在ProxyActivity的各個生命週期函式中,對外掛Activity的生命週期函式進行回撥:
@Override
protected void onResume() {
super.onResume();
Method onResume = mActivityLifecircleMethods.get("onResume");
if (onResume != null) {
try {
onResume.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
protected void onPause() {
Method onPause = mActivityLifecircleMethods.get("onPause");
if (onPause != null) {
try {
onPause.invoke(mRemoteActivity, new Object[] { });
} catch (Exception e) {
e.printStackTrace();
}
}
super.onPause();
}
現在就解決了生命週期的問題啦!
4. 總結
總結一下DL框架的思想:
其本質就是,整個過程中,在Android系統中管理的Activity只是宿主apk中的ProxyActivity,而外掛中的Activity本質就是個普通類而已,只不過ProxyActivity所有的邏輯程式碼(資源載入、佈局顯示等等)通過外掛中的Activity來完成。
因此,在理解的時候,可以參照如下:
站在宿主apk的角度去想的時候:
外掛中的Activity只是ProxyActivity呼叫的“工具類”,這個“工具類”幫助ProxyActivity執行佈局顯示等等相關的邏輯。
站在外掛Activity的角度去想的時候:
當前外掛Activity沒有實際的上下文,我們需要藉助ProxyActivity提供的上下文來完成Android系統環境中的api的呼叫。
相關推薦
外掛化原理(以DL框架說明)
1. 外掛化原理 DL框架的原理很簡單: 在宿主apk中,有一個ProxyActivity,即代理Activity,這個Activity相當於一個空殼,外掛中的Activity依靠ProxyActivity來對生命週期回撥、資源載入以及啟動另一個Act
Android外掛化原理(一)Activity外掛化
相關文章 前言 四大元件的外掛化是外掛化技術的核心知識點,而Activity外掛化更是重中之重,Activity外掛化主要有三種實現方式,分別是反射實現、介面實現和Hook技術實現。反射實現會對效能有所影響,主流的外掛化框架沒有采用此方式,關於介面實
PhoneGap 外掛呼叫方法(以Camera為例)
PhoneGap 官方提供了各種外掛的API呼叫,我們就一Camera為例,總結一下PhoneGap外掛的呼叫。主要可以概括為以下幾步: 新建工程 -> 外掛下載 -> 工程編譯 -> 外掛匯入Eclipse -> 外掛呼叫 執行cmd,鍵
Android Plugin插樁式實現外掛化開發(一)-實現原理及Activity外掛化實現
1. 前言在現在一些大型的Android應用中都採用了外掛化的開發方式,比如美團,支付寶及我們常用的微信等採用了插修的化的開發方式來進行開發的,既然國內一流的網際網路公司都採用這樣的方式來開發那它一定能帶給開發部署大型應用帶來很大的便捷,那麼外掛化的優勢在哪裡呢?1.1 外掛
Android插件化原理(一)Activity插件化
ssa AS 直接 接收 hat ati 操作 運行 for Android深入四大組件系列 Android解析AMS系列 Android解析ClassLoader系列 前言 四大組件的插件化是插件化技術的核心知識點,而Activity插件化更是重中之重,Activity插
雜記——controller的工作原理(以CSDN網站導航條為例)
最近初學springMVC,今天明白了controller和jsp之間聯絡的工作原理,於是記一個小筆記。 先看一個程式碼 下面是controller中的一個cookieBind方法 @RequestMapping(value="/cookiebind", method = {Reques
Android外掛化原理和實踐 (四) 之 合併外掛中的資源
我們繼續來學習Android外掛化相關知識,還是要圍繞著三個根本問題來展開。在前面兩章中已經講解過第一個根本問題:在宿主中如何去載入外掛以及呼叫外掛中類和元件程式碼。Demo中使用了Service來演示,因為還沒有解決載入外掛中資源的問題,用Activity不好展示。所以本文將要從資源的載入機制
Android外掛化原理和實踐 (七) 之 專案實踐
我們在前面一系列文章中已經介紹完了外掛化原理以及三個根本問題的解決方案,本文主要就是作為前面幾篇文章的一個總結,通過專案實踐將前面的知識點串起來使完成一個入門級簡單的外掛化工程以及在實際外掛化開發中遇到的一些總結。 實踐 我們先通過Android Studio建立一個工程,工程中包括了兩
Router:一款單品、元件化、外掛化全支援的路由框架
簡介 由於現在已經有很多各種各樣的路由框架了,所以在這裡。我也不再贅述什麼是路由?路由框架的意義是什麼之類的了。 特性 安全: 路由啟動過程中。全程catch住異常並通知使用者。完全不用擔心crash問題。 強大的攔截器功能:與大部分的路由不同。提供三種路由攔截器機制
Android 外掛化分析(3)- Activity啟動流程
在真正分析外掛化技術前,我們必須瞭解一些必要的關於Android四大元件的相關知識。 以Activity為例,我們需要了解Activity啟動過程,才能有效的進行Hook實現外掛化。 以Android 8.1為例 我們啟動一個Activity通常會使用startActi
Android外掛化原理解析——ContentProvider的外掛化
目前為止我們已經完成了Android四大元件中Activity,Service以及BroadcastReceiver的外掛化,這幾個元件各不相同,我們根據它們的特點定製了不同的外掛化方案;那麼對於ContentProvider,它又有什麼特點?應該如何實現它的外掛化? 與Activity,Broadcast
android外掛化原理
最近幾年移動開發業界興起了「 外掛化技術 」的旋風,各個大廠都推出了自己的外掛化框架,各種開源框架都評價自身功能優越性,令人目不暇接。隨著公司業務快速發展,專案增多,開發資源卻有限,如何能在有限資源內滿足需求和專案的增長,同時又能快速響應問題和迭代新需求,這就是一個矛盾點。此
Android外掛化原理和實踐 (八) 之 注意事項
注意事項 關於外掛化的三個根本問題和解決方案就已經全部介紹完畢了,前一篇文章也通過一個入門級的工程來完整地演示了。但是其實目前熱門的外掛化框架也遠不止這些內容,我們在實際開發中也遠不止這麼簡單。前面介紹的所有知識點只是一個入門而已,外掛化雖然帶來了很多便利,但是在開發過程中也增添了不少麻煩和
Android外掛化原理和實踐 (六) 之 四大元件解決方案
在前面的幾篇文章中已經介紹完了Android外掛化的第一和第二個根本問題,就是宿主和外掛的程式碼互相呼叫問題和外掛中資源的讀取問題。現剩下的就是Android外掛化裡最麻煩的第三個根本問題,也就是在外掛中使用四大元件的問題。我們知道,目前外掛中的四大元件要想正常使用就必須要在宿主中的Androi
Android外掛化原理和實踐 (五) 之 解決合併資源後資源Id衝突的問題
Android外掛化中,要解決資源的問題,有些外掛化框架會選擇不合並資源,這樣就得維護多套mResources變數,這樣的話難免開發上沒有那麼的靈活和方便。所以一般地都是選擇合併資源,也就是我們上一遍文章《Android外掛化原理和實踐 (四) 之 合併外掛中的資源》介紹的辦法。但是合併後資源i
Android外掛化原理和實踐 (三) 之 載入外掛中的元件程式碼
我們在上一篇文章《Android外掛化原理和實踐 (二) 之 載入外掛中的類程式碼》中埋下了一個懸念,那就是通過構造一個DexClassLoader物件後使用反射只能反射出普通的類,而不能正常使用四大元件,因為會報出異常。今天我們就來解開這個懸念和提出解決方法。 1 揭開懸念 還記得《A
Android外掛化原理和實踐 (二) 之 載入外掛中的類程式碼
我們在上一篇文章《Android外掛化原理和實踐 (一)之 外掛化簡介和基本原理簡述》中介紹了外掛化一些基本知識和歷史,最後還列出了三個根本問題。接下來我們打算圍繞著這三個根本問題展開對外掛化的學習。首先本章將介紹第一個根本問題:宿主和外掛中如何相互呼叫程式碼。要實現它們相互呼叫,就得要宿主先將
Android外掛化原理和實踐 (一) 之 外掛化簡介和基本原理簡述
1 外掛化簡介 Android外掛化技術是一種這幾年間非常火爆的技術,也是隻有在中國才流行起來的技術,這幾年間每每開發者大會上幾乎都會提起關於外掛化技術和相關方向。在國內各大網際網路公司無不都有自己的外掛化框架。 外掛化技術到底是什麼? 其實很好理解,像某些App中整合了很多功能點,
Android外掛化探索(二)資源載入
前情提要 PathClassLoader和DexClassLoader的區別 DexClassLoader的原始碼如下: public class DexClassLoader extends BaseDexClassLoader {
Android 外掛化原理解析——Service的外掛化
在 Activity生命週期管理 以及 廣播的管理 中我們詳細探討了Android系統中的Activity、BroadcastReceiver元件的工作原理以及它們的外掛化方案,相信讀者已經對Android Framework和外掛化技術有了一定的瞭解;本文將探討