最有深度的模塊化惕橙、組件化、插件化钉跷、熱修復(fù)原理總結(jié)弥鹦,你確定不來看看嗎?

前言

談到熱修復(fù)相信大家應(yīng)該比較熟悉爷辙,因?yàn)樗悄壳氨容^重要的技術(shù)彬坏,平常面試中也是被問的比較多。插件化和熱修復(fù)同出一門膝晾,倆者都屬于動(dòng)態(tài)更新栓始,而模塊化和組件化是基礎(chǔ)。相信看完本篇的內(nèi)容血当,對于這些模糊的概念應(yīng)該會(huì)有一個(gè)比較清晰的了解幻赚。

原文鏈接:https://blog.csdn.net/csdn_aiyang/article/details/103735995?

一禀忆、模塊化

1、概念

模塊(Module)坯屿,Android Studio提出的概念油湖,根據(jù)不同關(guān)注點(diǎn)將原項(xiàng)目中共享的部分或業(yè)務(wù)抽取出來形成獨(dú)立module,這就類似我們最集成的第三方庫的SDK领跛。

2乏德、思想

實(shí)際開發(fā)中,我們通常會(huì)抽取第三方庫吠昭、整個(gè)項(xiàng)目的初始化的代碼喊括、自定義的Utils工具類、自定義View 矢棚、圖片郑什、xml這些(value目錄下的各種xml文件)等到一個(gè)共有的Common模塊中,其他模塊在配置Gradle依賴后蒲肋,就能夠調(diào)用這些API蘑拯。

特別注意的是style.xml文件,對于全局共用的style兜粘,我們應(yīng)該把它也放在common模塊中申窘。例如我們的項(xiàng)目theme主題,本來是放在main組件的style里面孔轴,我們可以把它移到common中剃法,這樣其他組件調(diào)試時(shí),作為一個(gè)單獨(dú)的項(xiàng)目路鹰,也能和主項(xiàng)目有一樣的主題贷洲。

總之,你認(rèn)為需要共享的資源晋柱,都應(yīng)該放在common組件中优构。

3、使用

每一個(gè)Module都可以在自身的 build.gradle 中進(jìn)行設(shè)置兩種格式:application和library雁竞。

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

引用時(shí)钦椭,就像添加依賴GitHub庫一樣。

二浓领、組件化

1、概念

組件化是基于模塊化的势腮,可以在打包時(shí)是設(shè)置為library联贩,開始調(diào)試運(yùn)行是設(shè)置成application。目的是解耦與加快開發(fā)捎拯。組件化適用于多人合作開發(fā)的場景泪幌,隔離不需要關(guān)注的模塊盲厌,大家各自分工、各守其職祸泪。簡而言之吗浩,就是把一個(gè)項(xiàng)目分開成多個(gè)項(xiàng)目

(1)好處

  • 業(yè)務(wù)模塊分開,解耦的同時(shí)也降低了項(xiàng)目的復(fù)雜度没隘。
  • 開發(fā)調(diào)試時(shí)不需要對整個(gè)項(xiàng)目進(jìn)行編譯懂扼。
  • 多人合作時(shí)可以只關(guān)注自己的業(yè)務(wù)模塊,把某一業(yè)務(wù)當(dāng)成單一項(xiàng)目來開發(fā)右蒲。
  • 可以靈活的對業(yè)務(wù)模塊進(jìn)行組裝和拆分阀湿。

(2)規(guī)則

  • 只有上層的組件才能依賴下層組件,不能反向依賴瑰妄,否則可能會(huì)出現(xiàn)循環(huán)依賴的情況陷嘴;
  • 同一層之間的組件不能相互依賴,這也是為了組件之間的徹底解耦间坐;

2灾挨、使用

1、在整個(gè)項(xiàng)目 gradle.properties 文件中竹宋,添加代碼

#是否處于debug狀態(tài)
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宪潮、版本管理

