關於Android外掛框架機制的介紹,我打算分幾章來介紹,這是第一篇也就是預熱篇。
Android 外掛框架機制系列文章:
Android 外掛框架機制之Small
Android 外掛框架機制之DroidPlugin
為什麼使用外掛化
隨著應用的模組化的不斷增加,APK的體積不斷增長,方法數很可能會引發64K問題(解決方案),谷歌提供的方案並不完美,而且APK的啟動速度會受影響。
- 提高工程的執行速度,每個模組作為一個獨立的外掛進行開發和除錯。
- 提高應用的啟動速度,應用啟動時可以選擇只載入必須的模組,其他模組使用時再載入
- 多團隊並行開發
- 線上動態載入或更新模組
- 靈活的功能配置
基礎知識
機制
外掛化的根本思路就是讓你的應用呼叫未安裝的apk,jar,dex檔案中的方法。
在Android中,系統提供了兩個API可供選擇:
- PathClassLoader:只能載入已經安裝到Android系統中的APK檔案,這個是另一種載入思路,但是跟外掛化沒關係,這裡不提。
- DexClassLoader:支援載入外部的APK,Jar,或Dex檔案。
基礎示例
我們先寫一個外掛APK,新建一個工程,修改MainActivity:
public class MainActivity extends Activity {
private Activity otherActivity;
@Override
public void onCreate(Bundle savedInstanceState) {
boolean b = false;
if (savedInstanceState != null) {
b = savedInstanceState.getBoolean("KEY_START_FROM_OTHER_ACTIVITY", false);
if (b) {
this.otherActivity.setContentView(new TBSurfaceView(
this.otherActivity));
}
}
if (!b) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.main);
setContentView(new TBSurfaceView(this));
}
}
public void setActivity(Activity paramActivity) {
this.otherActivity = paramActivity;
}
}複製程式碼
在被載入的Activity中是不是識別和載入資原始檔的,所以不能用佈局檔案,只能用一個View。
public class TBSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder sfh;
private Thread th;
private Canvas canvas;
private Paint paint;
public TBSurfaceView(Context context) {
super(context);
th = new Thread(this);
sfh = this.getHolder();
sfh.addCallback(this);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
this.setKeepScreenOn(true);
}
public void surfaceCreated(SurfaceHolder holder) {
th.start();
}
private void draw() {
try {
canvas = sfh.lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.WHITE);
canvas.drawText("Time: " + System.currentTimeMillis(), 100,
100, paint);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (canvas != null) {
sfh.unlockCanvasAndPost(canvas);
}
}
}
public void run() {
while (true) {
draw();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
public void surfaceDestroyed(SurfaceHolder holder) {
}
}複製程式碼
然後將生成的apk,不要安裝在手機,而是存入手機,比如我們就存入sd卡的根目錄,可以使用adb命令:
adb push /Users/xxxxx/file/source/loadActivity/app/build/outputs/apk/app-debug.apk /sdcard/複製程式碼
然後我們新建一個應用,寫一個載入的方法:
public void dex() {
String apkPath = "/sdcard/app-debug.apk";
String optPath = "/mnt/sdcard/";
// String libPath = info.activityInfo.applicationInfo.nativeLibraryDir;
File dexOutputDir = getDir("dex", 0);
DexClassLoader clsLoader = new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(),
null, this.getClass().getClassLoader());
try {
Class localClass = clsLoader
.loadClass("deep.loadactivity.MainActivity");
mActivityClass = localClass;
Constructor localConstructor = localClass.getConstructor(new Class[] {});
instance = localConstructor.newInstance(new Object[] {});
mActivityInstance = instance;
Method localMethodSetActivity = localClass.getDeclaredMethod(
"setActivity", new Class[] { Activity.class });
localMethodSetActivity.setAccessible(true);
localMethodSetActivity.invoke(instance, new Object[] { this });
Method methodonCreate = localClass.getDeclaredMethod("onCreate", new Class[] { Bundle.class });
methodonCreate.setAccessible(true);
Bundle paramBundle = new Bundle();
paramBundle.putBoolean("KEY_START_FROM_OTHER_ACTIVITY", true);
paramBundle.putString("str", "MainActivity");
methodonCreate.invoke(instance, new Object[] { paramBundle });
} catch (Exception e) {
e.printStackTrace();
}
}複製程式碼
下面主要說一下DexClassLoader:
DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)複製程式碼
dexPath:被解壓的apk路徑,不能為空。
optimizedDirectory:解壓後的.dex檔案的儲存路徑,不能為空。
libraryPath:庫檔案的的搜尋路徑,一般來說是 .so 庫檔案的路徑,也可以指明多個路徑。
parent:父親載入器,一般為ClassLoader.getSystemClassLoader()。
說明
在上面的例子中我們其實是演示了一下動態載入Activity的方法,Activity被動態載入後,是沒有生命週期的,只是當做一個類來做處理,由上面程式可以看出,我們是手動呼叫onCreate方法的。
當然,我們也可以寫一些其他類或方法進行呼叫,不一定非要用Activity。
開源框架
說完了外掛機制,我們也認識到了外掛機制的一些不足,就比如剛才說的,Activity生命週期沒有了,需要手動呼叫各個方法,那給我們的開發帶來了很多不方便,但是網上的一些開源框架,解決了這些不方便,將呼叫的方法,順序封裝好,我們只管呼叫即可。
這些框架,我會在後面的文章中詳細演示。
android-pluginmgr
利用DexMaker的動態熱部署功能來實現Activity。
- 優點:
1.外掛app不需要任何規則和限制
2.技術方法相對成熟穩定 - 缺點:
1.oom問題突出
2.只支援Activity,不支援其它元件。
dynamic-load-apk
這是基於代理的方式實現外掛框架的,需要按照一定的規則來開發外掛APK。
- 優點:
1.外掛需要遵循一些規則,更加可控
2.方案簡單 - 缺點:
1.不支援this呼叫元件中的方法,需要that,有些難理解
2.不支援隱式呼叫APK內部的Activity
3.相容性問題較多
DynamicAPK
這是攜程實現的一種多APK/DEX載入的外掛框架。
- 優點:
1.很少修改即可實現改造
2.提升工程編譯速度
3.可實現熱更新
4.提高App的啟動速度 - 缺點:
1.不支援so庫
2.不支援aar,maven遠端倉庫的依賴
DroidPlugin
這是360實現的一種外掛框架,他可以直接執行第三方獨立得APK。完全不需要對APK進行修改或安裝。
- 優點:
1.支援四大元件
2.支援所有系統API
3.外掛與外掛之間,外掛與宿主之間的程式碼和資源完全隔離
4.實現了程式管理,佔用記憶體低 - 缺點:
1.不支援自定義資源的Notification
2.不支援IntentFilter
3.缺乏對Native層的Hook操作
4.由於外掛與外掛,外掛與宿主之間完全隔離,因此,如果需要通訊,需要Android系統級別的通訊方式。
Small
這是一個跨平臺的外掛化框架。
- 優點:
1.外掛編碼與資原始檔的使用與普通開發無差別
2.通過設定URI,宿主可以方便地與外掛間進行通訊
3.支援Android IOS HTML5
*缺點:
不支援Service的動態註冊。
總結
以上是我對外掛調研的一個總結,在後面的演示中,我會將主流的外掛化框架進行詳細說明。