1. 程式人生 > >《Android外掛化技術——原理篇》

《Android外掛化技術——原理篇》

| 導語 外掛化技術最早從2012年誕生至今,已經走過了5個年頭。從最初只支援Activity的動態載入發展到可以完全模擬app執行時的沙箱系統,各種開源專案層出不窮,在此挑選了幾個代表性的框架,總結其中的技術原理。由於本人水平有限,外掛化框架又相當複雜,文中若有錯誤或者不準確的地方望高手指點。

內容概要

一、發展歷史

外掛化技術最初源於免安裝執行apk的想法,這個免安裝的apk可以理解為外掛。支援外掛化的app可以在執行時載入和執行外掛,這樣便可以將app中一些不常用的功能模組做成外掛,一方面減小了安裝包的大小,另一方面可以實現app功能的動態擴充套件。想要實現外掛化,主要是解決下面三個問題:

  • 外掛中程式碼的載入和與主工程的互相呼叫

  • 外掛中資源的載入和與主工程的互相訪問

  • 四大元件生命週期的管理

下面是比較出名的幾個開源的外掛化框架,按照出現的時間排序。研究它們的實現原理,可以大致看出外掛化技術的發展,根據實現原理我把這幾個框架劃分成了三代。

第一代:dynamic-load-apk最早使用ProxyActivity這種靜態代理技術,由ProxyActivity去控制外掛中PluginActivity的生命週期。該種方式缺點明顯,外掛中的activity必須繼承PluginActivity,開發時要小心處理context。而DroidPlugin通過Hook系統服務的方式啟動外掛中的Activity,使得開發外掛的過程和開發普通的app沒有什麼區別,但是由於hook過多系統服務,異常複雜且不夠穩定。

第二代:為了同時達到外掛開發的低侵入性(像開發普通app一樣開發外掛)和框架的穩定性,在實現原理上都是趨近於選擇儘量少的hook,並通過在manifest中預埋一些元件實現對四大元件的外掛化。另外各個框架根據其設計思想都做了不同程度的擴充套件,其中Small更是做成了一個跨平臺,元件化的開發框架。

第三代:VirtualApp比較厲害,能夠完全模擬app的執行環境,能夠實現app的免安裝執行和雙開技術。Atlas是阿里今年開源出來的一個結合元件化和熱修復技術的一個app基礎框架,其廣泛的應用與阿里系的各個app,其號稱是一個容器化框架。

下面詳細介紹外掛化框架的原理,分別對應著實現外掛化的三個核心問題。

二、基本原理

2.1 類載入

外部apk中類的載入

Android中常用的有兩種類載入器,DexClassLoader和PathClassLoader,它們都繼承於BaseDexClassLoader。

// DexClassLoaderpublic class DexClassLoader extends BaseDexClassLoader {    public DexClassLoader(String dexPath, String optimizedDirectory,            String libraryPath, ClassLoader parent) {        super(dexPath, new File(optimizedDirectory), libraryPath, parent);    } } // PathClassLoader public class PathClassLoader extends BaseDexClassLoader {    public PathClassLoader(String dexPath, ClassLoader parent) {        super(dexPath, null, null, parent);    }       public PathClassLoader(String dexPath, String libraryPath,            ClassLoader parent) {        super(dexPath, null, libraryPath, parent);    } }

區別在於呼叫父類構造器時,DexClassLoader多傳了一個optimizedDirectory引數,這個目錄必須是內部儲存路徑,用來快取系統建立的Dex檔案。而PathClassLoader該引數為null,只能載入內部儲存目錄的Dex檔案。

所以我們可以用DexClassLoader去載入外部的apk,用法如下

//第一個引數為apk的檔案目錄//第二個引數為內部儲存目錄//第三個為庫檔案的儲存目錄//第四個引數為父載入器new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent)

雙親委託機制