每個(gè)Module的build.gradle文件中很多地方需要些寫版本號溯警,例如 targetSdkVersion、appcompat-v7狡相、第三方庫等梯轻。修改時(shí)都要同時(shí)修改多份build.gradle文件。如果把版本號可以統(tǒng)一管理起來尽棕,就會(huì)省時(shí)省力喳挑,又避免不同的組件使用的版本不一樣,導(dǎo)致合并在一起時(shí)引起沖突滔悉。

整個(gè)項(xiàng)目根目錄下的 build.gradle 文件中伊诵,添加代碼

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

每個(gè)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回官、模塊間跳轉(zhuǎn)

我們知道曹宴,通常在Gradle中依賴的庫是可以直接引用的,即通過startActivity跳轉(zhuǎn)歉提。根據(jù)組件化的規(guī)則笛坦,宿主可以依賴下層組件区转,而組件之間不可以依賴。因此版扩,當(dāng)常規(guī)業(yè)務(wù)模塊之間遇到業(yè)務(wù)需求废离,進(jìn)行互相跳轉(zhuǎn)時(shí)該怎么處理?

這里簡單介紹兩種方式礁芦,即路由和反射蜻韭。路由的方式以用阿里的ARouter/美團(tuán)的WMRouter,但是我覺得人少宴偿、項(xiàng)目小的公司必要用到這么強(qiáng)大的工具湘捎,直接反射就好。

放在common組件中的EventUtile工具類

