前言
談到熱修復(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)原理
- 通過dexclassloader加載彻坛。
- 代理模式添加生命周期。
- 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é)
模塊化概疆、組件化、插件化通訊方式不同之處
- 模塊化相互引入峰搪,抽取了公共的common模塊岔冀,其他模塊自然要引入這個(gè)module。
- 組件化主流是隱式和路由。隱式使解耦和靈活大大降低使套,因此路由是主流罐呼。
- 插件化本身是不同進(jìn)程,因此是binder機(jī)制進(jìn)程間通訊侦高。