dynamic-load-apk外掛程式原理整理,dynamicloadapk
因為當前項目功能越來越多,編譯速度越來越慢(公司電腦配置也挺差的...),並且方法數已超出65535的限制了,雖然通過multidex暫時解決了,但是這並不是一個好的解決方式。所以通過外掛程式來加快編譯速度以及解決方案數的限制,算是一個越來越重要的任務了,工作中還有很多新需求,所以趁放假的2天研究了下現在比較流行的外掛程式架構dynamic-load-apk,並整理了下。
架構github地址:https://github.com/singwhatiwanna/dynamic-load-apk
lib module的svn地址:https://github.com/singwhatiwanna/dynamic-load-apk/trunk/DynamicLoadApk/lib
一、載入apk總流程:
//外掛程式檔案File plugin = new File(apkPath);PluginItem item = new PluginItem();//外掛程式檔案路徑item.pluginPath = plugin.getAbsolutePath();//PackageInfo = PackageManager.getPackageArchiveInfoitem.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);//launcherActivityif (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) { item.launcherActivityName = item.packageInfo.activities[0].name;}//launcherServiceif (item.packageInfo.services != null && item.packageInfo.services.length > 0) { item.launcherServiceName = item.packageInfo.services[0].name;}//載入apk資訊DLPluginManager.getInstance(this).loadApk(item.pluginPath);
二、loadApk資訊過程:
1、createDexClassLoader:
private DexClassLoader createDexClassLoader(String dexPath) { dexOutputPath = mContext.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath(); DexClassLoader loader = new DexClassLoader(dexPath, dexOutputPath, //getDir("dex", Context.MODE_PRIVATE) mNativeLibDir, //optimizedDirectory=getDir("pluginlib", Context.MODE_PRIVATE) mContext.getClassLoader()); //host.Appliceation.getClassLoader() return loader;}
2、createAssetManager:
private AssetManager createAssetManager(String dexPath) { try { AssetManager assetManager = AssetManager.class.newInstance(); //通過反射調用addAssetPath方法,將apk資源載入到AssetManager Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); addAssetPath.invoke(assetManager, dexPath); return assetManager; } catch (Exception e) { e.printStackTrace(); return null; }}
後面會重寫DLProxyActivity的getAssets()方法,返回此處產生的AssetManager,從而實現從外掛程式apk載入資源:
@Overridepublic AssetManager getAssets() { return impl.getAssets() == null ? super.getAssets() : impl.getAssets();}
3、createResources:
private Resources createResources(AssetManager assetManager) { //通過剛建立的assetManager以及宿主程式的Resources建立Plugin的Resources Resources superRes = mContext.getResources(); Resources resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration()); return resources;}
後面會重寫DLProxyActivity的getResources()方法,返回此處產生的Resources,從而實現從外掛程式apk載入資源:
@Overridepublic Resources getResources() { return impl.getResources() == null ? super.getResources() : impl.getResources();}
4、建立pluginPackage並通過外掛程式的packageName儲存外掛程式資訊:
pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
mPackagesHolder.put(packageInfo.packageName, pluginPackage);
5、copySoLib(拷貝so檔案到應用的pluginlib目錄下):
SoLibManager.getSoLoader().copyPluginSoLib(mContext, dexPath, mNativeLibDir);
三、調用外掛程式:
1、要向外掛程式Intent傳遞可序列化對象,必須通過DLIntent,設定Bundle的ClassLoader:
@Overridepublic Intent putExtra(String name, Parcelable value) { setupExtraClassLoader(value); return super.putExtra(name, value);}@Overridepublic Intent putExtra(String name, Serializable value) { setupExtraClassLoader(value); return super.putExtra(name, value);}private void setupExtraClassLoader(Object value) { ClassLoader pluginLoader = value.getClass().getClassLoader(); DLConfigs.sPluginClassloader = pluginLoader; setExtrasClassLoader(pluginLoader); //設定Bundle的ClassLoader}
2、startPluginActivity:
外掛程式內部的activity之間相互調用,需要使用此方法。
public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) { if (mFrom == DLConstants.FROM_INTERNAL) { dlIntent.setClassName(context, dlIntent.getPluginClass()); performStartActivityForResult(context, dlIntent, requestCode); return DLPluginManager.START_RESULT_SUCCESS; } String packageName = dlIntent.getPluginPackage(); //驗證intent的包名 if (TextUtils.isEmpty(packageName)) { throw new NullPointerException("disallow null packageName."); } //檢測外掛程式是否載入 DLPluginPackage pluginPackage = mPackagesHolder.get(packageName); if (pluginPackage == null) { return START_RESULT_NO_PKG; } //要調用的外掛程式Activity的class完整路徑 final String className = getPluginActivityFullPath(dlIntent, pluginPackage); //Class.forName Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className); if (clazz == null) { return START_RESULT_NO_CLASS; } //擷取代理Activity的class,DLProxyActivity/DLProxyFragmentActivity Class<? extends Activity> proxyActivityClass = getProxyActivityClass(clazz); if (proxyActivityClass == null) { return START_RESULT_TYPE_ERROR; } //put extra data dlIntent.putExtra(DLConstants.EXTRA_CLASS, className); dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName); dlIntent.setClass(mContext, proxyActivityClass); //通過context啟動宿主Activity performStartActivityForResult(context, dlIntent, requestCode); return START_RESULT_SUCCESS;}
四、Activity生命週期的管理:
外掛程式apk中的activity其實就是一個普通的對象,不是真正意義上的activity(沒有在宿主程式中註冊且沒有完全初始化),不具有activity的性質,因為系統啟動activity是要做很多初始化工作的,而我們在應用程式層通過反射去啟動activity是很難完成系統所做的初始化工作的,所以activity的大部分特性都無法使用包括activity的生命週期管理,這就需要我們自己去管理。
DL採用了介面機制,將activity的大部分生命週期方法提取出來作為一個介面(DLPlugin),然後通過代理activity(DLProxyActivity)去調用外掛程式activity實現的生命週期方法,這樣就完成了外掛程式activity的生命週期管理,並且沒有採用反射,當我們想增加一個新的生命週期方法的時候,只需要在介面中聲明一下同時在代理activity中實現一下即可。
public interface DLPlugin { public void onCreate(Bundle savedInstanceState); public void onStart(); public void onRestart(); public void onActivityResult(int requestCode, int resultCode, Intent data); public void onResume(); public void onPause(); public void onStop(); public void onDestroy(); public void attach(Activity proxyActivity, DLPluginPackage pluginPackage); public void onSaveInstanceState(Bundle outState); public void onNewIntent(Intent intent); public void onRestoreInstanceState(Bundle savedInstanceState); public boolean onTouchEvent(MotionEvent event); public boolean onKeyUp(int keyCode, KeyEvent event); public void onWindowAttributesChanged(LayoutParams params); public void onWindowFocusChanged(boolean hasFocus); public void onBackPressed(); public boolean onCreateOptionsMenu(Menu menu); public boolean onOptionsItemSelected(MenuItem item);}
DLBasePluginActivity的部分實現:
public class DLBasePluginActivity extends Activity implements DLPlugin { /** * 代理activity,可以當作Context來使用,會根據需要來決定是否指向this */ protected Activity mProxyActivity; /** * 等同於mProxyActivity,可以當作Context來使用,會根據需要來決定是否指向this<br/> * 替代this來使用(應為this指向的是外掛程式中的Activity,已經不是常規意義上的activity,所以this是沒有意義的) * 如果是DLPlugin中已經覆蓋的Activity的方法,就不需使用that了,直接調用this即可 */ protected Activity that; protected DLPluginManager mPluginManager; protected DLPluginPackage mPluginPackage; protected int mFrom = DLConstants.FROM_INTERNAL; @Override public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) { mProxyActivity = (Activity) proxyActivity; that = mProxyActivity; mPluginPackage = pluginPackage; } @Override public void onCreate(Bundle savedInstanceState) { if (savedInstanceState != null) { mFrom = savedInstanceState.getInt(DLConstants.FROM, DLConstants.FROM_INTERNAL); } if (mFrom == DLConstants.FROM_INTERNAL) { super.onCreate(savedInstanceState); mProxyActivity = this; that = mProxyActivity; } mPluginManager = DLPluginManager.getInstance(that); } @Override public void setContentView(View view) { if (mFrom == DLConstants.FROM_INTERNAL) { super.setContentView(view); } else { mProxyActivity.setContentView(view); } } ......}
在代理類DLProxyActivity中的實現:
public class DLProxyActivity extends Activity implements DLAttachable { protected DLPlugin mRemoteActivity; private DLProxyImpl impl = new DLProxyImpl(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); impl.onCreate(getIntent()); } @Override public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) { mRemoteActivity = remoteActivity; } @Override public AssetManager getAssets() { return impl.getAssets() == null ? super.getAssets() : impl.getAssets(); } @Override public Resources getResources() { return impl.getResources() == null ? super.getResources() : impl.getResources(); } @Override public Theme getTheme() { return impl.getTheme() == null ? super.getTheme() : impl.getTheme(); } @Override public ClassLoader getClassLoader() { return impl.getClassLoader(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { mRemoteActivity.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data); } @Override protected void onStart() { mRemoteActivity.onStart(); super.onStart(); } ......}
總結:
外掛程式主要的2個問題就是資源載入以及Activity生命週期的管理。
資源載入:
通過反射調用AssetManager的addAssetPath方法,我們可以將一個外掛程式apk中的資源載入到AssetManager中,然後再通過AssetManager來建立一個新的Resources對象,然後就可以通過這個Resources對象來訪問外掛程式apk中的資源了。
Activity生命週期管理:
採用介面機制,將activity的大部分生命週期方法提取出來作為一個介面(DLPlugin),然後通過代理activity(DLProxyActivity)去調用外掛程式activity實現的生命週期方法,這樣就完成了外掛程式activity的生命週期管理。
另外,一個需要注意的地方:
外掛程式項目引用 android-support-v4.jar、lib.jar等libs,產生apk時不能將這些打包到apk,只在編譯時間引用,只有host項目裡才編譯並打包,保證host以及外掛程式中的代碼只有一份。
在studio裡面使用provided而非compile:
dependencies {
provided files('provide-jars/android-support-v4.jar')
provided files('provide-jars/lib.jar')
}