引言
上篇文章我們有介紹如何獲取插件的Resource加載其資源,例子支持加載res文件夾下的素材資源例如動畫、圖片、布局、字符串等,本篇文章介紹宿主如何跳轉到插件的Activity。
跳轉到插件的Activity方法比較多,但是目前為止都是一件挺復雜的事兒。常見的方法有宿主代理Activity模式和宿主動態創建Activity模式。兩者區別是宿主代理無需在宿主中注冊Activity,所有跳轉均由一個傀儡Activity完成,這樣的好處是無需過多的改變宿主即可完成插件開發,但是插件Activity并不享有系統提供的生命周期,其所有生命周期必須由宿主通過反射的方式傳遞。動態創建的好處是Activity有著自己的生命周期,但是必須提前在宿主AndroidManifest文件中注冊。本文介紹簡單的代理Activity模式。(更多詳情可見這里)
Demo創建
- 在PluginA工程中創建BaseActivity.java,關鍵代碼如下:
public class BaseActivity extends Activity {
....
// 通過隱式調用宿主的ProxyActivity
public static final String PROXY_VIEW_ACTION = "h3c.pluginapp.ProxyActivity";
// 因為插件的Activity沒有Context,所以一切與Context的行為都必須通過宿主代理Activity實現!
protected Activity mProxyActivity;
public void setProxy(Activity proxyActivity) {
mProxyActivity = proxyActivity;
}
@Override
public void setContentView(int layoutResID) {
mProxyActivity.setContentView(layoutResID);
}
@Override
public View findViewById(int id) {
return mProxyActivity.findViewById(id);
}
// 插件的startActivity其實就是調用宿主開啟另一個ProxyActivity
public void startActivity(String className) {
Intent intent = new Intent(PROXY_VIEW_ACTION);
intent.putExtra("Class", className);
mProxyActivity.startActivity(intent);
}
....
}
- 在PluginA工程中創建AActivity.java和BActivity.java。讓AActivity可以點擊跳轉到BActivity即可。
- 重新編譯PluginA,將Apk替換到宿主中。
- 在宿主工程中創建ProxyActivity.java并在AndroidManifest文件中注冊。關鍵代碼:
public class ProxyActivity extends Activity {
....
// 因為插件Activity獲得的是宿主的Context,這樣就拿不到自己的資源,所以這里要用插件的Resource替換ProxyActivity的Resource!
private Resources mBundleResources;
@Override
protected void attachBaseContext(Context context) {
replaceContextResources(context);
super.attachBaseContext(context);
}
public void replaceContextResources(Context context){
try {
Field field = context.getClass().getDeclaredField("mResources");
field.setAccessible(true);
if (null == mBundleResources) {
mBundleResources = AssertsDexLoader.getBundleResource(context, context.getDir(AssertsDexLoader.APK_DIR, Context.MODE_PRIVATE).
getAbsolutePath() + "/app-debug.apk");
}
field.set(context, mBundleResources);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ProxyActivity要加載的插件Activity名字
String className = getIntent().getStringExtra("Class");
try {
Class<?> localClass = AssertsDexLoader.loadClass(className);
Constructor<?> localConstructor = localClass
.getConstructor(new Class[] {});
Object instance = localConstructor.newInstance(new Object[] {});
// 把當前的傀儡Activity注入到插件中
Method setProxy = localClass.getMethod("setProxy", new Class[] { Activity.class });
setProxy.setAccessible(true);
setProxy.invoke(instance, new Object[] { this });
// 調用插件的onCreate()
Method onCreate = localClass.getDeclaredMethod("onCreate",
new Class[] { Bundle.class });
onCreate.setAccessible(true);
onCreate.invoke(instance, new Object[] { null });
} catch (Exception e) {
e.printStackTrace();
}
}
....
}
- 跳轉
Intent intent = new Intent(MainActivity.this, ProxyActivity.class);
intent.putExtra("Class", "h3c.plugina.AActivity");
startActivity(intent);
講解
宿主中跳轉到ProxyActivity,根據傳入的參數反射創建一個插件的Activity,把插件的Resource注入到自己中,并把自己注入到插件Activity中實現生命周期的同步。
以上Demo只能實現由宿主跳轉到插件的AActivity,由AActivity跳轉到BActivity。主要由于生命周期的同步問題,如果想要完成更多的功能需要按需完善相關方法的補充!
總結
本文簡單的描述了Android插件化開發的主要實現,可以大概的了解到安卓是如何實現插件化開發的,具體實例可參考百度singwhatiwanna的dynamic-load-apk
總的來說這種插件開發難度較大,學習成本高不說,開發時必須遵照特定的編碼規范,要花大量的時間用于宿主和插件的聯調,考慮到大部分應用都會關聯數據庫,使用網絡請求庫,圖片加載庫等,而且很多網絡請求還需要賬戶登錄獲取token,由此可見這種模式下的插件開發支持的情景有限。
接下來我們會看看奇虎360的DroidPlugin和wequick的Small能否解決插件化難用的麻煩。