public class EventUtil{
    /**
     * 頁面跳轉(zhuǎn)
     * 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","未集成窄刘,無法跳轉(zhuǎn)");
        }
    }
    /**
     * 頁面跳轉(zhuǎn)窥妇,可以傳參,參數(shù)放在intent中娩践,所以需要傳入一個(gè)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","未集成活翩,無法跳轉(zhuǎn)");
        }
    }
}

5、資源命名問題

首先翻伺,多組件集成時(shí)材泄,特別容易出現(xiàn)資源命名重復(fù)的問題《至耄可以讓各個(gè)組件中使用統(tǒng)一前綴拉宗,比如home組件中的資源,以home_開通辣辫、video組件中以video_開頭旦事。當(dāng)然,如果是嫌麻煩急灭,我們可以在build.gradle文件中姐浮,加入如下代碼:

resourcePrefix"home_"

但是這個(gè)功能其實(shí)很弱。比較xml文件報(bào)錯(cuò)葬馋,依然可以運(yùn)行卖鲤,圖片文件不已home_為前綴,也不會(huì)報(bào)錯(cuò)畴嘶。

三蛋逾、插件化

也是屬于模塊化的一種體現(xiàn)。將完整的項(xiàng)目按業(yè)務(wù)劃分不同的插件窗悯,分治法区匣,越小的模塊越容易維護(hù)。單位是apk蟀瞧,一個(gè)完整的項(xiàng)目沉颂。插件化比熱修復(fù)簡單,插件化只是增加新的功能或資源文件悦污。靈活性在于加載apk铸屉,按需下載,動(dòng)態(tài)更新切端。

實(shí)現(xiàn)原理

  1. 通過dexclassloader加載彻坛。
  2. 代理模式添加生命周期。
  3. hook思想跳過清單驗(yàn)證踏枣。

Android 使用Java的反射機(jī)制總結(jié)

Android 動(dòng)態(tài)代理與Hook機(jī)制詳解

總結(jié)

  • 宿主和插件分開編譯
  • 動(dòng)態(tài)更新插件
  • 按需下載插件
  • 緩解65535方法數(shù)限制

四昌屉、熱修復(fù)

1、概述

熱修復(fù)與插件化都利用classloader實(shí)現(xiàn)加載新功能茵瀑。熱修復(fù)比插件化復(fù)雜间驮,插件化只是增加新的功能或資源文件,所以不涉及搶先加載舊類的使命马昨。熱修復(fù)為了修復(fù)bug竞帽,要將新的同名類替舊的同名bug類,要搶在加載bug類之前加載新的類鸿捧。

2屹篓、流派

熱修復(fù)作為當(dāng)下熱門的技術(shù),在業(yè)界內(nèi)比較著名的有阿里巴巴的AndFix匙奴、Dexposed堆巧,騰訊QQ空間的超級補(bǔ)丁和微信的Tinker,以及大眾點(diǎn)評nuwa和美團(tuán)Robust泼菌。阿里百川推出的HotFix熱修復(fù)服務(wù)就基于AndFix技術(shù)谍肤,定位于線上緊急BUG的即時(shí)修復(fù)。雖然Tinker支持修復(fù)的功能強(qiáng)大兼容性很好灶轰,但是不能即時(shí)生效谣沸、集成負(fù)責(zé)、補(bǔ)丁包大笋颤。

3乳附、原理

(1)native修復(fù)方案

AndFix

提供了一種運(yùn)行時(shí)在Native修改Filed指針的方式,實(shí)現(xiàn)方法的替換伴澄,達(dá)到即時(shí)生效無需重啟赋除,對應(yīng)用無性能消耗的目的。實(shí)現(xiàn)過程三步驟:

  • setup():對于Dalvik的即時(shí)編譯機(jī)制(JIT)非凌,在運(yùn)行時(shí)裝載libdvm.so動(dòng)態(tài)鏈接庫举农,從而獲取native層內(nèi)部函數(shù):dvmThreadSelf( ):查詢當(dāng)前的線程;dvmDecodeIndirectRef( ):根據(jù)當(dāng)前線程獲得ClassObject對象敞嗡。
  • setFieldFlag():把 private颁糟、protected的方法和字段都改為public航背,這樣才可被動(dòng)態(tài)庫看見并識別,因?yàn)閯?dòng)態(tài)庫會(huì)忽略非public屬性的字段和方法棱貌。
  • replaceMethod():該步驟是方法替換的核心玖媚。拿到新舊方法的指針,將指針指向新的替換方法來實(shí)現(xiàn)方法替換婚脱。

(2)Dex 分包方案

概述

DEX分包是為了解決65536方法限制今魔,系統(tǒng)在應(yīng)用打包APK階段,會(huì)將有調(diào)用關(guān)系的類打包在同一個(gè)Dex文件中障贸,并且同一個(gè)dex中的類會(huì)被打上CLASS_ISPREVERIFIED的標(biāo)志错森。因?yàn)榧虞d后的類不能卸載,必須通過重啟后虛擬機(jī)進(jìn)行加載才能實(shí)現(xiàn)修復(fù)篮洁,所以此方案不支持即時(shí)生效涩维。

QQ空間超級補(bǔ)丁

是把BUG方法修復(fù)以后放到一個(gè)patch.dex,拿到當(dāng)前應(yīng)用BaseDexClassloader后袁波,通過反射獲取到DexPathList屬性對象pathList激挪、再反射調(diào)用pathList的dexElements方法把patch.dex轉(zhuǎn)化為Element[],兩個(gè)Element[]進(jìn)行合并锋叨,最后把patch.dex插入到dexElements數(shù)組的最前面垄分,讓虛擬機(jī)去加載修復(fù)完后的方法,就可以達(dá)到修復(fù)目的娃磺。

問題

而然薄湿,問題就是兩個(gè)有調(diào)用關(guān)系的類不再同一個(gè)Dex文件中,那么就會(huì)拋“unexpected DEX problem”異常報(bào)錯(cuò)偷卧。解決辦法豺瘤,就是單獨(dú)放一個(gè)AnitLazyLoad類在另外DEX中,在每一個(gè)類的構(gòu)造方法中引用其他DEX中的唯一AnitLazyLoad類听诸,避免類被打上CLASS_ISPREVERIFIED標(biāo)志坐求。

不足

此方案通過增加dex來修復(fù),但是修復(fù)的類到了一定數(shù)量晌梨,就需要花不少的時(shí)間加載桥嗤。對手淘這種航母級應(yīng)用來說,啟動(dòng)耗時(shí)增加2s以上是不能夠接受的事仔蝌。在ART模式下泛领,如果類修改了結(jié)構(gòu),就會(huì)出現(xiàn)內(nèi)存錯(cuò)亂的問題敛惊。為了解決這個(gè)問題渊鞋,就必須把所有相關(guān)的調(diào)用類、父類子類等等全部加載到patch.dex中,導(dǎo)致補(bǔ)丁包異常的大锡宋,進(jìn)一步增加應(yīng)用啟動(dòng)加載的時(shí)候儡湾,耗時(shí)更加嚴(yán)重。

微信Tinker

微信Tinker采用的是DEX差量包执俩,整體替換DEX的方案盒粮。主要的原理是與QQ空間超級補(bǔ)丁技術(shù)基本相同,但不將patch.dex增加到elements數(shù)組中奠滑。差量的方式拿到patch.dex,開啟新進(jìn)程的服務(wù)TinkerPatchService妒穴,將patch.dex與應(yīng)用中的classes.dex合并宋税,得到一個(gè)新的fix_classess.dex。通過反射操作得到PathClassLoader的DexPatchList讼油,再反射調(diào)用patchlist的makeDexElements()方法杰赛,把fix_classess.dex直接替換到Element[]數(shù)組中去,達(dá)到修復(fù)的目的矮台。從而提高了兼容性和穩(wěn)定性乏屯。

(3)Instand Run 方案

Instant Run,是android studio2.0新增的一個(gè)運(yùn)行機(jī)制瘦赫,用來減少對當(dāng)前應(yīng)用的構(gòu)建和部署的時(shí)間辰晕。

構(gòu)建項(xiàng)目的流程:

構(gòu)建修改的部分 → 部署修改的dex或資源 → 熱部署,溫部署确虱,冷部署含友。

熱拔插:方法實(shí)現(xiàn)的修改,或者變量值修改校辩,不需要重啟應(yīng)用窘问,不需要重建當(dāng)前activity。

溫拔插:代碼修改涉及到了資源文件宜咒,activity需要被重啟惠赫。

冷拔插:修改了繼承規(guī)則、修改了方法簽名故黑,app需要被重啟儿咱,但是仍然不需要重新安裝 。

五场晶、總結(jié)

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

  1. 模塊化相互引入峰搪,抽取了公共的common模塊岔冀,其他模塊自然要引入這個(gè)module。
  2. 組件化主流是隱式和路由。隱式使解耦和靈活大大降低使套,因此路由是主流罐呼。
  3. 插件化本身是不同進(jìn)程,因此是binder機(jī)制進(jìn)程間通訊侦高。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嫉柴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子奉呛,更是在濱河造成了極大的恐慌计螺,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞧壮,死亡現(xiàn)場離奇詭異登馒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)咆槽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門陈轿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人秦忿,你說我怎么就攤上這事麦射。” “怎么了灯谣?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵潜秋,是天一觀的道長。 經(jīng)常有香客問我胎许,道長半等,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任呐萨,我火速辦了婚禮杀饵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谬擦。我一直安慰自己切距,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布惨远。 她就那樣靜靜地躺著谜悟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪北秽。 梳的紋絲不亂的頭發(fā)上葡幸,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機(jī)與錄音贺氓,去河邊找鬼蔚叨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蔑水。 我是一名探鬼主播邢锯,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼搀别!你這毒婦竟也來了丹擎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤歇父,失蹤者是張志新(化名)和其女友劉穎蒂培,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榜苫,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡护戳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了单刁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡府适,死狀恐怖羔飞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情檐春,我是刑警寧澤逻淌,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站疟暖,受9級特大地震影響卡儒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜俐巴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一骨望、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧欣舵,春花似錦擎鸠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至糟把,卻和暖如春绢涡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遣疯。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工雄可, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓滞项,卻偏偏與公主長得像狭归,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子文判,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354