成為一名優(yōu)秀的Android開發(fā),需要一份完備的知識體系,在這里匆赃,讓我們一起成長為自己所想的那樣~缀棍。
一然低、App 的編譯和打包流程
1鹿蜀、APK 的組成
我們都知道庵佣,APK 其實是一個 zip 類型的壓縮包满哪,而一個典型的 APK 通常都會包含了以下七部分的內(nèi)容:
- 1婿斥、AndroidManifest.xml:如果 App 是一本書,那么這個文件就是它的 “封面” 和 “目錄” 哨鸭。它記載了 App 的名稱民宿、權(quán)限聲明、所包含的組件等一系列信息像鸡。
- 2勘高、classes.dex:它是由項目源碼生成的 .class 文件經(jīng)過進(jìn)一步地轉(zhuǎn)換而生成的 Android 系統(tǒng)可識別的 Dalvik Byte Code。并且坟桅,由于 Android 系統(tǒng)中的字節(jié)碼和標(biāo)準(zhǔn) JVM 中的字節(jié)碼是有區(qū)別的华望,所以如果 App 中引用了第三方 jar 包的話,那么通常情況下它也會被包含在 classes.dex 中仅乓。
- 3赖舟、resources.arsc:資源索引表,包含編譯后的二進(jìn)制資源文件夸楣。每當(dāng)在 res 文件夾下放一個文件時宾抓,aapt 就會自動生成對應(yīng)的 id 并保存在 .R 文件中子漩,但 .R 文件僅僅只是保證編譯程序不會報錯,實際上在應(yīng)用運行時石洗,系統(tǒng)會根據(jù) ID 尋找對應(yīng)的資源路徑幢泼,而 resources.arsc 文件就是用來記錄這些 ID 和 資源文件位置對應(yīng)關(guān)系 的文件。
- 4讲衫、res 目錄:未編譯的資源文件缕棵。
- 5、asserts:額外建立的資源文件夾涉兽。res 和 assets 的不同在于 res 目錄下的文件會在 .R 文件中生成對應(yīng)的資源 ID招驴,而 assets 不會自動生成對應(yīng)的 ID,而是通過 AssetManager 類的接口來獲取枷畏。
- 6别厘、libs 目錄:如果存在的話,存放的是 ndk 編出來的 so 庫 拥诡。
- 7触趴、META-INF 目錄:用于保存 App 的簽名和校驗信息,以保證程序的完整性渴肉。當(dāng)生成 APK 包時雕蔽,系統(tǒng)會對包中的所有內(nèi)容做一次校驗,然后將結(jié)果保存在這里宾娜。而手機(jī)在安裝這一 App 時還會對內(nèi)容再做一次校驗批狐,并和 META-INF 中的值進(jìn)行比較,以避免 APK 被惡意篡改前塔。其中包含如下 三個文件嚣艇,如下所示:
- 1)、MANIFEST.MF:其中每一個資源文件都有一個對應(yīng)的 SHA-256-Digest(SHA1) 簽名华弓,MANIFEST.MF 文件的 SHA256(SHA1) 經(jīng)過 base64 編碼的結(jié)果即為 CERT.SF 中的 SHA256(SHA1)-Digest-Manifest 值食零。
- 2)、CERT.SF:除了開頭處定義的 SHA256(SHA1)-Digest-Manifest 值寂屏,后面幾項的值是對 MANIFEST.MF 文件中的每項再次 SHA256(SHA1) 經(jīng)過 base64 編碼后的值贰谣。
- 3)、CERT.RSA:其中包含了公鑰迁霎、加密算法等信息吱抚。首先,對前一步生成的 CERT.SF 使用了 SHA256(SHA1)生成了數(shù)字摘要并使用了 RSA 加密考廉,接著秘豹,利用了開發(fā)者私鑰進(jìn)行簽名。然后昌粤,在安裝時使用公鑰解密既绕。最后啄刹,將其與未加密的摘要信息(MANIFEST.MF文件)進(jìn)行對比,如果相符凄贩,則表明內(nèi)容沒有被修改誓军。
接下來,我們來看看 App 的編譯和打包過程疲扎。
2昵时、APK 的編譯打包流程
早在 深入探索 Android 包體積優(yōu)化(匠心制作) 一文中我們就探討過打包的部分流程,這里我們需要更加全面地了解下评肆。Android 官方的編譯打包流程圖如下所示:
為了 了解更多打包過程中的細(xì)節(jié),我們需要查看更加詳細(xì)的舊版 APK 打包流程圖 非区,如下圖所示:
打包流程可簡述為如下 八個步驟:
- 1瓜挽、首先,.aidl(Android Interface Description Language)文件需要通過 aidl 工具轉(zhuǎn)換成編譯器能夠處理的 Java 接口文件征绸。
- 2久橙、同時,資源文件(包括 AndroidManifest.xml管怠、布局文件淆衷、各種 xml 資源等等)將被 AAPT(Asset Packaging Tool)(Android Gradle Plugin 3.0.0 及之后使用 AAPT2 替代了 AAPT)處理為最終的 resources.arsc,并生成 R.java 文件以保證源碼編寫時可以方便地訪問到這些資源蝎土。
- 3淘讥、然后厂僧,通過 Java Compiler 編譯 R.java、Java 接口文件佳头、Java 源文件,最終它們會統(tǒng)一被編譯成 .class 文件晴氨。
- 4康嘉、因為 .class 并不是 Android 系統(tǒng)所能識別的格式,所以還需要通過 dex 工具將它們轉(zhuǎn)化為相應(yīng)的 Dalvik 字節(jié)碼(包含壓縮常量池以及清除冗余信息等工作)籽前。這個過程中還會加入應(yīng)用所依賴的所有 “第三方庫”亭珍。
- 5、下一步枝哄,通過 ApkBuilder 工具將資源文件肄梨、DEX 文件打包生成 APK 文件。
- 6挠锥、接著峭范,系統(tǒng)將上面生成的 DEX、資源包以及其它資源通過 apkbuilder 生成初始的 APK 文件包瘪贱。
- 7纱控、然后辆毡,通過簽名工具 Jarsigner 或者其它簽名工具對 APK 進(jìn)行簽名得到簽名后的 APK。如果是在 Debug 模式下甜害,簽名所用的 keystore 是系統(tǒng)自帶的默認(rèn)值舶掖,否則我們需要提供自己的私鑰以完成簽名過程。
- 8尔店、最后眨攘,如果是正式版的 APK,還會利用 ZipAlign 工具進(jìn)行對齊處理嚣州,以提高程序的加載和運行速度鲫售。而對齊的過程就是將 APK 文件中所有的資源文件距離文件的起始位置都偏移4字節(jié)的整數(shù)倍,這樣通過 mmap 訪問 APK 文件的速度會更快该肴,并且會減少其在設(shè)備上運行時的內(nèi)存占用情竹。
至此,我們已經(jīng)了解了整個 APK 編譯和打包的流程匀哄。
那么秦效,為什么 XML 資源文件要從文本格式編譯成二進(jìn)制格式?
主要基于以下 兩點原因
:
1涎嚼、
空間占用更小
:因為所有 XML 元素的標(biāo)簽阱州、屬性名稱、屬性值和內(nèi)容所涉及到的字符串都會被統(tǒng)一收集到一個字符串資源池中法梯,并且會去重苔货。有了這個字符串資源池,原來使用字符串的地方就會被替換成一個索引到字符串資源池的整數(shù)值立哑,從而可以減少文件的大小蒲赂。2、
解析效率更高
:二進(jìn)制格式的 XML 文件解析速度更快刁憋。這是由于二進(jìn)制格式的 XML 元素里面不再包含有字符串值滥嘴,因此就避免了進(jìn)行字符串解析,從而提高了解析效率至耻。
而 Android 資源管理框架又是如何快速定位到最匹配資源的若皱?
主要基于兩個文件,如下所示:
- 1尘颓、
資源 ID 文件 R.java
:賦予每一個非 assets 資源一個 ID 值走触,這些 ID 值以常量的形式定義在 R.java 文件中。 - 2疤苹、
資源索引表 resources.arsc
:用來描述那些具有 ID 值的資源的配置信息互广。
除此之外,APK 的簽名也是至關(guān)重要的,那么惫皱,其簽名算法的實現(xiàn)原理是怎樣的呢像樊?下面我們就來了解下 APK 簽名算法的實現(xiàn)原理。
3旅敷、簽名算法的原理
什么是簽名生棍?
在 Apk 中寫入一個 “指紋”。指紋寫入以后媳谁,Apk 中有任何修改涂滴,都會導(dǎo)致這個指紋無效,Android 系統(tǒng)在安裝 Apk 進(jìn)行簽名校驗時就會不通過晴音,從而保證了安全性柔纵。
那么,為什么要簽名锤躁?
主要有 兩點原因
搁料,如下所示:
- 1、確保 Apk 來源的真實性进苍。
- 2加缘、確保 Apk 沒有被第三方篡改鸭叙。
在了解 APK 簽名的實現(xiàn)之前觉啊,我們還必須知道什么是數(shù)字摘要。
數(shù)字摘要
對一個任意長度的數(shù)據(jù)沈贝,通過一個 Hash 算法計算后杠人,都可以得到一個固定長度的二進(jìn)制數(shù)據(jù),這個數(shù)據(jù)就稱為 “摘要”宋下。
在簽名和校驗的流程之中嗡善,應(yīng)用了許多密碼學(xué)的知識,這里我們需要先大致了解一下学歧。
Hash(散列算法)的基礎(chǔ)原理
Hash 算法就是 將數(shù)據(jù)(如一段文字)運算變?yōu)榱硪还潭ㄩL度值罩引。它的特點主要有如下 三點
:
- 1、
唯一性
枝笨。 - 2袁铐、
固定長度
:比較常用的 Hash 算法有 MD5 和 SHA1,MD5 的長度是128位横浑,SHA1 的長度是160位剔桨。 - 3、
不可逆性
徙融。
而常用的 Hash 算法有如下 三種
:
- 1洒缀、
SHA-1
:在密碼學(xué)中,SHA-1(安全散列算法1)是一種加密散列函數(shù),它接受輸入并產(chǎn)生一個160 位(20 字節(jié))散列值树绩,稱為消息摘要萨脑。 - 2、
MD5
:MD5 消息摘要算法(英語:MD5 Message-Digest Algorithm)葱峡,一種被廣泛使用的密碼散列函數(shù)砚哗,可以產(chǎn)生出一個128位(16字節(jié))的散列值(hash value),用于確保信息傳輸完整一致砰奕。 - 3蛛芥、
SHA-2
:名稱來自于安全散列算法2(Secure Hash Algorithm 2)的縮寫,一種密碼散列函數(shù)算法標(biāo)準(zhǔn)军援,其下又可再分為六個不同的算法標(biāo)準(zhǔn)仅淑,包括了:SHA-224、SHA-256胸哥、SHA-384涯竟、SHA-512、SHA-512/224空厌、SHA-512/256庐船。
簽名和校驗的主要過程
簽名就是 在摘要的基礎(chǔ)上再進(jìn)行一次加密,對摘要加密后的數(shù)據(jù)就可以當(dāng)作數(shù)字簽名嘲更。
簽名過程:
簽名過程可以細(xì)分為 三步
筐钟,如下所示:
- 1、
計算摘要
:通過 Hash 算法提取出原始數(shù)據(jù)的摘要赋朦。 - 2篓冲、
計算簽名
:再通過基于密鑰(私鑰)的非對稱加密算法對提取出的摘要進(jìn)行加密,加密后的數(shù)據(jù)就是簽名信息宠哄。 - 3壹将、
寫入簽名
:將簽名信息寫入原始數(shù)據(jù)的簽名區(qū)塊內(nèi)。
校驗過程:
校驗過程同樣也可以分為 三步
毛嫉,如下:
- 1诽俯、
提取摘要
:首先用同樣的 Hash 算法從接收到的數(shù)據(jù)中提取出摘要。 - 2承粤、
解密簽名
:使用發(fā)送方的公鑰對數(shù)字簽名進(jìn)行解密暴区,解密出原始摘要。 - 3密任、
比較摘要
:如果解密后的數(shù)據(jù)和提取的摘要一致颜启,則校驗通過;如果數(shù)據(jù)被第三方篡改過浪讳,解密后的數(shù)據(jù)和摘要將會不一致缰盏,則校驗不通過。
那么,我們該如何保證公鑰的可靠性呢口猜?答案是 數(shù)字證書
负溪。
數(shù)字證書
數(shù)字證書是 身份認(rèn)證機(jī)構(gòu)(Certificate Authority)頒發(fā)
的,主要包含了以下 六類信息
:
- 1济炎、證書頒發(fā)機(jī)構(gòu)
- 2川抡、證書頒發(fā)機(jī)構(gòu)簽名
- 3、證書綁定的服務(wù)器域名
- 4须尚、證書版本崖堤、有效期
- 5、簽名使用的加密算法(非對稱算法耐床,如 RSA)
- 6密幔、公鑰等
接收方收到消息后,需要先向 CA 驗證證書的合法性撩轰,再進(jìn)行簽名校驗
胯甩。
需要注意的是,Apk 的證書通常是自簽名的堪嫂,也就是由開發(fā)者自己制作偎箫,沒有向 CA 機(jī)構(gòu)申請。Android 在安裝 Apk 時并沒有校驗證書本身的合法性皆串,只是從證書中提取公鑰和加密算法
淹办,這也正是對第三方 Apk 重新簽名后,還能夠繼續(xù)在沒有安裝這個 Apk 的系統(tǒng)中繼續(xù)安裝的原因愚战。
keystore 和證書格式
keystore 文件中包含了 私鑰娇唯、公鑰和數(shù)字證書
齐遵。根據(jù)編碼不同寂玲,keystore 文件分為很多種,Android 使用的是 Java 標(biāo)準(zhǔn) keystore 格式 JKS(Java Key Storage)梗摇,所以通過 Android Studio 導(dǎo)出的 keystore 文件是以 .jks 結(jié)尾的拓哟。
keystore 使用的 證書標(biāo)準(zhǔn)是 X.509
,X.509 標(biāo)準(zhǔn)也有多種 編碼格式伶授,常用的有兩種:pem(Privacy Enhanced Mail)和 der(Distinguished Encoding Rules)
断序。jks 使用的是 der 格式,但是糜烹,Android 也支持直接使用 pem 格式的證書進(jìn)行簽名违诗。
下面,我們了解下兩種證書編碼格式的區(qū)別疮蹦,如下所示:
-
DER(Distinguished Encoding Rules)
:二進(jìn)制格式诸迟,所有類型的證書和私鑰都可以存儲為 der 格式。 -
PEM(Privacy Enhanced Mail)
:base64 編碼,內(nèi)容以-----BEGIN xxx----- 開頭阵苇,以-----END xxx----- 結(jié)尾壁公。
jarsigner 和 apksigner 的區(qū)別
Android 提供了 兩種對 Apk 的簽名方式
,一種是基于 JAR 的簽名方式绅项,另一種是基于 Apk 的簽名方式紊册,它們的 主要區(qū)別在于使用的簽名文件不一樣:jarsigner 使用 keystore 文件進(jìn)行簽名;而 apksigner 除了支持使用 keystore 文件進(jìn)行簽名外快耿,還支持直接指定 pem 證書文件和私鑰進(jìn)行簽名囊陡。
在我們簽名時,除了要指定 keystore 文件和密碼外掀亥,也要指定 alias 和 key 的密碼关斜,這是為什么呢?
keystore 是一個密鑰庫铺浇,也就是說它可以存儲多對密鑰和證書痢畜,keystore 的密碼是用于保護(hù) keystore 本身的,每一對密鑰和證書是通過 alias 來區(qū)分的鳍侣。所以 jarsigner 是支持使用多個證書對 Apk 進(jìn)行簽名的丁稀,apksigner 也同樣支持。
Android Apk V1 驗證簽名的原理
Android Apk V1 驗證簽名的過程主要可以分為如下 四步
:
- 1倚聚、解析出 CERT.RSA 文件中的證書线衫、公鑰,解密 CERT.RSA 中的加密數(shù)據(jù)惑折。
- 2授账、解密結(jié)果和 CERT.SF 的指紋進(jìn)行對比,保證 CERT.SF 沒有被篡改惨驶。
- 3白热、接著,將 CERT.SF 中的內(nèi)容再和 MANIFEST.MF 中的指紋對比粗卜,保證 MANIFEST.MF 文件沒有被篡改屋确。
- 4、MANIFEST.MF 中的內(nèi)容和 APK 所有文件指紋逐一對比续扔,保證 APK 沒有被篡改攻臀。
在整個 App 的編譯打包過程中,Gradle 自動化構(gòu)建工具發(fā)揮出了重要作用纱昧,而編譯速度可是需要我們迫切解決的一大痛點刨啸。下面,我們就來看看如何對編譯進(jìn)行提速识脆。
二设联、編譯提速
1加匈、了解 Android Studio 3.0 依賴類型的變化
在 Android Studio 3.0 之前 共有 六種
依賴方式,如下所示:
- 1仑荐、
Compile
:對所有的 build type 以及 falvors 編譯并且打包到 APK雕拼。 - 2、
Provided
:對所有的 build type 以及 falvors 只編譯粘招,不打包到 APK啥寇。 - 3、
APK
:只會打包到 APK洒扎,不參與編譯辑甜,比如引用 jar 中的類或者方法, 編譯時就會報錯。 - 4袍冷、
Test compile
:僅對單元測試的代碼和打包的測試 APK 有效磷醋,而對 debug 或者 release APK 包無效胡诗。 - 5邓线、
Debug compile
:僅對 debug 模式的編譯和打包的 debug APK 有效,而對 test 或者 release APK 打包無效煌恢。 - 6
骇陈、Release compile
:僅對 Release 模式的編譯和打包的 Release APK 有效,而對 test 或者 debug APK 打包無效瑰抵。
而在 Android Studio 3.0 之后你雌,新增了兩種方式:api 和 implementation。其中 api 完全等同于 compile
二汛。
api
等同于 compile, 用 api 指令編譯婿崭,表示 三方庫的依賴對 module 是可見的
,即等同 app Module 可以使用此三方庫依賴肴颊。
implementation
特點是 將該依賴隱藏在內(nèi)部氓栈,而不對外部公開
。比如在組件化項目中苫昌,有一個 app module 和一個 base module颤绕,app moudle 引入了 base module幸海。其中 base module 使用 implementation 依賴了 Glide 庫祟身,因為 implementation 是內(nèi)部依賴,所以是無法調(diào)用到 Glide 庫的功能的物独。因此 implementation 可 以 對外隱藏不必要的接口袜硫,并且,使用它可以有效地 提高編譯速度挡篓。比如婉陷,在組件化項目中一般含有多個 Moudle 模塊帚称,如 Module A => Module B => Moudle C
, 比如 改動 Moudle C 接口的相關(guān)代碼,如果使用的是 implementation秽澳,這時候編譯只需要單獨編譯 Module B 模塊就行闯睹,但是如果使用 api 或者舊版本的 compile,由于 Module A 也可以訪問到 Moudle C担神,所以 Module A 部分也需要重新編譯楼吃。所以,在使用無錯的情況下妄讯,可以優(yōu)先使用 implementation
孩锡。
2、現(xiàn)有編譯方案
Gradle 的官方方案 Instant Run亥贸。在 Android Plugin 2.3 之前躬窜,它使用了 Multidex 實現(xiàn)。在 Android Plugin 2.3 之后炕置,它使用了 Android 5.0 新增的 Split APK 機(jī)制荣挨。如下圖所示:
但是,如果你的應(yīng)用較大朴摊,會有如下四個問題:
- 1垦沉、
多進(jìn)程的限制
:如果應(yīng)用存在多進(jìn)程,熱交換和溫交換都不能生效仍劈。此時厕倍,Instant Run 的速度就會降低不少。 - 2贩疙、
Split APK 安裝耗時
:雖然 Split APK 的安裝不會生成 Odex 文件讹弯,但是這里依然會進(jìn)行簽名校驗和文件拷貝,這可能需要幾秒到幾十秒这溅。 - 3组民、
Annotation Processor 需全量 javac 的問題
:在 Gradle 4.6 及之前,如果項目中運用了 Annotation Processor悲靴,本次修改以及它依賴的模塊都需要全量 javac臭胜,這可能會需要幾十秒。 - 4癞尚、
常量需全量 javac 的問題
:此時耸三,常量池會直接把值編譯到其他類中,Gradle 并不知道有哪些類使用了這個常量浇揩。
阿里的 FreeLine 在大部分情況比 Instant Run 更快仪壮,但是,它 犧牲了正確性
胳徽。因為积锅,為了追求更快的速度爽彤,它直接忽略了 Annotation 和常量改變可能帶來錯誤的編譯產(chǎn)物。而 Instant Run 作為官方方案缚陷,它優(yōu)先保證了 100% 的正確性适篙。
但是,在 Android Studio 3.5 之后箫爷,Android 8.0 以后的設(shè)備將會使用新的方案 Apply Changes 去代替 Instant Run匙瘪。而 ApplyChange 采用了跟 InstantRun 不一樣的原理來加快 AndroidStudio 部署安裝 APK 的流程。下面蝶缀,我們就來了解下他們之間的區(qū)別丹喻。
InstantRun
InstantRun 主要解決以下兩個問題:
- 1、減少構(gòu)建和部署 app 到手機(jī)的時間翁都。
- 2碍论、熱更新代碼改動,無需重啟 app 或者 activity柄慰。
為了實現(xiàn)這兩個目標(biāo)鳍悠,InstantRun 通過重寫 apk 的構(gòu)建流程往每個類里去注入 Hook(鉤子) 來達(dá)到類的熱替換。關(guān)于 InstantRun 詳細(xì)的實現(xiàn)原理可以看看我之前寫的深入探索Android啟動速度優(yōu)化一文坐搔。
對于小型的應(yīng)用藏研,InstantRun 確實很好用,能夠節(jié)省構(gòu)建和部署的時間概行,并且不會出錯蠢挡。但是,對于大型的復(fù)雜應(yīng)用凳忙,它會導(dǎo)致更長的構(gòu)建時間业踏,同時由于 InstantRun 構(gòu)建過程和正常的 app 構(gòu)建存在沖突,常常出現(xiàn)讓開發(fā)者意想不到的錯誤涧卵。AS 開發(fā)團(tuán)隊在連續(xù)幾個大版本中都嘗試去解決這些問題勤家,但是效果不理想。
所以基于此柳恐,AS 開發(fā)者團(tuán)隊 重新設(shè)計了底層的架構(gòu)伐脖,推出了 ApplyChangs。和 InstantRun 不同的是乐设,它不會在構(gòu)建過程中去修改 apk讼庇。取而代之,它使用了 Android 8.0(Oreo)上支持的 Runtime Instrumentation 以及更新的設(shè)備和模擬器在運行時重定義類伤提。
ApplyChanges
對于 運行在 Android 8.0 或者更新版本上的設(shè)備和虛擬機(jī)巫俺,Android Studio 現(xiàn)在有 三個按鈕
來控制應(yīng)用程序重啟的程度:
-
Run
:會部署所有的改動并重啟應(yīng)用程序。 -
Apply Changes
:會嘗試應(yīng)用資源和代碼的更改肿男,并只重啟 Activity介汹, 而不是重啟應(yīng)用程序。 -
Apply Code Changes
:會嘗試應(yīng)用代碼的更改舶沛,而不重啟任何東西嘹承。
通常只有方法體內(nèi)部的代碼更改才會對 Apply Changes 具有兼容性。而 ApplyChanges 的 實現(xiàn)原理 就是 找出 AndroidStudio 構(gòu)建出來的 apk 和已經(jīng)安裝到手機(jī)設(shè)備 apk 的差異如庭。找出差異后叹卷,然后將差異發(fā)送到手機(jī)上執(zhí)行差異合并。ApplyChanges 的 總體架構(gòu)
如下圖所示:
那么坪它,理想的編譯方案是怎么樣的呢骤竹?
3、理想的編譯方案
我們可以把安裝的 Base APK 作為一個殼 APK往毡,而真正的業(yè)務(wù)代碼都放到 Assets 的 ClassesN.dex 中
蒙揣。該方案需要包含以下 三個優(yōu)化點
:
- 1、
免安裝
:可以參考 Tinker 熱修復(fù)的實現(xiàn)原理开瞭,每次只把修改以及依賴的類插入到 pathclassloader 的最前方即可懒震。 - 2、
使用 ReDex 源碼中的 Oatmeal
:首次安裝運行時 ClassesN.dex 轉(zhuǎn)換成 Odex 很耗時嗤详,我們可以使用 ReDex 優(yōu)化模塊下的 Oatmeal个扰,通過它應(yīng)用可以在 100 ms 內(nèi)生成一個完全解釋執(zhí)行的 Odex 文件。 - 3葱色、
關(guān)閉 JIT 優(yōu)化
:使用把修改和依賴的類插入到 pathclassloader 最前方的這種方式在 Android N的混合編譯 會遇到一些問題:無論是使用插入 pathlist 還是 parent classloader 的方式递宅,若補(bǔ)丁修改的 class 已經(jīng)存在于 app image(app image 的作用是記錄已經(jīng)編譯好的 “熱代碼”,并且在啟動時一次性把它們加載到緩存苍狰,而預(yù)先加載是為了代替用時查找以提升應(yīng)用的性能)恐锣,它們都是無法通過熱補(bǔ)丁更新的。它們在啟動 app 時已經(jīng)加入到 PathClassloader 的 ClassTable 中舞痰,因此系統(tǒng)在查找類時會直接使用 dex 中的 class土榴。這時我們需要關(guān)閉虛擬機(jī)的 JIT 優(yōu)化,通過在 AndroidManifest 指定 android:vmSafeMode=“true”
即可响牛。
4玷禽、編譯速度優(yōu)化
除了將電腦更換為 Mac Pro 頂配版之外,還有以下方式可以提升編譯速度:
- 1呀打、及時升級 Gradle 和 Build Tools 編譯工具鏈矢赁,充分利用谷歌最新的優(yōu)化成果。
- 2贬丛、可以將項目中基本不變的模塊拆離出去撩银,使用遠(yuǎn)端 Cache 的模式保留編譯后的緩存,具體的搭建流程可參見 Caching for faster builds豺憔。
- 3额获、使用 Android Gradle Plugin 3.0.0 和更高的版本够庙,因為此時在默認(rèn)情況下啟用 AAPT2,它替代了 AAPT 來編譯資源抄邀,并實現(xiàn)了資源的增量編譯耘眨,其中并將資源的編譯拆分為了兩個步驟:Compile 和 Link。Compile 負(fù)責(zé)將資源文件編譯為二進(jìn)制格式境肾,Link 則會合并所有已編譯的文件剔难,并將它們打包到一個軟件包中。
- 4奥喻、Android Studio 3.1 開始默認(rèn)使用 D8 編譯器(3.0推出)偶宫,它取代了之前的 dx 工具,將 .class 文件轉(zhuǎn)換為 Dex 文件环鲤,使用它能夠提升編譯速度和減少生成的 Dex 的大小纯趋。此外,在 Android Studio 3.1 開始楔绞,你可以在 gradle.properties 開啟 R8结闸,它的目標(biāo)是取代混淆和 D8,對于 D8 和 R8 的詳細(xì)介紹酒朵,具體可以看看我寫的深入探索Android包體積優(yōu)化一文桦锄。
- 5、此外蔫耽,可以嘗試將構(gòu)建系統(tǒng)切換到 Buck 或使用 Flutter 混合開發(fā)结耀,F(xiàn)lutter 中的 Hot Reload秒級編譯功能比較強(qiáng)大,其堪稱為開發(fā)者的神兵利器匙铡,它能夠在快速修改 UI图甜,增加功能,修復(fù)bug的情況下鳖眼,而不需要去重新啟動應(yīng)用黑毅,即可看到改動效果。
- 6钦讳、最后矿瘦,Gradle 官方有一個 build-scan 的功能,它可以生成構(gòu)建期間的詳細(xì)報告愿卒,其中有性能相關(guān)的統(tǒng)計缚去,可以用于幫助分析一些耗時的 task 。
那么琼开,F(xiàn)lutter 的 Hot Reload 的實現(xiàn)原理是什么呢易结?
在回答這個問題之前,我們必須先了解 Flutter 的編譯模式。
Flutter 的編譯模式
編譯模式大體可以分為 兩種
搞动,如下所示:
-
AOT(Ahead Of Time)編譯
:是在程序運行前就已經(jīng)編譯躏精,因此,在運行時不需要進(jìn)行分析滋尉、編譯玉控,因此執(zhí)行速度更快飞主。 -
JIT(Just In Time)編譯
:代碼可以在程序執(zhí)行時期編譯狮惜,因為要在程序執(zhí)行前進(jìn)行分析、編譯碌识,JIT 編譯可能會導(dǎo)致程序執(zhí)行時間較慢碾篡。
而 Flutter 使用了與眾不同的編譯模式畔况,在開發(fā)階段下适荣,使用了 Kernel Snapshot 模式(對應(yīng) JIT 編譯)惦辛,將 dart 代碼生成了標(biāo)記化的源代碼捺疼,而在運行時編譯使用的是解釋執(zhí)行冈敛。在 release 階段怎顾,iOS 使用 AOT 編譯床佳,編譯器將 dart 代碼生成匯編代碼蔓彩,最終生成 app.framwork导俘,而 android 使用了 Core JIT 編譯峦耘,將 dart 轉(zhuǎn)化為二進(jìn)制模式,并在 VM 啟動前載入旅薄。
因此辅髓,在開發(fā)階段的 Kernel Snapshot 編譯模式下,Hot Reload 會通過掃描項目文件少梁,將有改動的 dart 文件轉(zhuǎn)化為標(biāo)記化源代碼 kernel files洛口,并發(fā)送到正在運行的 DartVM,等待 DartVM 替換資源凯沪,然后通知 Flutter Framework 重建第焰、重新布局、重新繪制 WidgetsTree妨马,即可看到改動效果挺举。
那么,flutter 又是如何觸發(fā) WidgetsTree 的重建呢身笤?
Flutter framework 中 BindingBase 注冊了名為 reassemble的Dart VM 服務(wù)豹悬,用于外部與正在運行的 Dart VM 通信,這樣液荸,便能夠觸發(fā)根節(jié)點樹實現(xiàn)重建操作瞻佛。當(dāng) Hot Reload 導(dǎo)致需重建 WidgetsTree時,reassemble 的 Dart VM 服務(wù)就會被觸發(fā),觸發(fā)后伤柄,就會由根節(jié)點開始一步步實現(xiàn)widgets樹重建绊困,其重建流程如下所示:
ext.flutter.reassemble => BindingBase.reassembleApplication =>
WidgetsBinding.performReassemble => BuildOwner.reassemble => Element.reassemble
復(fù)制代碼
三、廣義的編譯-CI
CI 即 持續(xù)集成适刀,在大型開發(fā)團(tuán)隊中秤朗,CI 的建設(shè)是重中之重,CI 主要包括 打包構(gòu)建笔喉、Code Review取视、代碼工程管理、代碼掃描
等一系列流程常挚。它的 整套運轉(zhuǎn)體系 可以簡化為下圖:
1作谭、持續(xù)集成的原因
構(gòu)建 CI 的目的主要是為了解決以下四個問題。
1)奄毡、項目依賴復(fù)雜
隨著業(yè)務(wù)的發(fā)展折欠,基礎(chǔ)組件庫的數(shù)量會持續(xù)上漲,這個時候組件間的關(guān)系就會變得錯綜復(fù)雜吼过,這將會導(dǎo)致如下 兩個問題
:
- 1锐秦、如果某個開發(fā)同學(xué)需要修改代碼,極有可能會影響到其它業(yè)務(wù)盗忱,牽一發(fā)而動全身酱床。
- 2、人工維護(hù)組件間復(fù)雜的依賴關(guān)系非常困難售淡。
2)斤葱、瑣碎的研發(fā)流程
在日常的功能開發(fā)中,我們一般都會經(jīng) 代碼開發(fā)揖闸、組件發(fā)版揍堕、組件集成、打包汤纸、測試這五個步驟衩茸。如果測試發(fā)現(xiàn) Bug 需要進(jìn)行修復(fù),然后會再次經(jīng)歷代碼修改贮泞、組件發(fā)版楞慈、組件集成、打包啃擦、測試囊蓝,直到測試通過交付產(chǎn)品。傳統(tǒng)的研發(fā)流程如下圖所示:
可以看到令蛉,開發(fā)同學(xué)在整個開發(fā)流程中需要手動提交 MR聚霜、升級組件狡恬、觸發(fā)打包以及去實時監(jiān)控流程的狀態(tài),這樣肯定會嚴(yán)重影響開發(fā)的專注度蝎宇,降低研發(fā)的生產(chǎn)力弟劲。
3)、與 App 性能監(jiān)控體系的融合
隨著 App從 項目初期 => 成長期 => 成熟期姥芥,對性能的要求會越來越高兔乞,為了保障性能的足夠穩(wěn)定,我們需要制造出許多性能監(jiān)控的工具凉唐,以實時監(jiān)控我們應(yīng)用的性能庸追。而 App 性能監(jiān)控體系必須和 CI 結(jié)合起來,以實現(xiàn)流程的自動化和平臺化熊榛。
4)锚国、項目的編譯構(gòu)建速度緩慢
隨著 App 的體積變大腕巡,依賴變多玄坦,項目的編譯構(gòu)建速度會越來越慢,緩慢的編譯速度會嚴(yán)重拖垮開發(fā)同學(xué)的研發(fā)效率绘沉。因此煎楣,提升 App 的編譯構(gòu)建速度刻不容緩。
2车伞、持續(xù)集成的主要步驟
持續(xù)集成涉及的流程非常多择懂,但是有 兩個主要的步驟是非常重要
的,具體如下所示:
1)另玖、代碼檢查
為了防止不符合規(guī)范的代碼提交到遠(yuǎn)程倉庫中困曙,我們需要 自定義一套符合自身項目的編碼規(guī)范,并使用專門的插件來檢測谦去。自定義代碼檢測可以通過完全自己實現(xiàn)或者擴(kuò)展 Findbugs 插件慷丽,例如美團(tuán)就利用 Findbugs 實現(xiàn)了 Android 漏洞掃描工具 Code Arbiter,其中 FindBugs 是一個靜態(tài)分析工具鳄哭,它一般用來檢查類或者 JAR 文件要糊,將字節(jié)碼與一組缺陷模式進(jìn)行對比來發(fā)現(xiàn)可能存在的問題,它可以以獨立的 JAR 包形式運行妆丘,也可以作為集成開發(fā)工具的插件形式而存在锄俄。而 FindBugs 插件具有著極強(qiáng)的可擴(kuò)展性,只需要將擴(kuò)展的 JAR 包導(dǎo)入 FindBugs 插件勺拣,重啟 AS奶赠,即可完成相關(guān)功能的擴(kuò)展。
在 FindBugs 有一款專門對安全問題進(jìn)行檢測的擴(kuò)展插件 Find Security Bugs药有,該插件主要用于對 Web 安全問題進(jìn)行檢測毅戈,也有極少對Android相關(guān)安全問題的檢測規(guī)則。我們只需要 定制化自己的 Find Security Bugs,通過增加檢測項來檢測盡可能多的安全問題竹祷,通過優(yōu)化檢測規(guī)則來減少檢測的誤報 即可谈跛,這里我們可以直接使用 Android_Code_Arbiter 這個插件,它 去除了其中跟 Android 漏洞無關(guān)的漏洞塑陵,保留了與 Android 相關(guān)的感憾,并增加了其它的一些檢測項,以此形成了針對與于 Android 的源碼審計工具令花。
此外阻桅,我們也可以使用 第三方的代碼檢查工具,例如收費的 Coverity兼都,以及 Facebook 開源的 Infer嫂沉。
然而,盡管將問題代碼掃描出來了扮碧,可是還是會有不少開發(fā)同學(xué)不知道如何修改趟章,對于這種情況,我們可以給在自定義代碼掃描工具的時候慎王,對于每一個問題檢查項都給出對應(yīng)的修改方針蚓土。
最后,我們可以據(jù)此建立一個解決項目異常的流程:建立一個服務(wù)專門每天跑項目的 Lint 檢查赖淤,跑完將警告匯總分配到對應(yīng)的負(fù)責(zé)人身上蜀漆,并郵件告知他,直到上線咱旱。
2)确丢、Code Review
Code Review 非常重要,在每一次提交代碼時吐限,我們都需要自己進(jìn)行一次 Code Review鲜侥,然后再讓別人去 Review,以建立自身良好的技術(shù)品牌毯盈。
有些同學(xué)可能會認(rèn)為 CI 并不重要剃毒,它好像跟具體的技術(shù)并無關(guān)聯(lián)。但是搂赋,我們需要知道赘阀,學(xué)會不僅僅是鉆在開發(fā)角度看問題,跳脫出來脑奠,站在用戶角度基公,站在產(chǎn)品角度,或許會有意外的收獲宋欺。
四轰豆、總結(jié)
到這里胰伍,關(guān)于 Android 編譯相關(guān)的知識就介紹完了。下面酸休,總結(jié)一下本篇文章涉及的 三大主題
:
- 1骂租、
App 的編譯和打包流程
:APK 的編譯打包流程、簽名算法的原理斑司。 - 2渗饮、
編譯提速
:了解 Android Studio 3.0 依賴類型的變化、現(xiàn)有編譯方案宿刮、理想的編譯方案互站、編譯速度優(yōu)化。 - 3僵缺、
廣義的編譯-CI
:持續(xù)集成的原因胡桃、持續(xù)集成的主要步驟。
在本篇文章磕潮,我們即涉及到了 Android 編譯的深度方面:App 的編譯和打包流程翠胰、簽名算法的原理,也涉及到了 Android 編譯的廣度方面:持續(xù)集成揉抵。因此亡容,在我們學(xué)習(xí)的過程中,技術(shù)就像是一棵樹冤今,在頂部葉子上各個領(lǐng)域看似毫不相干,但是在一個領(lǐng)域越往下深入茂缚,各個領(lǐng)域相互交錯到的知識或者設(shè)計方式就越多戏罢,所以技術(shù)深度和廣度并不是對立面,對技術(shù)深度的探索不僅有利于你在特定領(lǐng)域有更深理解脚囊,更加可以幫助你輕松切換到另一個領(lǐng)域龟糕,特別是像前端的各細(xì)分領(lǐng)域的工作,很多領(lǐng)域的知識背后都殊途同歸悔耘,而技術(shù)的廣度也不是有的人說的那樣不堪讲岁,在有技術(shù)深度的基礎(chǔ)上,去拓展自己的技術(shù)廣度衬以,其實會讓你對原有技術(shù)的理解變得更加地深入缓艳。
作者:jsonchao
鏈接:https://juejin.cn/post/6844904106545414157
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán)看峻,非商業(yè)轉(zhuǎn)載請注明出處阶淘。