Android插件化技術——原理篇

《Android插件化技術——原理篇》

轉載:https://mp.weixin.qq.com/s/Uwr6Rimc7Gpnq4wMFZSAag?utm_source=androidweekly&utm_medium=websiteandroid

| 導語 插件化技術最先從2012年誕生至今,已經走過了5個年頭。從最初只支持Activity的動態加載發展到能夠徹底模擬app運行時的沙箱系統,各類開源項目層出不窮,在此挑選了幾個表明性的框架,總結其中的技術原理。因爲本人水平有限,插件化框架又至關複雜,文中如有錯誤或者不許確的地方望高手指點。web

內容概要

1、發展歷史

插件化技術最初源於免安裝運行apk的想法,這個免安裝的apk能夠理解爲插件。支持插件化的app能夠在運行時加載和運行插件,這樣即可以將app中一些不經常使用的功能模塊作成插件,一方面減少了安裝包的大小,另外一方面能夠實現app功能的動態擴展。想要實現插件化,主要是解決下面三個問題:緩存

  • 插件中代碼的加載和與主工程的互相調用安全

  • 插件中資源的加載和與主工程的互相訪問微信

  • 四大組件生命週期的管理markdown

 

下面是比較出名的幾個開源的插件化框架,按照出現的時間排序。研究它們的實現原理,能夠大體看出插件化技術的發展,根據實現原理我把這幾個框架劃分紅了三代。app

 

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

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

第三代:VirtualApp比較厲害,可以徹底模擬app的運行環境,可以實現app的免安裝運行和雙開技術。Atlas是阿里今年開源出來的一個結合組件化和熱修復技術的一個app基礎框架,其普遍的應用與阿里系的各個app,其號稱是一個容器化框架。函數

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

2、基本原理

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框架。推薦第二種方式,不用入侵原有的編譯流程。

3、四大組件支持

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分發。

     

4、發展方向

經過對插件化技術的學習,能夠看出目前插件化技術的兩個發展方向

結合組件化技術,成爲一箇中大型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/)

4.深度 | 滴滴插件化方案 VirtualApk 源碼解析

5.VirtualAPK資源加載原理解析
(https://www.notion.so/VirtualAPK-1fce1a910c424937acde9528d2acd537)

相關文章
相關標籤/搜索