天天看點

VirtualAPK插件架構介紹(一)----架構接入 背景 什麼是插件化? 為什麼要插件化? VirtualAPK架構接入 結束語

背景

近幾年Android插件化技術是比較熱門領域,6月30日滴滴也開源了自研的插件化架構VirtualAPK。一周多的時間已經有3300+star,從官方wiki可以看到,該架構功能完備,支援Android四大元件,良好的相容性,且入侵性較低,作為加載耦合插件方案是較好選擇。筆者會寫一系列VirtualAPK學習的文章,第一篇先介紹一下插件化和VirtualAPK如何接入,請先Clone https://github.com/piglet696/VirtualAPKDemo.git 項目,從一個全新的項目接入VirtualAPK。

什麼是插件化?

插件(Plugin)在計算機領域是一個通用的概念,維基百科上這樣解釋:

In computing, a plugin is a software component that adds a specific feature to an existing computer program.

意思是對現有的程式添加額外的功能元件。這樣的好處是可以對原有程式進行擴充,以Android Studio為例,我們可以安裝FindBugs插件,這樣IDE就具備了更強大的靜态代碼檢查功能。插件可以是第三方的開發者開發,隻需要遵循一定的協定即可,插件可以随時更新、解除安裝,可以看到插件化使我們的程式更加靈活。

為什麼要插件化?

插件化聽起來很美好,但是谷歌并沒有賦予Android這種能力,因為這樣存在安全風險,試想一下表面上是一個電腦App,谷歌稽核通過後,加載插件後變成一個竊取隐私的App怎麼辦。那為什麼在國内插件化技術非常熱門,各個大廠都在使用呢?總結了下有以下幾個原因:

  1. 國内App的版本碎片較為嚴重,想減少更新成本。國内沒有統一的應用分發市場,靜默更新需要ROM的支援,否則第三方應用市場需要Root的方式實作;
  2. 解決App方法數超65536問題。在谷歌官方的Multidex方案沒有出現時,可以采用插件方式解決,而現在該問題不應該是你選擇插件化的原因;
  3. 減少App包大小。宿主App包含了主要功能,其餘放到插件中實作,動态下發;
  4. 快速修改線上Bug或者釋出新功能。作為程式員這個場景肯定遇到過,剛釋出一個新版本存在Bug,再重新發版成本又特别高。而隻釋出插件成本就會很低,又能快速解決線上問題。
  5. 子產品解耦,協同開發。一個超級App,可以拆分成不同的插件子產品,基于一定的規則業務線之間協同開發,最終每個業務子產品生成一個插件,由宿主加載組合即可。再比如你需要接入第三方App的功能,隻需要第三方基于插件協定開發即可,不需要在宿主代碼中進行開發,很好的做到業務隔離,合作終止時直接移除插件即可。

當你想引入插件化技術時問一下自己接入的原因和要解決什麼問題,不要為了接入而接入,畢竟接入和維護需要一定的成本,比如解決一些奇怪的問題、一套插件管理&下發的背景等。

VirtualAPK架構接入

簡介

VirtualAPK的開源位址:https://github.com/didi/VirtualAPK ,請讀者詳細閱讀一下Readme和Wiki,對VirtualAPK架構有個整體的認識。引用Wiki中VirtualAPK和其他開源架構的對比:

特性 DynamicLoadApk DynamicAPK Small DroidPlugin VirtualAPK
支援四大元件 隻支援Activity 隻支援Activity 隻支援Activity 全支援 全支援
元件無需在宿主manifest中預注冊 ×
插件可以依賴宿主 ×
支援PendingIntent × × ×
Android特性支援 大部分 大部分 大部分 幾乎全部 幾乎全部
相容性适配 一般 一般 中等
插件建構 部署aapt Gradle插件 Gradle插件

