Android 淺談模塊化、組件化、插件化、熱修復的簡單理解

目錄

前言 

一、模塊化

二、組件化

1、概念

2、使用

3、版本管理

4、模塊間跳轉

5、資源命名問題

三、插件化

四、熱修復

1、概述

2、流派

3、原理

五、總結


前言 

談到熱修復相信大家應該比較熟悉,因爲它是目前比較重要的技術,平常面試中也是被問的比較多。插件化和熱修復同出一門,倆者都屬於動態更新,而模塊化和組件化是基礎。相信看完本篇的內容,對於這些模糊的概念應該會有一個比較清晰的瞭解。

 

一、模塊化

1、概念

模塊(Module),Android Studio提出的概念,根據不同關注點將原項目中共享的部分或業務抽取出來形成獨立module,這就類似我們最集成的第三方庫的SDK。

2、思想

實際開發中,我們通常會抽取第三方庫、整個項目的初始化的代碼、自定義的Utils工具類、自定義View 、圖片、xml這些(value目錄下的各種xml文件)等到一個共有的Common模塊中,其他模塊在配置Gradle依賴後,就能夠調用這些API。

特別注意的是style.xml文件,對於全局共用的style,我們應該把它也放在common模塊中。例如我們的項目theme主題,本來是放在main組件的style裏面,我們可以把它移到common中,這樣其他組件調試時,作爲一個單獨的項目,也能和主項目有一樣的主題。

 總之,你認爲需要共享的資源,都應該放在common組件中。

3、使用

每一個Module都可以在自身的 build.gradle 中進行設置兩種格式:application和library。 

apply plugin: 'com.android.application'
//或
apply plugin: 'com.android.library'

引用時,就像添加依賴GitHub庫一樣。

 

二、組件化

1、概念

組件化是基於模塊化的可以在打包時是設置爲library,開始調試運行是設置成application。目的是解耦與加快開發。組件化適用於多人合作開發的場景,隔離不需要關注的模塊,大家各自分工、各守其職。簡而言之,就是把一個項目分開成多個項目

(1)好處

  • 業務模塊分開,解耦的同時也降低了項目的複雜度。
  • 開發調試時不需要對整個項目進行編譯。
  • 多人合作時可以只關注自己的業務模塊,把某一業務當成單一項目來開發。
  • 可以靈活的對業務模塊進行組裝和拆分。

(2)規則

  • 只有上層的組件才能依賴下層組件,不能反向依賴,否則可能會出現循環依賴的情況;
  • 同一層之間的組件不能相互依賴,這也是爲了組件之間的徹底解耦;

2、使用

1、在整個項目 gradle.properties 文件中,添加代碼

#是否處於debug狀態
isDebug = flase

 

2、在其他Module的 build.gradle 文件中,添加代碼

if (isDebug.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

3、在宿主Module的 build.gradle 文件中,添加代碼

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    //...
    if(!isDebug.toBoolean()){//不是debug,就添加依賴其他模塊
        compile project(':home')
        compile project(':personal')
        compile project(':video')
    }
    if(isDebug.toBoolean()){
        compile project(':common')
    }
}

3、版本管理

每個Module的build.gradle文件中很多地方需要些寫版本號,例如 targetSdkVersion、appcompat-v7、第三方庫等。修改時都要同時修改多份build.gradle文件。如果把版本號可以統一管理起來,就會省時省力,又避免不同的組件使用的版本不一樣,導致合併在一起時引起衝突。

整個項目根目錄下的 build.gradle 文件中,添加代碼

ext {
    compileSdkVersion = 25
    buildToolsVersion = "25.0.2"
    minSdkVersion = 14
    targetSdkVersion = 25
    versionCode = 1
    versionName = "1.0"
}

每個Mudule的 build.Gradle 文件中,改寫代碼

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
    defaultConfig {
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName
    }
//...
}

4、模塊間跳轉

我們知道,通常在Gradle中依賴的庫是可以直接引用的,即通過startActivity跳轉。根據組件化的規則,宿主可以依賴下層組件,而組件之間不可以依賴。因此,當常規業務模塊之間遇到業務需求,進行互相跳轉時該怎麼處理?

這裏簡單介紹兩種方式,即路由和反射。路由的方式以用阿里的ARouter/美團的WMRouter,但是我覺得人少、項目小的公司必要用到這麼強大的工具,直接反射就好。

放在common組件中的EventUtile工具類

public class EventUtil{
    /**
     * 頁面跳轉
     * className  全路徑類名
     */
    public static void open(Context context,String className){
        try {
            Class clazz = Class.forName(className);
            Intent intent = new Intent(context,clazz);
           context.startActivity(intent);
        } catch (ClassNotFoundException e) {
            Log.e("zhuang","未集成,無法跳轉");
        }
    }
    /**
     * 頁面跳轉,可以傳參,參數放在intent中,所以需要傳入一個intent
     */
    public static void open(Context context,String className,Intentintent){
        try {
            Class clazz = Class.forName(className);
           intent.setClass(context,clazz);
           context.startActivity(intent);
        } catch (ClassNotFoundException e) {
            Log.e("zhuang","未集成,無法跳轉");
        }
    }
}

5、資源命名問題

首先,多組件集成時,特別容易出現資源命名重複的問題。可以讓各個組件中使用統一前綴,比如home組件中的資源,以home_開通、video組件中以video_開頭。當然,如果是嫌麻煩,我們可以在build.gradle文件中,加入如下代碼:

resourcePrefix"home_"

但是這個功能其實很弱。比較xml文件報錯,依然可以運行,圖片文件不已home_爲前綴,也不會報錯。