ClassLoader呼叫loadClass方法載入類

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {        //首先從已經載入的類中查詢        Class<?> clazz = findLoadedClass(className);        if (clazz == null) {            ClassNotFoundException suppressed = null;               try {                  //如果沒有載入過,先呼叫父載入器的loadClass                clazz = parent.loadClass(className, false);            } catch (ClassNotFoundException e) {                suppressed = e;            }              if (clazz == null) {                        try {                                  //父載入器都沒有載入,則嘗試載入                    clazz = findClass(className);                } catch (ClassNotFoundException e) {                    e.addSuppressed(suppressed);                           throw e;                }            }        }                return clazz;    }

可以看出ClassLoader載入類時,先檢視自身是否已經載入過該類,如果沒有載入過會首先讓父載入器去載入,如果父載入器無法載入該類時才會呼叫自身的findClass方法載入,該機制很大程度上避免了類的重複載入。

DexClassLoader的DexPathList

DexClassLoader過載了findClass方法,在載入類時會呼叫其內部的DexPathList去載入。DexPathList是在構造DexClassLoader時生成的,其內部包含了DexFile。如下圖所示

DexPathList的loadClass會去遍歷DexFile直到找到需要載入的類

public Class findClass(String name, List<Throwable> suppressed) {        //迴圈dexElements,呼叫DexFile.loadClassBinaryName載入class        for (Element element : dexElements) {            DexFile dex = element.dexFile;            if (dex != null) {                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);                       if (clazz != null) {                         return clazz;                }            }        }        if (dexElementsSuppressedExceptions != null) {            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));        }             return null;    }

有一種熱修復技術正是利用了DexClassLoader的載入機制,將需要替換的類新增到dexElements的前面,這樣系統會使用先找到的修復過的類。

2.2 單DexClassLoader與多DexClassLoader

通過給外掛apk生成相應的DexClassLoader便可以訪問其中的類,這邊又有兩種處理方式,有單DexClassLoader和多DexClassLoader兩種結構。

多DexClassLoader 

對於每個外掛都會生成一個DexClassLoader,當載入該外掛中的類時需要通過對應DexClassLoader載入。這樣不同外掛的類是隔離的,當不同外掛引用了同一個類庫的不同版本時,不會出問題。RePlugin採用的是該方案。

單DexClassLoader

將外掛的DexClassLoader中的pathList合併到主工程的DexClassLoader中。這樣做的好處時,可以在不同的外掛以及主工程間直接互相呼叫類和方法,並且可以將不同外掛的公共模組抽出來放在一個common外掛中直接供其他外掛使用。Small採用的是這種方式。

互相呼叫

外掛和主工程的互相呼叫涉及到以下兩個問題

外掛呼叫主工程

  • 在構造外掛的ClassLoader時會傳入主工程的ClassLoader作為父載入器,所以外掛是可以直接可以通過類名引用主工程的類。

主工程呼叫外掛

  • 若使用多ClassLoader機制,主工程引用外掛中類需要先通過外掛的ClassLoader載入該類再通過反射呼叫其方法。外掛化框架一般會通過統一的入口去管理對各個外掛中類的訪問,並且做一定的限制。

  • 若使用單ClassLoader機制,主工程則可以直接通過類名去訪問外掛中的類。該方式有個弊病,若兩個不同的外掛工程引用了一個庫的不同版本,則程式可能會出錯,所以要通過一些規範去避免該情況發生。

2.3 資源載入

Android系統通過Resource物件載入資源,下面程式碼展示了該物件的生成過程

//建立AssetManager物件 AssetManager assets = new AssetManager(); //將apk路徑新增到AssetManager中  if (assets.addAssetPath(resDir) == 0){                  return null;   } //建立Resource物件 r = new Resources(assets, metrics, getConfiguration(), compInfo);

因此,只要將外掛apk的路徑加入到AssetManager中,便能夠實現對外掛資源的訪問。

具體實現時,由於AssetManager並不是一個public的類,需要通過反射去建立,並且部分Rom對建立的Resource類進行了修改,所以需要考慮不同Rom的相容性。

資源路徑的處理

和程式碼載入相似,外掛和主工程的資源關係也有兩種處理方式

  • 合併式:addAssetPath時加入所有外掛和主工程的路徑

  • 獨立式:各個外掛只新增自己apk路徑

合併式由於AssetManager中加入了所有外掛和主工程的路徑,因此生成的Resource可以同時訪問外掛和主工程的資源。但是由於主工程和各個外掛都是獨立編譯的,生成的資源id會存在相同的情況,在訪問時會產生資源衝突。

獨立式時,各個外掛的資源是互相隔離的,不過如果想要實現資源的共享,必須拿到對應的Resource物件。

Context的處理

通常我們通過Context物件訪問資源,光創建出Resource物件還不夠,因此還需要一些額外的工作。 對資源訪問的不同實現方式也需要不同的額外工作。以VirtualAPK的處理方式為例

第一步:建立Resource

if (Constants.COMBINE_RESOURCES) {    //外掛和主工程資源合併時需要hook住主工程的資源    Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());    ResourcesManager.hookResources(context, resources);        return resources; } else {        //外掛資源獨立,該resource只能訪問外掛自己的資源    Resources hostResources = context.getResources();    AssetManager assetManager = createAssetManager(context, apk);          return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); }

第二步:hook主工程的Resource

對於合併式的資源訪問方式,需要替換主工程的Resource,下面是具體替換的程式碼。

public static void hookResources(Context base, Resources resources) {    try {            ReflectUtil.setField(base.getClass(), base, "mResources", resources);            Object loadedApk = ReflectUtil.getPackageInfo(base);            ReflectUtil.setField(loadedApk.getClass(), loadedApk, "mResources", resources);            Object activityThread = ReflectUtil.getActivityThread(base);            Object resManager = ReflectUtil.getField(activityThread.getClass(), activityThread, "mResourcesManager");            if (Build.VERSION.SDK_INT < 24) {                Map<Object, WeakReference<Resources>> map = (Map<Object, WeakReference<Resources>>) ReflectUtil.getField(resManager.getClass(), resManager, "mActiveResources");                Object key = map.keySet().iterator().next();                map.put(key, new WeakReference<>(resources));            } else {                // still hook Android N Resources, even though it's unnecessary, then nobody will be strange.                Map map = (Map) ReflectUtil.getFieldNoException(resManager.getClass(), resManager, "mResourceImpls");                Object key = map.keySet().iterator().next();                Object resourcesImpl = ReflectUtil.getFieldNoException(Resources.class, resources, "mResourcesImpl");                map.put(key, new WeakReference<>(resourcesImpl));            }    } catch (Exception e) {        e.printStackTrace();

注意下上述程式碼hook了幾個地方,包括以下幾個hook點

  • 替換了主工程context中LoadedApk的mResource物件

  • 將新的Resource新增到主工程ActivityThread的mResourceManager中,並且根據Android版本做了不同處理

第三步:關聯resource和Activity

Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); activity.setIntent(intent); //設定Activity的mResources屬性,Activity中訪問資源時都通過mResources ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());

上述程式碼是在Activity建立時被呼叫的(後面會介紹如何hook Activity的建立過程),在activity被構造出來後,需要替換其中的mResources為外掛的Resource。由於獨立式時主工程的Resource不能訪問外掛的資源,所以如果不做替換,會產生資源訪問錯誤。

做完以上工作後,則可以在外掛的Activity中放心的使用setContentView,inflater等方法載入佈局了。

資源衝突

合併式的資源處理方式,會引入資源衝突,原因在於不同外掛中的資源id可能相同,所以解決方法就是使得不同的外掛資源擁有不同的資源id。

資源id是由8位16進位制數表示,表示為0xPPTTNNNN。PP段用來區分包空間,預設只區分了應用資源和系統資源,TT段為資源型別,NNNN段在同一個APK中從0000遞增。如下表所示

所以思路是修改資源ID的PP段,對於不同的外掛使用不同的PP段,從而區分不同外掛的資源。具體實現方式有兩種

  • 修改aapt原始碼,編譯期修改PP段。

  • 修改resources.arsc檔案,該檔案列出了資源id到具體資源路徑的對映。

具體實現可以分別參考Atlas框架和Small框架。推薦第二種方式,不用入侵原有的編譯流程。

三、四大元件支援

Android開發中有一些特殊的類,是由系統建立的,並且由系統管理生命週期。如常用的四大元件,Activity,Service,BroadcastReceiver和ContentProvider。 僅僅構造出這些類的例項是沒用的,還需要管理元件的生命週期。其中以Activity最為複雜,不同框架採用的方法也不盡相同。下面以Activity為例詳細介紹外掛化如何支援元件生命週期的管理。 大致分為兩種方式:

  • ProxyActivity代理

  • 預埋StubActivity,hook系統啟動Activity的過程

3.1 ProxyActivity代理

ProxyActivity代理的方式最早是由dynamic-load-apk提出的,其思想很簡單,在主工程中放一個ProxyActivy,啟動外掛中的Activity時會先啟動ProxyActivity,在ProxyActivity中建立外掛Activity,並同步生命週期。下圖展示了啟動外掛Activity的過程。

  1. 首先需要通過統一的入口(如圖中的PluginManager)啟動外掛Activity,其內部會將啟動的外掛Activity資訊儲存下來,並將intent替換為啟動ProxyActivity的intent。

  2. ProxyActivity根據外掛的資訊拿到該外掛的ClassLoader和Resource,通過反射建立PluginActivity並呼叫其onCreate方法。

  3. PluginActivty呼叫的setContentView被重寫了,會去呼叫ProxyActivty的setContentView。由於ProxyActivity重寫了getResource返回的是外掛的Resource,所以setContentView能夠訪問到外掛中的資源。同樣findViewById也是呼叫ProxyActivity的。

  4. ProxyActivity中的其他生命週期回撥函式中呼叫相應PluginActivity的生命週期。

代理方式的關鍵總結起來有下面兩點:

  • ProxyActivity中需要重寫getResouces,getAssets,getClassLoader方法返回外掛的相應物件。生命週期函式以及和使用者互動相關函式,如onResume,onStop,onBackPressedon,KeyUponWindow,FocusChanged等需要轉發給外掛。

  • PluginActivity中所有呼叫context的相關的方法,如setContentView,getLayoutInflater,getSystemService等都需要呼叫ProxyActivity的相應方法。

該方式有幾個明顯缺點:

  • 外掛中的Activity必須繼承PluginActivity,開發侵入性強。

  • 如果想支援Activity的singleTask,singleInstance等launchMode時,需要自己管理Activity棧,實現起來很繁瑣。

  • 外掛中需要小心處理Context,容易出錯。

  • 如果想把之前的模組改造成外掛需要很多額外的工作。

該方式雖然能夠很好的實現啟動外掛Activity的目的,但是由於開發式侵入性很強,dynamic-load-apk之後的外掛化方案很少繼續使用該方式,而是通過hook系統啟動Activity的過程,讓啟動外掛中的Activity像啟動主工程的Activity一樣簡單。

3.2 hook方式

在介紹hook方式之前,先用一張圖簡要的介紹下系統是如何啟動一個Activity的。

上圖列出的是啟動一個Activity的主要過程,具體步驟如下:

  1. Activity1呼叫startActivity,實際會呼叫Instrumentation類的execStartActivity方法,Instrumentation是系統用來監控Activity執行的一個類,Activity的整個生命週期都有它的影子。

  2. 通過跨程序的binder呼叫,進入到ActivityManagerService中,其內部會處理Activity棧。之後又通過跨程序呼叫進入到Activity2所在的程序中。

  3. ApplicationThread是一個binder物件,其執行在binder執行緒池中,內部包含一個H類,該類繼承於類Handler。ApplicationThread將啟動Activity2的資訊通過H物件傳送給主執行緒。

  4. 主執行緒拿到Activity2的資訊後,呼叫Instrumentation類的newActivity方法,其內通過ClassLoader建立Activity2例項。

下面介紹如何通過hook的方式啟動外掛中的Activity,需要解決以下兩個問題

  • 外掛中的Activity沒有在AndroidManifest中註冊,如何繞過檢測。

  • 如何構造Activity例項,同步生命週期

解決方法有很多種,以VirtualAPK為例,核心思路如下:

  1. 先在Manifest中預埋StubActivity,啟動時hook上圖第1步,將Intent替換成StubActivity。

  2. hook第10步,通過外掛的ClassLoader反射建立外掛Activity\

  3. 之後Activity的所有生命週期回撥都會通知給外掛Activity

下面具體分析整個過程涉及到的程式碼:

替換系統Instrumentation

VirtualAPK在初始化時會呼叫hookInstrumentationAndHandler,該方法hook了系統的Instrumentaiton類,由上文可知該類和Activity的啟動息息相關。

private void hookInstrumentationAndHandler() {    try {            //獲取Instrumentation物件        Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);                 //構造自定義的VAInstrumentation        final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);                      //設定ActivityThread的mInstrumentation和mCallBack        Object activityThread = ReflectUtil.getActivityThread(this.mContext);        ReflectUtil.setInstrumentation(activityThread, instrumentation);        ReflectUtil.setHandlerCallback(this.mContext, instrumentation);          this.mInstrumentation = instrumentation;    } catch (Exception e) {        e.printStackTrace();    } }

該段程式碼將主執行緒中的Instrumentation物件替換成了自定義的VAInstrumentation類。在啟動和建立外掛activity時,該類都會偷偷做一些手腳。

hook activity啟動過程

VAInstrumentation類重寫了execStartActivity方法,圖 3.2中的第一步。

public ActivityResult execStartActivity(    //省略了無關引數    Intent intent) { //轉換隱式intent    mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);    if (intent.getComponent() != null) {        //替換intent中啟動Activity為StubActivity        this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);    }            //呼叫父類啟動Activity的方法} public void markIntentIfNeeded(Intent intent) {    if (intent.getComponent() == null) {            return;    }    String targetPackageName = intent.getComponent().getPackageName();    String targetClassName = intent.getComponent().getClassName();    // search map and return specific launchmode stub activity    if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {        intent.putExtra(Constants.KEY_IS_PLUGIN, true);        intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);        intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);        dispatchStubActivity(intent);    } }

execStartActivity中會先去處理隱式intent,如果該隱式intent匹配到了外掛中的Activity,將其轉換成顯式。之後通過markIntentIfNeeded將待啟動的的外掛Activity替換成了預先在AndroidManifest中佔坑的StubActivity,並將外掛Activity的資訊儲存到該intent中。其中有個dispatchStubActivity函式,會根據Activity的launchMode選擇具體啟動哪個StubActivity。VirtualAPK為了支援Activity的launchMode在主工程的AndroidManifest中對於每種啟動模式的Activity都預埋了多個坑位。

hook Activity的建立過程

上一步欺騙了系統,讓系統以為自己啟動的是一個正常的Activity。當來到圖 3.2的第10步時,再將外掛的Activity換回來。此時呼叫的是VAInstrumentation類的newActivity方法。

@Overridepublic Activity newActivity(ClassLoader cl, String className, Intent intent){    try {        cl.loadClass(className);    } catch (ClassNotFoundException e) {        //通過LoadedPlugin可以獲取外掛的ClassLoader和Resource        LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);                //獲取外掛的主Activity        String targetClassName = PluginUtil.getTargetActivity(intent);                if (targetClassName != null) {                   //傳入外掛的ClassLoader構造外掛Activity            Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);            activity.setIntent(intent);                    //設定外掛的Resource,從而可以支援外掛中資源的訪問            try {                ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());            } catch (Exception ignored) {                                   // ignored.            }            return activity;        }    }    return mBase.newActivity(cl, className, intent); }

由於AndroidManifest中預埋的StubActivity並沒有具體的實現類,所以此時會發生ClassNotFoundException。之後在處理異常時取出外掛Activity的資訊,通過外掛的ClassLoader反射構造外掛的Activity。

一些額外操作

外掛Activity構造出來後,為了能夠保證其正常執行還要做些額外的工作。VAInstrumentation類在圖3.2中的第11步中也做了一些處理。

@Overridepublic void callActivityOnCreate(Activity activity, Bundle icicle) {    final Intent intent = activity.getIntent();        if (PluginUtil.isIntentFromPlugin(intent)) {        Context base = activity.getBaseContext();                try {            LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);            ReflectUtil.setField(base.getClass(), base, "mResources", plugin.getResources());            ReflectUtil.setField(ContextWrapper.class, activity, "mBase", plugin.getPluginContext());            ReflectUtil.setField(Activity.class, activity, "mApplication", plugin.getApplication());            ReflectUtil.setFieldNoException(ContextThemeWrapper.class, activity, "mBase", plugin.getPluginContext());                // set screenOrientation            ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));               if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {                activity.setRequestedOrientation(activityInfo.screenOrientation);            }        } catch (Exception e) {            e.printStackTrace();        }    }    mBase.callActivityOnCreate(activity, icicle); }

這段程式碼主要是將Activity中的Resource,Context等物件替換成了外掛的相應物件,保證外掛Activity在呼叫涉及到Context的方法時能夠正確執行。

經過上述步驟後,便實現了外掛Activity的啟動,並且該外掛Activity中並不需要什麼額外的處理,和常規的Activity一樣。那問題來了,之後的onResume,onStop等生命週期怎麼辦呢?答案是所有和Activity相關的生命週期函式,系統都會呼叫外掛中的Activity。原因在於AMS在處理Activity時,通過一個token表示具體Activity物件,而這個token正是和啟動Activity時建立的物件對應的,而這個Activity被我們替換成了外掛中的Activity,所以之後AMS的所有呼叫都會傳給外掛中的Activity。

小結

VirtualAPK通過替換了系統的Instrumentation,hook了Activity的啟動和建立,省去了手動管理外掛Activity生命週期的繁瑣,讓外掛Activity像正常的Activity一樣被系統管理,並且外掛Activity在開發時和常規一樣,即能獨立執行又能作為外掛被主工程呼叫。

其他外掛框架在處理Activity時思想大都差不多,無非是這兩種方式之一或者兩者的結合。在hook時,不同的框架可能會選擇不同的hook點。如360的RePlugin框架選擇hook了系統的ClassLoader,即圖3.2中構造Activity2的ClassLoader,在判斷出待啟動的Activity是外掛中的時,會呼叫外掛的ClassLoader構造相應物件。另外RePlugin為了系統穩定性,選擇了儘量少的hook,因此它並沒有選擇hook系統的startActivity方法來替換intent,而是通過重寫Activity的startActivity,因此其外掛Activity是需要繼承一個類似PluginActivity的基類的。不過RePlugin提供了一個Gradle外掛將外掛中的Activity的基類換成了PluginActivity,使用者在開發外掛Activity時也是沒有感知的。

3.3 其他元件

四大元件中Activity的支援是最複雜的,其他元件的實現原理要簡單很多,簡要概括如下

  • Service:Service和Activity的差別在於,Activity的生命週期是由使用者互動決定的,而Service的生命週期是我們通過程式碼主動呼叫的,且Service例項和manifest中註冊的是一一對應的。實現Service外掛化的思路是通過在manifest中預埋StubService,hook系統startService等呼叫替換啟動的Service,之後在StubService中建立外掛Service,並手動管理其生命週期。

  • BroadCastReceiver:解析外掛的manifest,將靜態註冊的廣播轉為動態註冊。

  • ContentProvider:類似於Service的方式,對外掛ContentProvider的所有呼叫都會通過一個在manifest中佔坑的ContentProvider分發。

四、發展方向

通過對外掛化技術的學習,可以看出目前外掛化技術的兩個發展方向

結合元件化技術,成為一箇中大型app的基礎框架

以Small和阿里的Atlas為代表,利用了外掛化技術對複雜工程的模組進行解耦,將app分成主工程和多個外掛模組。主工程在執行期間動態載入相應模組的外掛執行,並負責外掛模組的管理工作。各個外掛可以獨立開發和執行,也可以依賴主工程或者其他外掛。下面是基於Atlas的手淘app的框架圖

其中的獨立bundle即是一個外掛,手淘中的首頁,詳情頁,掃碼,支付等都做成了單獨的bundle,並且首頁bundle還可以依賴於定位bundle。而主工程中則包含了各種基礎功能庫供各個bundle呼叫,並且包含了對bundle的安裝,執行,版本管理,安全校驗等執行期的管理工作。

元件化技術是利用gradle指令碼實現的編譯期的功能解耦,而Atlas是利用外掛化技術實現了一套執行期的功能解耦,所以其也號稱是動態元件化技術。

app沙盒系統,完全模擬app的執行環境

以VirtualAPP為代表,在應用層構建了一個虛擬的app執行環境,實現了免安裝執行apk,應用雙開等黑科技。另外作為應用開發者也需要注意我們的應用可能會執行在一個虛擬的環境下,對於支付,登入等功能要特別注意其安全性。

最後用VirtualAPP的作者Lody的一句話結束本篇文章,相信外掛化技術還會繼續發展壯大下去。

“外掛化技術的成熟程度雖然在最近幾年呈上升趨勢,但是總體而言仍然處於初、中級階段。 
App沙盒技術的出現就是外掛化發展的創新和第一階段的產物。在未來,我相信很多外掛化技 
術會被更多的應用,如果外掛化穩定到了一定的程度,甚至可以顛覆App開發的方式。”

參考 
1.Android外掛化:從入門到放棄(http://www.infoq.com/cn/articles/android-plug-ins-from-entry-to-give-up)

2.Android apk動態載入機制的研究 
(http://blog.csdn.net/singwhatiwanna/article/details/22597587)

3.Android外掛化原理解析系列文章 
(http://weishu.me/2016/01/28/understand-plugin-framework-overview/)

5.VirtualAPK資源載入原理解析 

(https://www.notion.so/VirtualAPK-1fce1a910c424937acde9528d2acd537)

http://itindex.net/detail/57916-android-%E6%8F%92%E4%BB%B6-%E6%8A%80%E6%9C%AF