VirtualAPK工程中包括了插件的源代碼和DEMO,為了更加清晰的展示如何接入SDK,筆者建立了一個VirtualAPKDemo工程,建議Clone代碼後結合源碼閱讀文章,更容易了解。該工程包括宿主工程Host和插件工程ImageBrowser,可以把宿主工程想象成一個所有功能的展示入口,而ImageBrowser插件工程用于實作具體的圖檔浏覽業務。為了展示VirtualAPK對耦合型插件的支援,這裡宿主和插件都會依賴Picasso庫加載圖檔,但VirtualAPK架構建構插件APK時會把插件中的Picasso庫移除,插件直接使用宿主的Picasso庫,下文會具體說明。

環境準備

  • Gradle版本需要為2.14.1,可以使用

    gradle -v

    檢視環境中配置的Gradle版本号。也可以使用工程中

    gradlew

    來編譯,可以在

    gradle/wrapper/gradle-wrapper.properties

    中更改版本号
  • com.android.tools.build的版本号為2.1.3

宿主工程接入

Host宿主工程接入需要以下6步

  1. 在宿主工程根目錄的build.gradle添加依賴
    dependencies {
     classpath 'com.didi.virtualapk:gradle:0.9.0'
    }
               
  2. 在App的工程子產品的build.gradle添加使用gradle插件
  3. 添加VirtualAPK SDK compile依賴
    dependencies {
     compile 'com.didi.virtualapk:core:0.9.0'
    }
               
  4. 在App的工程子產品proguard-rules.pro檔案添加混淆規則(Ps:Picasso庫的混淆規則沒有列出來)
    -keep class com.didi.virtualapk.internal.VAInstrumentation { *; }
    -keep class com.didi.virtualapk.internal.PluginContentResolver { *; }
    -dontwarn com.didi.virtualapk.**
    -dontwarn android.content.pm.**
    -keep class android.** { *; }
               
  5. MyApplication類是繼承了Application,覆寫attachBaseContext函數,進行插件SDK初始化工作
    @Override
    protected void attachBaseContext(Context base)  {
       super.attachBaseContext(base);
       PluginManager.getInstance(base).init();
    }                
  6. 在使用插件之前加載插件,可以根據具體業務場景選擇合适時機加載,我是在MainActivity的onCreate時機加載
    protected void onCreate(Bundle savedInstanceState) {
           // 加載plugin.apk插件包
         PluginManager pluginManager = PluginManager.getInstance(this);
         File apk = new File(getExternalStorageDirectory(), "plugin.apk");
         if (apk.exists()) {
             try {
                 pluginManager.loadPlugin(apk);
             } catch (Exception e) {
                 e.printStackTrace();
             }
         } 
     }                

經過上述6步後,VirtualAPK插件功能就內建到宿主中了,宿主打包和運作方式沒有任何改變。接下來看下插件工程如何內建和建構的。

插件工程接入

ImageBrowser插件工程接入分為3步:

  1. ImageBrowser工程根目錄的build.gradle添加依賴
    dependencies {
     classpath 'com.didi.virtualapk:gradle:0.9.0'
    }
               
  2. 在App的工程子產品的build.gradle添加使用gradle插件和插件配置資訊,資訊需要放在檔案最下面
    apply plugin: 'com.didi.virtualapk.plugin'
    ...
    ...
    // 插件配置資訊,放在檔案最下面
    virtualApk {
     packageId =              // 插件資源id,避免資源id沖突
     targetHost='../host/app'      // 宿主工程的路徑
     applyHostMapping = true      // 插件編譯時是否啟用應用宿主的apply mapping
    }
               
    解釋一下上面3個參數的作用
    • packageId用于定義每個插件的資源id,多個插件間的資源Id字首要不同,避免資源合并時産生沖突
    • targetHost指明宿主工程的應用子產品,插件編譯時需要擷取宿主的一些資訊,比如mapping檔案、依賴的SDK版本資訊、R資源檔案,一定不能填錯,否則在編譯插件時會提示找不到宿主工程。
    • applyHostMapping表示插件是否開啟apply mapping功能。當宿主開啟混淆時,一般情況下插件就要開啟applyHostMapping功能。因為宿主混淆後函數名可能有fun()變為a(),插件使用宿主混淆後的mapping映射來編譯插件包,這樣插件調用fun()時實際調用的是a(),才能找到正确的函數調用。
  3. 最後一步生成插件,需要使用Gradle指令
    gradle clean assemblePlugin
    或者
    ./gradlew clean assemblePlugin
               