三、插件化

也是屬於模塊化的一種體現。將完整的項目按業務劃分不同的插件,分治法,越小的模塊越容易維護。單位是apk,一個完整的項目。插件化比熱修復簡單,插件化只是增加新的功能或資源文件。靈活性在於加載apk,按需下載,動態更新。

實現原理

  1. 通過dexclassloader加載。
  2. 代理模式添加生命週期。
  3. hook思想跳過清單驗證。

Android 使用Java的反射機制總結

Android 動態代理與Hook機制詳解

總結

  • 宿主和插件分開編譯
  • 動態更新插件
  • 按需下載插件
  • 緩解65535方法數限制

四、熱修復

1、概述

熱修復與插件化都利用classloader實現加載新功能。熱修復比插件化複雜,插件化只是增加新的功能或資源文件,所以不涉及搶先加載舊類的使命。熱修復爲了修復bug,要將新的同名類替舊的同名bug類,要搶在加載bug類之前加載新的類。

2、流派

   熱修復作爲當下熱門的技術,在業界內比較著名的有阿里巴巴的AndFix、Dexposed,騰訊QQ空間的超級補丁和微信的Tinker,以及大衆點評nuwa和美團Robust。阿里百川推出的HotFix熱修復服務就基於AndFix技術,定位於線上緊急BUG的即時修復。雖然Tinker支持修復的功能強大兼容性很好,但是不能即時生效、集成負責、補丁包大。

3、原理

(1)native修復方案

AndFix 

提供了一種運行時在Native修改Filed指針的方式,實現方法的替換,達到即時生效無需重啓,對應用無性能消耗的目的。實現過程三步驟:

  • setup():對於Dalvik的即時編譯機制(JIT),在運行時裝載libdvm.so動態鏈接庫,從而獲取native層內部函數:dvmThreadSelf( ):查詢當前的線程;dvmDecodeIndirectRef( ):根據當前線程獲得ClassObject對象。
  • setFieldFlag():把 private、protected的方法和字段都改爲public,這樣纔可被動態庫看見並識別,因爲動態庫會忽略非public屬性的字段和方法。
  • replaceMethod():該步驟是方法替換的核心。拿到新舊方法的指針,將指針指向新的替換方法來實現方法替換。

(2)Dex 分包方案

概述

DEX分包是爲了解決65536方法限制,系統在應用打包APK階段,會將有調用關係的類打包在同一個Dex文件中,並且同一個dex中的類會被打上`CLASS_ISPREVERIFIED`的標誌。因爲加載後的類不能卸載,必須通過重啓後虛擬機進行加載才能實現修復,所以此方案不支持即時生效。

 QQ空間超級補丁

是把BUG方法修復以後放到一個patch.dex,拿到當前應用BaseDexClassloader後,通過反射獲取到DexPathList屬性對象pathList、再反射調用pathList的dexElements方法把patch.dex轉化爲Element[],兩個Element[]進行合併,最後把patch.dex插入到dexElements數組的最前面,讓虛擬機去加載修復完後的方法,就可以達到修復目的。

問題 

而然,問題就是兩個有調用關係的類不再同一個Dex文件中,那麼就會拋“unexpected DEX problem”異常報錯。解決辦法,就是單獨放一個AnitLazyLoad類在另外DEX中,在每一個類的構造方法中引用其他DEX中的唯一AnitLazyLoad類,避免類被打上CLASS_ISPREVERIFIED標誌。

不足 

此方案通過增加dex來修復,但是修復的類到了一定數量,就需要花不少的時間加載。對手淘這種航母級應用來說,啓動耗時增加2s以上是不能夠接受的事。在ART模式下,如果類修改了結構,就會出現內存錯亂的問題。爲了解決這個問題,就必須把所有相關的調用類、父類子類等等全部加載到patch.dex中,導致補丁包異常的大,進一步增加應用啓動加載的時候,耗時更加嚴重。

微信Tinker

微信Tinker採用的是DEX差量包,整體替換DEX的方案。主要的原理是與QQ空間超級補丁技術基本相同,但不將patch.dex增加到elements數組中。差量的方式拿到patch.dex,開啓新進程的服務TinkerPatchService,將patch.dex與應用中的classes.dex合併,得到一個新的fix_classess.dex。通過反射操作得到PathClassLoader的DexPatchList,再反射調用patchlist的makeDexElements()方法,把fix_classess.dex直接替換到Element[]數組中去,達到修復的目的。從而提高了兼容性和穩定性。

(3)Instand Run 方案

Instant Run,是android studio2.0新增的一個運行機制,用來減少對當前應用的構建和部署的時間。

構建項目的流程:

構建修改的部分 → 部署修改的dex或資源 → 熱部署,溫部署,冷部署。

熱拔插:方法實現的修改,或者變量值修改,不需要重啓應用,不需要重建當前activity。

溫拔插:代碼修改涉及到了資源文件,activity需要被重啓。

冷拔插:修改了繼承規則、修改了方法簽名,app需要被重啓,但是仍然不需要重新安裝 。

五、總結

模塊化、組件化、插件化通訊方式不同之處

  1. 模塊化相互引入,抽取了公共的common模塊,其他模塊自然要引入這個module。
  2. 組件化主流是隱式和路由。隱式使解耦和靈活大大降低,因此路由是主流。
  3. 插件化本身是不同進程,因此是binder機制進程間通訊。

推薦閱讀我的相關係列文章:

Android 熱修復原理,DVM或ART與JVM的介紹ClassLoad及雙親委派模型理解

Android 熱修復開源方案阿里、微信、美團等

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章