插件化框架DL源码的简单解析
目前行業(yè)內(nèi)已經(jīng)有較多的插件化實現(xiàn)方案。本文主要對DL(DynamicLoadApk)這一個開源的侵入式插件化方案進行簡單分析。因為Service組件插件化的實現(xiàn)邏輯和Activity大體相似,所以在這里主要用Activity來分析。
基本介紹
基本概念
1、宿主:主App,可以加載插件.
2、插件:插件App.被宿主App加載的App.
3、組件:對于Android來說,指的就是Android中的四大組件(Activity、Service、BroadcastReceiver、ContentProvider)
基本使用
1、PluginActivity
public class MainActivity extends DLBasePluginActivity {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);} }2、HostActivity
public class MainActivity extends AppCompatActivity {private Button btnTest;private TextView tvTip;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);this.btnTest = (Button) findViewById(R.id.btn_test);this.tvTip = (TextView) findViewById(R.id.tv_tip);this.init();}private void init() {String pluginFolder = "/mnt/sdcard/PluginDir";File file = new File(pluginFolder);File[] plugins = file.listFiles();if (plugins == null || plugins.length == 0) {this.tvTip.setVisibility(View.VISIBLE);return;}File plugin = plugins[0];final PluginItem item = new PluginItem();item.pluginPath = plugin.getAbsolutePath();item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {item.launcherActivityName = item.packageInfo.activities[1].name;//這里寫死了要啟動的插件Activity}tvTip.setText("檢測到一個插件:" + item.pluginPath);DLPluginManager.getInstance(this).loadApk(item.pluginPath);this.btnTest.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(getApplicationContext(), "開始調用插件", Toast.LENGTH_SHORT).show();usePlugin(item);}});}private void usePlugin(PluginItem pluginItem) {DLPluginManager pluginManager = DLPluginManager.getInstance(this);pluginManager.startPluginActivity(this, new DLIntent(pluginItem.packageInfo.packageName, pluginItem.launcherActivityName));}public static class PluginItem {public PackageInfo packageInfo;public String pluginPath;public String launcherActivityName;public String launcherServiceName;public PluginItem() {}} }3、AndroidManifest.xml
<activityandroid:name="example.hjd.com.mydl.DLProxyActivity"android:label="@string/app_name"><intent-filter><action android:name="com.hjd.dynamicload.proxy.activity.VIEW" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></activity>效果
關系示意圖
解釋:
1)DLPluginManager調用loadApk(...)加載插件.
2)啟動插件Activity 的時候首先啟動的是ProxyActivity
3)初始化插件Activity和 將插件Activity和代理Activity雙向綁定
插件化中的3個基本問題
資源的加載
宿主App調起插件Apk的時候,需要訪問插件中的資源,但是宿主中的R只能訪問宿主中的資源,訪問不了插件中的資源。這時候可以通過AssetManager加載插件apk的資源,通過新建一個Resource對象來讀取插件中的資源。
/*創(chuàng)建一個AssetManager,加載插件資源*/private AssetManager createAssetManager(String dexPath) {try {AssetManager assetManager = AssetManager.class.newInstance();Method addAssetPathMethod = assetManager.getClass().getMethod("addAssetPath", String.class);/*調用addAssetPath將目標目錄下的所有資源都加載到AssetManager中*/addAssetPathMethod.invoke(assetManager, dexPath);return assetManager;} catch (Exception e) {e.printStackTrace();}return null;}/*創(chuàng)建加載了插件資源的Resources*/private Resources createResources(AssetManager assetManager) {Resources superResource = context.getResources();Resources resources = new Resources(assetManager, superResource.getDisplayMetrics(), superResource.getConfiguration());return resources;}插件Activity生命周期的管理
插件Activity的生命周期管理常見的有以下兩種實現(xiàn)思路:反射式、接口式。
反射式
反射式的主要實現(xiàn)思路是:在代理Activity中通過反射來調用插件Activity的生命周期。
Demo代碼如下所示:
onResume(){Method pluginActivityOnResume = clsObj.getMethod("onResume");pluginActivityOnResume.invoke(...) }接口式
使用反射來解決相應的問題在很多場景下都很常見,但是反射存在一定的性能開銷。接口式的主要思想是把插件Activity中的生命周期或者其他重要節(jié)點的時機抽象成一個接口,由代理Activity去調插件Activity生命周期的方法。
Demo代碼如下:
class ProxyActivity extends Activity{DLPlugin pluginActivity;....onResume(){pluginActivity.onResume();super.onResume();}.... }插件的管理
在DL中,是通過一個HashMap來實現(xiàn)不同插件的存取。
ex:
Map<String,PluginPackage> pluginHolders = new HashMap<>()DLPluginManager源碼簡單解析
插件管理器,負責插件的加載、管理和插件組件的啟動。
loadApk函數(shù)流程圖
startPluginActivity函數(shù)流程圖
源碼
package example.hjd.com.mydl.internal;import android.app.Activity; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; import android.text.TextUtils;import java.io.File; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map;import dalvik.system.DexClassLoader; import example.hjd.com.mydl.DLBasePluginActivity; import example.hjd.com.mydl.DLProxyActivity; import example.hjd.com.mydl.utils.DLConstants; import example.hjd.com.mydl.utils.SoLibManager;/* DynamicLoadApk的核心類主要功能主要有: 1)加載和管理插件.(這里的插件指的是apk) 2)啟動插件組件.(這里的組件包括Activity,Service)*/ public class DLPluginManager {public static int START_RESULT_SUCCESS = 0;public static int START_RESULT_NO_PKG = 1;public static int START_RESULT_NO_CLASS = 2;public static int START_RESULT_TYPE_ERROR = 3;private static DLPluginManager instance;//已經(jīng)加載過的插件的存儲結構private Map<String, DLPluginPackage> packageHolder = new HashMap<>();//插件apk的so庫 拷到 宿主后的存儲目錄private String nativeLibDir = null;//dex解壓之后的存儲路徑private String dexOutputPath;private Context context;//標記來源(可用于區(qū)分是直接啟動插件組件,還是啟動代理組件)private int from;/*用于標記啟動插件service的執(zhí)行結果*/private int result;/*單例*/public static DLPluginManager getInstance(Context context) {if (instance == null) {synchronized (DLPluginManager.class) {if (instance == null) {instance = new DLPluginManager(context);}}}return instance;}public DLPluginManager(Context context) {this.context = context.getApplicationContext();//默認使用data/data/pkgName/pluginlibthis.nativeLibDir = context.getDir("pluginlib", Context.MODE_PRIVATE).getAbsolutePath();}/*加載插件.dexPath:插件的存儲路徑(例如/mnt/sdcard/xxxx.apk)hasSoLib:是否含有so庫.啟動插件之前必須先加載插件.*/public DLPluginPackage loadApk(String dexPath, boolean hasSoLib) {//將插件的來源標記為externalfrom = DLConstants.FROM_EXTERNAL;//獲取插件中的activity的組件和service的組件信息PackageInfo packageInfo = context.getPackageManager().getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);//如果沒有獲取到packageInfoif (packageInfo == null) {//直接返回nullreturn null;}//加載插件DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);//拷貝so庫if (hasSoLib) {copySoLib(dexPath);}//返回加載完成的插件return pluginPackage;}public DLPluginPackage loadApk(String dexPath) {return loadApk(dexPath, true);}/*加載插件及其資源*/public DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {/*判斷一下是否有緩存*/DLPluginPackage pluginPackage = packageHolder.get(packageInfo.packageName);if (pluginPackage != null) {return pluginPackage;}/*加載插件并讀取資源*/DexClassLoader classLoader = createDexClassLoader(dexPath);AssetManager assetManager = createAssetManager(dexPath);Resources resources = createResources(assetManager);pluginPackage = new DLPluginPackage(classLoader, resources, packageInfo);//將插件存入緩存中packageHolder.put(packageInfo.packageName, pluginPackage);return pluginPackage;}/*創(chuàng)建DexClassLoader*/private DexClassLoader createDexClassLoader(String dexPath) {File dexOutputFile = context.getDir("dex", Context.MODE_PRIVATE);dexOutputPath = dexOutputFile.getAbsolutePath();/*dexPath:插件apk的存儲路徑dexOutputPath:dex解壓之后的存儲路徑nativeLibDir:so庫在存儲路徑(在宿主apk中的存儲路徑)*/DexClassLoader dexClassLoader = new DexClassLoader(dexPath, dexOutputPath, nativeLibDir, context.getClassLoader());return dexClassLoader;}/*創(chuàng)建一個AssetManager,加載插件資源*/private AssetManager createAssetManager(String dexPath) {try {AssetManager assetManager = AssetManager.class.newInstance();Method addAssetPathMethod = assetManager.getClass().getMethod("addAssetPath", String.class);/*調用addAssetPath將目標目錄下的所有資源都加載到AssetManager中*/addAssetPathMethod.invoke(assetManager, dexPath);return assetManager;} catch (Exception e) {e.printStackTrace();}return null;}/*創(chuàng)建加載了插件資源的Resources*/private Resources createResources(AssetManager assetManager) {Resources superResource = context.getResources();Resources resources = new Resources(assetManager, superResource.getDisplayMetrics(), superResource.getConfiguration());return resources;}/*根據(jù)packageName來從緩存中獲取已經(jīng)加載過的插件*/public DLPluginPackage getPluginPackage(String packageName) {return packageHolder.get(packageName);}/*將插件中的so庫拷貝到宿主的nativeLibDir中*/private void copySoLib(String dexPath) {SoLibManager.getSoLoader().copyPluginSoLib(context, dexPath, nativeLibDir);}/*啟動插件Activity并返回結果如果是內(nèi)部調用,直接啟動插件Activity如果是外部調用,啟動代理Activity.*/public int startPluginActivityFroResult(Context context, DLIntent intent, int requestCode) {//判斷一下是否是內(nèi)部調用if (from == DLConstants.FROM_INTERNAL) {//直接啟用插件activityintent.setClassName(context, intent.getPluginClsName());performStartActivityForResult(context, intent, requestCode);return START_RESULT_SUCCESS;}//獲取要啟動的插件的packgaeNameString pluginPkgName = intent.getPluginPkgName();/*檢查一下plugin的packageName是否為空*/if (TextUtils.isEmpty(pluginPkgName)) {new IllegalStateException("disallow null packageName");}/*在內(nèi)存緩存中獲取該packageName所對應的package信息(判斷一下該插件是否被加載過)*/DLPluginPackage pluginPackage = packageHolder.get(pluginPkgName);//如果pluginPackage為空if (pluginPackage == null) {//表明該插件沒有被加載過,直接返回NO_PKGreturn START_RESULT_NO_PKG;}String pluginClsFullName = getPluginActivityFullPath(intent, pluginPackage);//加載要啟動的插件cls對象(使用PluginClassLoader來加載要啟動的PluginClass)Class<?> pluginCls = loadPluginClass(pluginPackage.classLoader, pluginClsFullName);//如果要啟動的插件組件的class對象為空if (pluginCls == null) {//返回NO_CLASS錯誤return START_RESULT_NO_CLASS;}//根據(jù)插件Activity找到相應的代理ActivityClass<? extends Activity> proxyActivityClass = getProxyActivityClass(pluginCls);//如果沒找到對應的代理Activityif (proxyActivityClass == null) {//直接返回TYPE_ERRORreturn START_RESULT_TYPE_ERROR;}/*啟動的代理Activity*///在intent中設置plugin的class和packageNameintent.putExtra(DLConstants.EXTRA_CLASS, pluginClsFullName);intent.putExtra(DLConstants.EXTRA_PACKAGE, pluginPkgName);intent.setClass(context, proxyActivityClass);performStartActivityForResult(context, intent, requestCode);return START_RESULT_SUCCESS;}/*根據(jù)實際情況執(zhí)行startActivity操作*/private void performStartActivityForResult(Context context, DLIntent intent, int requestCode) {//如果context是Activity的實例if (context instanceof Activity) {//調用startActivityForResult((Activity) context).startActivityForResult(intent, requestCode);} else {//執(zhí)行startActivity()方法context.startActivity(intent);}}/*獲取要啟動的插件Activity的全路徑名.例如(example.hjd.com.plugin.MainActivity)如果要啟動的插件Activity為空,則默認啟動lauch main activity.*/private String getPluginActivityFullPath(DLIntent intent, DLPluginPackage pluginPackage) {String pluginClsName = intent.getPluginClsName();//判斷一下clsName是否為空pluginClsName = (TextUtils.isEmpty(pluginClsName) ? pluginPackage.defaultActivity : pluginClsName);//判斷一下clsName是否補全,例如.xxxxActivity的情況if (pluginClsName.startsWith(".")) {/*pkgName:example.hjd.com.plugin*/pluginClsName = intent.getPluginPkgName() + pluginClsName;}return pluginClsName;}/*獲取一個插件Activity Cls所對應的代理Activity Cls*/private Class<? extends Activity> getProxyActivityClass(Class<?> pluginActivityCls) {Class<? extends Activity> proxyActivityCls = null;//如果pluginActivityCls是DLBasePluginActivity的子類if (DLBasePluginActivity.class.isAssignableFrom(pluginActivityCls)) {//那么對應的代理Activity就是DLProxyActivityproxyActivityCls = DLProxyActivity.class;}return proxyActivityCls;}/*使用PluginClassLoader來加載PluginClass(要啟動的插件組件)*/private Class<?> loadPluginClass(ClassLoader classLoader, String clsName) {Class pluginCls = null;try {pluginCls = Class.forName(clsName, true, classLoader);} catch (ClassNotFoundException e) {e.printStackTrace();}return pluginCls;}/*啟動插件Activity*/public void startPluginActivity(Context context, DLIntent intent) {startPluginActivityFroResult(context, intent, -1);}}總結
以上是生活随笔為你收集整理的插件化框架DL源码的简单解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: STM 32 窗口看门狗
- 下一篇: 七牛云智能日志管理平台正式发布