強調一下如果建構時確定Gradle版本需要為2.14.1,否則建構可能發生錯誤。建構成功後在build/outputs/apk 或者plugin目錄中檢視插件,plugin目錄和apk目錄中插件的差別在于plugin将插件以packageName_timestamp格式重命名,DEMO中的插件建構成功後才3KB。

VirtualAPK插件架構介紹(一)----架構接入 背景 什麼是插件化? 為什麼要插件化? VirtualAPK架構接入 結束語

插件包位置.png

官方WIKI中還說明了:

  • 插件包均是Release包,不支援debug模式的插件包
  • 如果存在多個productFlavors,那麼将會建構出多個插件包

前面說到VirtualAPK是對耦合型業務有很好的支援,對于我們的DEMO來說,宿主和插件都用到了Picasso庫,我們反編譯插件包後看一下裡面包含的内容如下圖所示。

VirtualAPK插件架構介紹(一)----架構接入 背景 什麼是插件化? 為什麼要插件化? VirtualAPK架構接入 結束語

插件包内容.png

可以看到,插件包中沒有Picasso庫的相關源碼,建構時VirtualAPK已經幫我們移除了。需要注意,宿主和插件包中依賴的SDK版本需要完全一緻時才會被移除。

運作插件

因為宿主中代碼寫的是從SD卡根目錄加載plugin.apk插件,是以我們需要将生成的插件重命名後放到指定位置。

adb push 插件路徑 /sdcard/plugin.apk
           

然後啟動宿主程式後點選"檢視更多美圖",此時加載的Activity來自于插件中,啟動代碼如下所示

@Override
public void onClick(View v) {
    if (PluginManager.getInstance(this).getLoadedPlugin(PLUGIN_PKG_NAME) == null) {
        Toast.makeText(getApplicationContext(),
                    "插件未加載,請嘗試重新開機APP", Toast.LENGTH_SHORT).show();
        return;
    }
    Intent intent = new Intent();
    intent.setClassName("com.virtualapk.imageplugin",         "com.virtualapk.imageplugin.ImageBrowserActivity");
    startActivity(intent);
}                

OK,插件就麼運作起來了,可以看到在開發宿主和插件時,幾乎沒有侵入,在生成插件時需要一些指令操作,可以通過腳本實作編譯插件+Push到SD卡中,提高開發效率。

DEMO比較簡單隻包含了Activity,後面會擴充到其他元件的使用,大家可以仔細看下官方WIKI-插件開發指南,裡面介紹了插件中四大元件的使用、so庫的加載、已知的限制和插件加載時機的選取問題。

其他注意點

  1. 要想清楚宿主和插件的業務邊界很重要,才能找到插件的入口點。Demo中是以ImageBrowserActivity為邊界,從這個Activity之後的功能來來自于插件中。其實我們可以把插件看成一個類提供者,可以使用Class.forName()這種方式使用插件中的類,是以不是隻能從Activity/Fragment作為入口點。
  2. 編譯宿主工程時會生成一些資訊(在build/VAHost檔案夾下),插件建構時會讀取這些資訊,是以要確定運作的宿主和插件基于相同資訊建構的,宿主變化時請重新建構插件。

結束語

VirtualAPK插件架構在使用過程中還是比較簡單的,沒有過多的侵入性,是以大家可以嘗試玩起來。要實作一個插件化技術需要對Android系統原理、編譯建構都有較好了解,是以接下會學習、拆解該架構的技術原理,請大家繼續關注後續文章吧,會第一時間釋出在微信公衆号:Mob行者。VirtualAPK官方QQ群号656602897,作者玉剛大神也在啊!

VirtualAPK插件架構介紹(一)----架構接入 背景 什麼是插件化? 為什麼要插件化? VirtualAPK架構接入 結束語

微信公衆号二維碼.jpg