android-plugmgr是一個Android插件加載框架,它最大的特色就是對插件不須要進行任何約束。關於這個類庫的介紹見做者博客,市面上也有一些插件加載框架,可是感受沒有這個好。在這篇文章中,咱們將不只止於原理,對源代碼的具體實現進行分析。文章中涉及的代碼可從https://github.com/kissazi2/AndroidDemo/tree/master/PlugLoadDemo下載,基於Android Studio 1.2編譯。java
在正式開始分析源代碼以前,咱們首先須要有一些動態加載Apk的基礎知識。android
《 Android apk動態加載機制的研究(二):資源加載和activity生命週期管理》.github
這篇文章解決資源訪問的思路是,經過構造Resource對象及其依賴的AssetManager,將資源重定向到插件Apk,而後Override插件中getResource方法。解決生命週期函數沒法調用的方法是在ProxyActivity的每個生命週期函數調用的時候同時調用插件Activity的對應函數。安全
從這兩篇文章中,咱們大體瞭解了插件動態加載的流程。咱們將要分析的android_plugmgr的思路與上面文章中的思路徹底不一樣,它經過將繼承關係反轉,也就是說宿主Activity中的ProxyActivity去繼承插件中的Activity。在startActivity(...)啓動這個Activity以前,經過本身實現的ClassLoader去加載這個臨時被構造出來的Activity。這個框架不像上面文章介紹的插件加載框架有那麼多的限制。app
這個框架只須要在以下面截圖中的指定文件夾(通常是sdcard中的Download文件夾)中放入須要動態加載的APK文件,選擇對應的圖標就能夠實現動態加載了.
框架
一、加載插件信息ide
@Override public void onClick(View v) { final String dirText = pluginDirTxt.getText().toString().trim(); //省略沒必要要的代碼 new Thread(new Runnable() { @Override public void run() { Collection<PlugInfo> plugs = plugMgr .loadPlugin(new File(dirText)); setPlugins(plugs); } }).start(); } private void setPlugins(final Collection<PlugInfo> plugs) { if (plugs == null || plugs.isEmpty()) { return; } final ListAdapter adapter = new PlugListViewAdapter(this, plugs); runOnUiThread(new Runnable() { public void run() { pluglistView.setAdapter(adapter); } }); }
從上面咱們看到插件的信息經過文件地址dirText傳入,而後由 plugMgr.loadPlugin(new File(dirText))進行apk信息的加載,最終將信息保存在plugInfo的集合。plugMgr是一個PluginManager類型的對象,它定義在android_plugmgr類庫中。PlugListViewAdapter(繼承自BaseAdapter)就是一個帶有PlugInfo的Adatpter而已。函數
二、點擊每一項,加載對應的插件工具
private void plugItemClick(int position) { PlugInfo plug = (PlugInfo) pluglistView.getItemAtPosition(position); plugMgr.startMainActivity(this, plug.getPackageName()); }
在瞭解基本的使用以後,咱們看看android_plugmgr中包含什麼具體的類。截圖中的類所在的包與原做者的類庫中的包略有不一樣,由於要在Android Studio中編譯作的小修改。下面羅列出這些類,只爲給你們一個模糊的關於這個類庫結構,能夠嘗試着理清他們之間的關係,看不懂就跳過。
從前面的分析中,咱們知道要加載插件apk中的信息,咱們須要加載插件apk中的組件(Activity、service、brocastreceiver)、用DexClassLoader加載Dex文件中信息、修改Resource類的資源指向。要完成以上操做,咱們須要讀取插件apk包中的AndroidManifest信息,取出一些必要的內容(不僅僅是從AndroidManifest.xml,還有插件apk自己)。
以前咱們忽略了PluginManager的初始化。它的初始化是在宿主APK的MainActivity中進行的。初始化經過PluginManager.getInstance()→PluginManager.init()進行調用。getInstance()所作的就是獲取ApplicationContext而後傳給init().
private void init(Context ctx) { Log.i(tag, "init()..."); context = ctx; //plugsout是讓DexClassLoader存放它Dex文件的地方,能夠先放過 File optimizedDexPath = ctx.getDir("plugsout", Context.MODE_PRIVATE); if (!optimizedDexPath.exists()) { optimizedDexPath.mkdirs(); } dexOutputPath = optimizedDexPath.getAbsolutePath(); dexInternalStoragePath = context .getDir("plugins", Context.MODE_PRIVATE); //建立plugins用來存放插件apk,以及插件apk中的lib包 dexInternalStoragePath.mkdirs(); // 改變classLoader,以便根據是插件apk、仍是宿主apk中的類進行特殊處理 try { Object mPackageInfo = ReflectionUtils.getFieldValue(ctx, "mBase.mPackageInfo", true); frameworkClassLoader = new FrameworkClassLoader( ctx.getClassLoader()); // set Application's classLoader to FrameworkClassLoader ReflectionUtils.setFieldValue(mPackageInfo, "mClassLoader", frameworkClassLoader, true); } catch (Exception e) { e.printStackTrace(); } hasInit = true; }
咱們來模擬計算機一步步地執行看看。從"先用起來"那一節,咱們知道加載類庫會調用androidx.pluginmgr.PluginManager.loadPlugin(...)方法.在這一步裏面主要對後面用的到的信息進行加載.從代碼裏面無論單個仍是多個插件都進入PluginManager.buildPlugInfo(...)。
public Collection<PlugInfo> loadPlugin(final File pluginSrcDirFile) throws Exception { checkInit(); if (pluginSrcDirFile == null || !pluginSrcDirFile.exists()) { Log.e(tag, "invalidate plugin file or Directory :" + pluginSrcDirFile); return null; } if (pluginSrcDirFile.isFile()) { // 若是是文件則嘗試加載單個插件,暫不檢查文件類型,除apk外,之後可能支持加載其餘類型文件,如jar PlugInfo one = loadPluginWithId(pluginSrcDirFile, null, null); return Collections.singletonList(one); } // clear all first synchronized (this) { pluginPkgToInfoMap.clear(); pluginIdToInfoMap.clear(); } File[] pluginApks = pluginSrcDirFile.listFiles(this); if (pluginApks == null || pluginApks.length < 1) { throw new FileNotFoundException("could not find plugins in:" + pluginSrcDirFile); } for (File pluginApk : pluginApks) { PlugInfo plugInfo = buildPlugInfo(pluginApk, null, null); if (plugInfo != null) { savePluginToMap(plugInfo); } } return pluginIdToInfoMap.values(); } /** * 初始化插件apk的必要信息 * @param pluginApk 要加載的APK路徑 * @param pluginId 插件apk的名字(通常是xxx.apk),nullable * @param targetFileName 插件apk的名稱,nullable * @return * @throws Exception */ private PlugInfo buildPlugInfo(File pluginApk, String pluginId, String targetFileName) throws Exception { PlugInfo info = new PlugInfo(); info.setId(pluginId == null ? pluginApk.getName() : pluginId); File privateFile = new File(dexInternalStoragePath, targetFileName == null ? pluginApk.getName() : targetFileName); info.setFilePath(privateFile.getAbsolutePath()); //1.把插件apk複製到app_plugins目錄下,防止由於意外狀況被破壞 if (!pluginApk.getAbsolutePath().equals(privateFile.getAbsolutePath())) { copyApkToPrivatePath(pluginApk, privateFile); } //2.讀取AndroidManifest中的信息 String dexPath = privateFile.getAbsolutePath(); PluginManifestUtil.setManifestInfo(context, dexPath, info); //3.設置插件加載器 PluginClassLoader loader = new PluginClassLoader(dexPath, dexOutputPath, frameworkClassLoader, info); info.setClassLoader(loader); //4.重定向Resource對象的資源指向 try { AssetManager am = (AssetManager) AssetManager.class.newInstance(); am.getClass().getMethod("addAssetPath", String.class) .invoke(am, dexPath); info.setAssetManager(am); Resources ctxres = context.getResources(); Resources res = new Resources(am, ctxres.getDisplayMetrics(), ctxres.getConfiguration()); info.setResources(res); } catch (Exception e) { e.printStackTrace(); } //5.初始化插件的application,調用它的onCreate() if (actFrom != null) { initPluginApplication(info, actFrom, true); } // createPluginActivityProxyDexes(info); Log.i(tag, "buildPlugInfo: " + info); return info; }
這段函數的思路仍是很清晰的,其中咱們最爲感興趣的是,加載插件apk究竟須要AndroidManifest什麼信息?能夠預想到Android四大組件確定是必須的,權限不是必須的。插件apk的所擁有的權限只能宿主apk中AndroidManifest中的,這是Android的安全機制所保證的。進入PluginManifestUtil.setManifestInfo(...)。
/** * 設置跟Manifest有關的信息 * @param context MainActivity的實例 * @param apkPath 插件apk的路徑 * @param info 設置了插件apk的Id、FilePath的PlugInfo * @throws XmlPullParserException * @throws IOException */ static void setManifestInfo(Context context, String apkPath, PlugInfo info) throws XmlPullParserException, IOException { //1.讀取壓縮包裏面的信息,將AndroidManifest的值讀取出來 ZipFile zipFile = new ZipFile(new File(apkPath), ZipFile.OPEN_READ); ZipEntry manifestXmlEntry = zipFile.getEntry(XmlManifestReader.DEFAULT_XML); String manifestXML = XmlManifestReader.getManifestXMLFromAPK(zipFile, manifestXmlEntry); PackageInfo pkgInfo = context.getPackageManager() .getPackageArchiveInfo( apkPath, PackageManager.GET_ACTIVITIES | PackageManager.GET_RECEIVERS// | PackageManager.GET_PROVIDERS// | PackageManager.GET_META_DATA// | PackageManager.GET_SHARED_LIBRARY_FILES// ); //2.將插件apk中libs目錄下的全部文件複製到app_plugins/插件Id-dir-lib目錄下 File libdir = ActivityOverider.getPluginLibDir(info.getId()); try { if(extractLibFile(zipFile, libdir)){ pkgInfo.applicationInfo.nativeLibraryDir=libdir.getAbsolutePath(); } } finally { zipFile.close(); } //3.設置插件中activity、receiver、service、application等信息 setAttrs(info, manifestXML); }
從這個函數中,咱們就完成了對插件apk所須要的信息的加載,須要注意的是,android-plugmgr這版的源代碼暫時只支持Activity,Service、Receiver都仍是不支持的。
這部分是這個類庫的核心。這個類庫之因此可以無約束地調用插件apk中的Activity,依賴的就是依賴倒置,讓宿主apk中的ProxyActivity去繼承插件apk中特定的Activity。它的作法是利用dexmaker在運行時動態生成代碼實現繼承,而後將這個子類輸出成Dex文件放到/data/data/androidx.plmgrdemo/app_plugsout。最後它就利用DexClassLoader進行加載。
從點擊主界面點擊每個插件開始,
private void plugItemClick(int position) { PlugInfo plug = (PlugInfo) pluglistView.getItemAtPosition(position); plugMgr.startMainActivity(this, plug.getPackageName()); } public boolean startMainActivity(Context context, String pkgOrId) { Log.d(tag, "startMainActivity by:" + pkgOrId); //1.根據包名或Apk命,獲取plugInfo PlugInfo plug = preparePlugForStartActivity(context, pkgOrId); if (frameworkClassLoader == null) { Log.e(tag, "startMainActivity: frameworkClassLoader == null!"); return false; } if (plug.getMainActivity() == null) { Log.e(tag, "startMainActivity: plug.getMainActivity() == null!"); return false; } if (plug.getMainActivity().activityInfo == null) { Log.e(tag, "startMainActivity: plug.getMainActivity().activityInfo == null!"); return false; } //2.這裏很關鍵,三個參數分別是插件的包名、將要加載的插件Activity的名稱,這是爲了後面能正確地根據 //Activity或其餘普通類是宿主的仍是插件的,進行分別處理 String className = frameworkClassLoader.newActivityClassName( plug.getId(), plug.getMainActivity().activityInfo.name); //還記得以前貼的代碼中(3.1中buildPlugInfo函數),咱們提到的application中的classLoader被替換 //成FrameworkClassloader context.startActivity(new Intent().setComponent(new ComponentName( context, className))); return true; }
上面代碼就是插件加載Activity的整個過程,然而咱們漏掉了插件Activity的加載流程,以及特定的Activity怎麼被ProxyActivity繼承的過程。接下來分析這個類庫中自定義的FrameworkClassloader加載特定類的過程。在上面代碼context.startActivity(...)中會對將要start的Activity進行加載,也就是調用ClassLoader進行加載,因爲咱們已經將宿主apk中application的默認Classloader替換成了FrameworkClassloader,因此在context.startActivity(...)的過程當中會調用FrameworkClassLoader.loadClass(...)。上一個流程中調用了FrameworkClassloader.newActivityClassName(...),主要爲如今插件apk中Activity的加載埋下伏筆。
/** * 1.用actName記錄真正要加載的插件apk中的Activity * 2.返回ActivityOverider.targetClassName來指示後面的處理流程,要加載的是插件Apk中的Activity * @param plugId * @param actName * @return */ String newActivityClassName(String plugId, String actName) { this.plugId = plugId; this.actName = actName; return ActivityOverider.targetClassName; } protected Class<?> loadClass(String className, boolean resolv) throws ClassNotFoundException { Log.i("cl", "loadClass: " + className); if (plugId != null) { String pluginId = plugId; PlugInfo plugin = PluginManager.getInstance().getPluginById( pluginId); Log.i("cl", "plugin = " + plugin); if (plugin != null) { try { //這裏判斷要加載的Activity是否插件apk中的Activity if (className.equals(ActivityOverider.targetClassName)) { // Thread.dumpStack(); String actClassName = actName; //這裏的Classloader是PluginClassLoader return plugin.getClassLoader().loadActivityClass( actClassName); } else { return plugin.getClassLoader().loadClass(className); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } return super.loadClass(className, resolv); } }
看來frameworkClassloader也就是對類的加載進行預處理,真正的處理還在PluginClassLoader.loadActivityClass(...)。
/** * 加載Activity類 * @param actClassName 將要加載的特定Activity(帶包名) * @return * @throws ClassNotFoundException */ Class<?> loadActivityClass(final String actClassName) throws ClassNotFoundException { Log.d(tag, "loadActivityClass: " + actClassName); // 在類加載以前檢查建立代理的Activity dex文件,以避免調用者忘記生成此文件 File dexSavePath = ActivityOverider.createProxyDex(thisPlugin, actClassName, true); ClassLoader actLoader = proxyActivityLoaderMap.get(actClassName); if (actLoader == null) { actLoader = new DexClassLoader(dexSavePath.getAbsolutePath(), optimizedDirectory,libraryPath, this){ @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Log.d("PlugActClassLoader("+ actClassName+")", "loadClass: " + name); if (ActivityOverider.targetClassName.equals(name)) { Class<?> c = findLoadedClass(name); if (c == null) { Log.d("PlugActClassLoader("+ actClassName+")", "findClass"); c = findClass(name); } if (resolve) { resolveClass(c); } return c; } return super.loadClass(name, resolve); } }; proxyActivityLoaderMap.put(actClassName, actLoader); } return actLoader.loadClass(ActivityOverider.targetClassName); }
這段函數裏面重點就是ActivityOverider.createProxyDex(...)函數。另外,關於爲何 new DexClassLoader(...)的時候要將this(也就是PluginClassLoader)做爲parent傳入,參考Java ClassLoader基礎及加載不一樣依賴 Jar 中的公共類。
static File createProxyDex(PlugInfo plugin, String activity, boolean lazy) { //這裏savePath = /data/data/androidx.plmgrdemo/app_plugins/app名稱.apk-dir/activities/app包名.XXXXActivity.dex File savePath = getPorxyActivityDexPath(plugin.getId(), activity); createProxyDex(plugin, activity, savePath, lazy); return savePath; } private static void createProxyDex(PlugInfo plugin, String activity, File saveDir, boolean lazy) { // Log.d(tag + ":createProxyDex", "plugin=" + plugin + "\n, activity=" // + activity); if (lazy && saveDir.exists()) { // Log.d(tag, "dex alreay exists: " + saveDir); // 已經存在就不建立了,直接返回 return; } // Log.d(tag, "actName=" + actName + ", saveDir=" + saveDir); try { String pkgName = plugin.getPackageName(); //這裏是ProxyActivity繼承插件中特定Activity的具體處理 ActivityClassGenerator.createActivityDex(activity, targetClassName, saveDir, plugin.getId(), pkgName); } catch (Throwable e) { Log.e(tag, Log.getStackTraceString(e)); } } public static void createActivityDex(String superClassName, String targetClassName, File saveTo, String pluginId, String pkgName) throws IOException { byte[] dex = createActivityDex(superClassName, targetClassName, pluginId, pkgName); if (saveTo.getName().endsWith(".dex")) { FileUtil.writeToFile(dex, saveTo); } else { JarOutputStream jarOut = new JarOutputStream(new FileOutputStream( saveTo)); jarOut.putNextEntry(new JarEntry(DexFormat.DEX_IN_JAR_NAME)); jarOut.write(dex); jarOut.closeEntry(); jarOut.close(); } }
能夠看出ActivityClassGenerator.createActivityDex(...)是重點,它利用dexmaker在運行時動態生成代碼讓ProxyActivity繼承插件apk中特定的Activity,固然這個Activity中的生命週期函數確定要進行一些修改。在看接下來的代碼前,你可能須要先了解dexmarker的語法.
/** * 生成Activity的Dex文件 * @param superClassName * @param targetClassName * @param pluginId * @param pkgName * @return */ public static <S, D extends S> byte[] createActivityDex( final String superClassName, final String targetClassName, final String pluginId, String pkgName) { DexMaker dexMaker = new DexMaker(); TypeId<D> generatedType = TypeId.get('L' + targetClassName.replace('.', '/') + ';'); TypeId<S> superType = TypeId .get('L' + superClassName.replace('.', '/') + ';'); // 聲明類 dexMaker.declare(generatedType, "", PUBLIC | FINAL, superType); // 定義字段 //private static final String _pluginId = @param{pluginId}; // private AssetManager asm; // private Resources res; declareFields(dexMaker, generatedType, superType, pluginId,pkgName); // 聲明 默認構造方法 declare_constructor(dexMaker, generatedType, superType); // 聲明 方法:onCreate declareMethod_onCreate(dexMaker, generatedType, superType); // 聲明 方法:public AssetManager getAssets() declareMethod_getAssets(dexMaker, generatedType, superType); // 聲明 方法:public Resources getResources() declareMethod_getResources(dexMaker, generatedType, superType); /* * 聲明 方法:startActivityForResult(Intent intent, int requestCode, Bundle * options) */ declareMethod_startActivityForResult(dexMaker, generatedType,superType); // 聲明 方法:public void onBackPressed() declareMethod_onBackPressed(dexMaker, generatedType, superType); declareMethod_startService(dexMaker, generatedType, superType); declareMethod_bindService(dexMaker, generatedType, superType); declareMethod_unbindService(dexMaker, generatedType, superType); declareMethod_stopService(dexMaker, generatedType, superType); // Create life Cycle methods declareLifeCyleMethod(dexMaker, generatedType, superType, "onResume"); declareLifeCyleMethod(dexMaker, generatedType, superType, "onStart"); declareLifeCyleMethod(dexMaker, generatedType, superType, "onRestart"); declareLifeCyleMethod(dexMaker, generatedType, superType, "onPause"); declareLifeCyleMethod(dexMaker, generatedType, superType, "onStop"); declareLifeCyleMethod(dexMaker, generatedType, superType, "onDestroy"); declareMethod_attachBaseContext(dexMaker, generatedType, superType); declareMethod_getComponentName(dexMaker, generatedType, superType, superClassName); declareMethod_getPackageName(dexMaker, generatedType, pkgName); declareMethod_getIntent(dexMaker, generatedType, superType); declareMethod_setTheme(dexMaker, generatedType, superType); // Create the dex Content byte[] dex = dexMaker.generate(); return dex; }
傳入這個函數的值(以Android-PullToRefresh說明)
別看這段函數這麼長,它也就作了下面的事情(你能夠看一下原做者博客看這段生成的代碼到底是怎麼樣的):
咱們以declareMethod_onCreate(dexMaker, generatedType, superType)進行分析
private static <S, D extends S> void declareMethod_onCreate( DexMaker dexMaker, TypeId<D> generatedType, TypeId<S> superType) { // // 聲明 方法:onCreate TypeId<Bundle> Bundle = TypeId.get(Bundle.class); TypeId<ActivityOverider> ActivityOverider = TypeId .get(ActivityOverider.class); MethodId<D, Void> method = generatedType.getMethod(TypeId.VOID, "onCreate", Bundle); Code methodCode = dexMaker.declare(method, PROTECTED); // locals Local<D> localThis = methodCode.getThis(generatedType); Local<Bundle> lcoalBundle = methodCode.getParameter(0, Bundle); Local<Boolean> lcoalCreated = methodCode.newLocal(TypeId.BOOLEAN); Local<String> pluginId = get_pluginId(generatedType, methodCode); // this.mOnCreated = true; FieldId<D, Boolean> beforeOnCreate = generatedType.getField(TypeId.BOOLEAN, FIELD_mOnCreated); methodCode.loadConstant(lcoalCreated, true); methodCode.iput(beforeOnCreate, localThis, lcoalCreated); //ActivityOverider.callback_onCreate(str, this); MethodId<ActivityOverider, Void> method_call_onCreate = ActivityOverider .getMethod(TypeId.VOID, "callback_onCreate", TypeId.STRING, TypeId.get(Activity.class)); methodCode .invokeStatic(method_call_onCreate, null, pluginId, localThis); // super.onCreate() MethodId<S, Void> superMethod = superType.getMethod(TypeId.VOID, "onCreate", Bundle); methodCode.invokeSuper(superMethod, null, localThis, lcoalBundle); methodCode.returnVoid(); }
這段函數所作的事情能夠用下面的java代碼表示
protected void onCreate(Bundle paramBundle) { String str = _pluginId; this.mOnCreated = true; ActivityOverider.callback_onCreate(str, this); super.onCreate(paramBundle); }
其餘函數的聲明與此類似,這個類庫關於生命週期函數的處理就不如表面看的如此簡單,它在實現原有Activity功能的時候,同時提供了用ActivityOverride的回調函數。細細地考慮一下,這點給了咱們一個拓展的接口。另外若是什麼東西都用dexmark寫,那就太煩了。
咱們的分析就到這裏。這個類庫目前也就支持Activity、receiver(最新的實驗分支支持)而已,對於Service仍是不支持的。