一、知識詳解模塊
1.dex/class深入講解
2.jvm/dvm/art三個虛擬機的深入講解
3.class Loader類加載器的深入講解
二、熱修復應用模塊
1.熱修復原理深入講解
2.如何合理的接入開源的熱修復框架
3.開源框架的原理及版本發(fā)布控制
三、插件化應用模塊
1.插件化原理以及組件化的區(qū)別
2.如何將應用插件化
3.插件化能為我們帶來那些好處
一、知識詳解模塊
1.Class文件結(jié)構(gòu)深入解析(生成、執(zhí)行)
2.dex文件結(jié)構(gòu)深入解析(生成、執(zhí)行)
3.Class與Dex的對比
Class文件是什么?如何生成、作用以及文件格式詳解
其是能夠被JVM虛擬機識別加載并執(zhí)行的文件格式
(1)IDE編譯器--build生成
(2)javac命令生成
(3)通過java命令去執(zhí)行class文件 javac -target 1.6 -source 1.6 Hello.java
記錄一個類文件的所有信息
Class文件結(jié)構(gòu)解析:
(1)一種8字節(jié)的二進制流文件
(2)各個數(shù)據(jù)按順序緊密的排列、無間隙
(3)每一個類或接口都獨自占據(jù)一個class文件
http://blog.csdn.net/linzhuowei0775/article/details/49556621 Class類文件的結(jié)構(gòu) ??
u4 magic; ?它的唯一作用是用于確定這個文件是否為一個能被虛擬機接受的Class文件
u2 minor_version; ?次版本號
u2 major_version; ?主版本號(java版本)
u2 constant_pool_count; ?常量池的數(shù)量(這個容量計數(shù)是從1而不是從0開始)
cp_info constant_pool[constant_pool_count-1]; ?表類型數(shù)據(jù)集合,即常量池中每一項常量都是一個表,共有11種結(jié)構(gòu)各不相同的表結(jié)構(gòu)數(shù)據(jù)
u2 access_flags; ?----------------------------作用域
u2 this_class; ?------------------------------JVM生成的本類對象
u2 super_class; ?-----------------------------JVM生成的父類對象
u2 interfaces_count; ?------------------------接口
u2 interfaces[interfaces_count]; ?
u2 fields_count; ?----------------------------變量
field_info fields[fields_count]; ?
u2 methods_count; ?---------------------------方法
method_info methods[methods_count]; ?
u2 attributes_count; ?------------------------屬性
attribute_info attributes[attributes_count];?
Class文件的弊端:
(1)內(nèi)存占用大,不適合移動端
(2)JVM采用堆棧加載模式,加載速度慢
(3)文件IO操作多,類查找慢
Dex文件是什么?如何生成、作用、文件格式詳解
其是一個能夠被DVM識別,加載并執(zhí)行的文件格式
(1)通過IDE自動--build生成
(2)通過DX命令生成dex文件 dx --dex --output Hello.dex Hello.class
(3)手動運行dex文件到手機 dalvikvm -cp /scard/Hello.dex Hello
記錄整個工程中所有類文件的信息
Dex文件結(jié)構(gòu)解析:
(1)一個8字節(jié)二進制流文件
(2)各個數(shù)據(jù)按照順序緊密排列無縫隙
(3)整個應用中的所有Java源文件都放在一個Dex文件中
http://blog.csdn.net/feglass/article/details/51761902 Android Dex文件結(jié)構(gòu)解析
主要分為三部分:文件頭、索引區(qū)、數(shù)據(jù)區(qū)
header---文件頭
? ? ? ? ?索引區(qū)
string_ids--字符串的索引?
type_ids ---類型索引
proto_ids --方法原型的索引
field_ids ----域的索引
method_ids ---方法的索引
?數(shù)據(jù)區(qū)
class_def -----類的定義區(qū)
data -----------數(shù)據(jù)區(qū)
link_data -------鏈接數(shù)據(jù)區(qū)
Class與Dex的區(qū)別
(1)本質(zhì)上兩者是一樣的,dex是從class文件演變過來的 dx --dex --output xx.dex xx.class
(2)class文件存在很多的冗余信息,dex會去除冗余并合并
(3)結(jié)構(gòu)不一樣
第二章?
1.JVM虛擬機結(jié)構(gòu)解析
2.Dalvik與JVM的不同
3.ART與Dalvik相比有哪些優(yōu)勢
1.JVM虛擬機結(jié)構(gòu)解析
(1)JVM整體結(jié)構(gòu)講解
(2)Java代碼的編譯和執(zhí)行過程
(3)內(nèi)存管理和垃圾回收
JAVA虛擬機、Dalvik虛擬機和ART虛擬機簡要對比:
JVM:Java的跨平臺性是由JVM來實現(xiàn)的,就是把平臺無關的.class字節(jié)碼翻譯成平臺相關的機器碼,來實現(xiàn)的跨平臺;
JVM在把描述類的數(shù)據(jù)從class文件加載到內(nèi)存時,需要對數(shù)據(jù)進行校驗、轉(zhuǎn)換解析和初始化,最終才形成可以被虛擬機直接使用的JAVA類型,因為大量的冗余信息,會嚴重影響虛擬機解析文件的效率
DVM:為了減小執(zhí)行文件的體積,安卓使用Dalvik虛擬機,SDK中有個dx工具負責將JAVA字節(jié)碼轉(zhuǎn)換為Dalvik字節(jié)碼,dx工具對JAVA類文件重新排列,將所有JAVA類文件中的常量池分解,消除其中的冗余信息,重新組合形成一個常量池,所有的類文件共享同一個常量池,使得相同的字符串、常量在DEX文件中只出現(xiàn)一次,從而減小了文件的體積.
Dalvik執(zhí)行的是dex字節(jié)碼,依靠JIT編譯器去解釋執(zhí)行,運行時動態(tài)地將執(zhí)行頻率很高的dex字節(jié)碼翻譯成本地機器碼,然后在執(zhí)行,但是將dex字節(jié)碼翻譯成本地機器碼是發(fā)生在應用程序的運行過程中,并且應用程序每一次重新運行的時候,都要重新做這個翻譯工作,因此,及時采用了JIT,Dalvik虛擬機的總體性能還是不能與直接執(zhí)行本地機器碼的ART虛擬機相比
ART:
Dalvik虛擬機執(zhí)行的是dex字節(jié)碼,ART虛擬機執(zhí)行的是本地機器碼
安卓運行時從Dalvik虛擬機替換成ART虛擬機,并不要求開發(fā)者重新將自己的應用直接編譯成目標機器碼,也就是說,應用程序仍然是一個包含dex字節(jié)碼的apk文件。所以在安裝應用的時候,dex中的字節(jié)碼將被編譯成本地機器碼,之后每次打開應用,執(zhí)行的都是本地機器碼
JIT與AOT兩種編譯模式
JIT:即時編譯技術(shù),JIT會在運行時分析應用程序的代碼,識別哪些方法可以歸類為熱方法,這些方法會被JIT編譯器編譯成對應的匯編代碼,然后存儲到代碼緩存中,以后調(diào)用這些方法時就不用解釋執(zhí)行了,可以直接使用代碼緩存中已編譯好的匯編代碼。這能顯著提升應用程序的執(zhí)行效率
AOT:
ART優(yōu)點:
①系統(tǒng)性能顯著提升
②應用啟動更快、運行更快、體驗更流暢、觸感反饋更及時
③續(xù)航能力提升
④支持更低的硬件
ART缺點
①更大的存儲空間占用,可能增加10%-20%
②更長的應用安裝時間
總的來說ART就是“空間換時間”
JVM與DVM的對比:
(1)JAVA虛擬機運行的是JAVA字節(jié)碼,Dalvik虛擬機運行的是Dalvik字節(jié)碼
(2)Dalvik可執(zhí)行文件體積更小
(3)JVM基于棧,DVM基于寄存器
注意:
在DVM虛擬機中,總是在運行時通過JIT把字節(jié)碼文件編譯成機器碼,因此程序跑起來就比較慢,所以在ART模式(4.0引入,5.0設置為默認解決方案)上,就改為AOT預編譯,也就是在安裝應用或者OTA系統(tǒng)升級時提前把字節(jié)碼編譯成機器碼,這樣就可以直接運行了,提高了運行的效率。但是AOT的缺點,每次執(zhí)行的時間都太長,且ROM空間占用有比較大,所以在Android N上google做了混合編譯,即同時支持JIT+AOT。
簡單來講:安裝的時候不做編譯,而是JIT解釋字節(jié)碼,以便能夠快速啟動,在運行的時候分析運行過程中的代碼以及區(qū)分“熱代碼”,這樣就可以在機器空閑的時候?qū)νㄟ^dex2aot這部分熱代碼采用AOT進行編譯存儲為base.art文件然后在下次啟動的時候一次性把app image加載進來到緩存,預先加載代替用時查找以提升應用的性能,并且同一個應用可以編譯數(shù)次,以找到“熱”的代碼路徑或者對已經(jīng)編譯的代碼進行新的優(yōu)化。
JVM結(jié)構(gòu):
虛擬機內(nèi)存區(qū)域分為:運行時數(shù)據(jù)區(qū)+(執(zhí)行引擎+本地庫接口+本地方法庫)
運行時數(shù)據(jù)區(qū):方法區(qū)、Java棧、Java堆、本地方法棧、程序計數(shù)器
Class文件----(通過類加載器子系統(tǒng))-----加載到內(nèi)存空間(方法區(qū)、JAVA堆、JAVA棧、本地方法棧)-------垃圾收集器(GC)
內(nèi)存優(yōu)化方案
JVM內(nèi)存管理
1.JAVA棧(虛擬機棧):存儲JAVA方法執(zhí)行時所有的數(shù)據(jù);存放基本型,以及對象引用。線程私有區(qū)域
其由棧幀組成,一個棧幀代表一個方法的執(zhí)行,每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀
JAVA棧幀--每個方法從調(diào)用到執(zhí)行完成就對應一個棧幀在虛擬機中入棧到出棧
JAVA棧幀存儲:局部變量、棧操作數(shù)、動態(tài)鏈接、方法出口.
2.JAVA堆:所有通過new創(chuàng)建的對象產(chǎn)生的內(nèi)存都在堆中分配存放對象實例和數(shù)組,此區(qū)域也是垃圾回收器主要作用的區(qū)域。
特點:
是虛擬機中最大的一塊內(nèi)存,是GC要回收的部分
堆區(qū)內(nèi)存:
Young generation(新創(chuàng)建的存儲區(qū))
Old generation(暫時用不到的,不可達的對象存儲區(qū))若是Young與Old都存儲滿了 就會爆出OOM異常
Permanent generation
分成兩個的原因:有利于動態(tài)調(diào)整兩個區(qū)域的大小
即時通信服務時調(diào)用MSG消息比較多,就可以把Young調(diào)整的大一些,這樣便于內(nèi)存的分配,加快對象的創(chuàng)建
3.本地方法棧:是專門為native方法提供服務的 虛擬機中的JIT即時編譯單元負責本機系統(tǒng)中比如C語言或者C++語言和Java程序間的互相調(diào)用,
這個Native Method Stack就是用來存放與本線程互相調(diào)用的本機代碼
4.方法區(qū):存儲被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的數(shù)據(jù),是所有線程的共享區(qū)域。
也稱為永久代(Permanent Generation)但隨著Java8的到來,已放棄永久代改為采用Native Memory來實現(xiàn)方法區(qū)的規(guī)劃。
此區(qū)域回收目標主要是針對常量池的回收和對類型的卸載
5.程序計數(shù)器:可看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器;如果線程在執(zhí)行Java方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令地址;
如果執(zhí)行的是Native方法,這個計數(shù)器的值為空(Undefined)
垃圾回收:
垃圾收集算法(垃圾確認)
1.引用計數(shù)算法(JDk1.2之前提供)
優(yōu)點:實現(xiàn)簡單,判定效率高
缺點:兩個不可達的對象相互引用,導致內(nèi)存無法回收。
2.JDK1.2之后采用可達性算法(根搜索算法)---采用離散數(shù)學原理
從GC Roots為根節(jié)點一直向下遍歷,找到下一個節(jié)點。。。。,這樣找不到的就是不可達的,那這些就是垃圾,被垃圾回收器回收
引用的類型:
強引用(弱引用內(nèi)存不足也不會回收,除非OOM,它是內(nèi)存泄漏的主要原因之一)通常new產(chǎn)生的對象就是強引用
軟引用(SoftReference內(nèi)存充足時,保持引用,內(nèi)存不足時,進行回收)
弱引用(WeakReference不管JVM的內(nèi)存空間是否足夠,總會回收該對象占用的內(nèi)存)
虛引用
垃圾回收算發(fā):(垃圾回收)
1.標記清除算法--
好處:不需要對象的移動,并且僅對不存活的對象進行處理
在存活對象比較多的情況下極為高效,效率問題,標記和清除兩個過程的效率都不高
壞處:一般是使用單線程操作,直接回收不存活對象,會造成大量的不連續(xù)的內(nèi)存碎片,從而不利于后續(xù)對一些對象的分配
2.復制算法
好處:在存活對象比較少時極為的高效,實現(xiàn)簡單,運行高效;每次都是對整個半?yún)^(qū)進行內(nèi)存回收,內(nèi)存分配時也不需要考慮內(nèi)存碎片等情況,只要移動堆頂指針,按順序分配內(nèi)存即可
壞處:需要一塊內(nèi)存空間作為交換空間
3.標記--整理算法--標記清除算法基礎之上的一個算法,解決了內(nèi)存碎片的問題,主要針對對象存活率高的老年代
4.分代收集算法---根據(jù)對象的存活周期的不同將內(nèi)存劃分為幾塊,一般是把Java堆分為新生代和老年代,這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖占惴ā?/p>
在新生代中,每次垃圾收集時都會發(fā)現(xiàn)有大量對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、
沒有額外空間對它進行分配擔保,就必須使用標記—清除算法或標記—整理算法來進行回收
垃圾回收的觸發(fā)機制:(垃圾回收的時機)
java虛擬機無法再為新的對象分配內(nèi)存空間了
手動調(diào)用System.gc()方法
低優(yōu)先級的GC線程,被JVM啟動時就會執(zhí)行GC
Dalvik VM 與JVM的不同
1.執(zhí)行的文件不同,一個是class的一個是dex的
2.類加載的系統(tǒng)與JVM區(qū)別較大
3.可以同時存在多個dvm
4.Dalvik是基于寄存器的,而JVM是基于堆棧的
Dalvik VM與ART的不同之處
DVM使用JIT(即時編譯每次運行都會執(zhí)行編譯)來將節(jié)碼轉(zhuǎn)換成機器碼,效率低
ART采用了AOT預編譯技術(shù),執(zhí)行速度更快(JDK 1.9 代碼的分段緩存技術(shù),執(zhí)行速度更快)
ART會占用更多的應用安裝時間和存儲空間
第三章:
Java中的ClassLoader回顧
Android中的ClassLoader的作用詳解
Android中的動態(tài)加載比一般Java程序復雜在哪里
Android ClassLoader概述
Android中ClassLoader的種類
Android中ClassLoader的特點
ClassLoader源碼講解
Java中的ClassLoader回顧
BootstrapClassLoader: Load JRE\lib\rt.jar或者Xbootclasspath選項指定的jar包
ExtensionClassLoader: Load JRE\lib\ext\*.jar或者Djava.ext.dirs指定目錄下的Jar包
AppClassLoader: Load CLASSPATH或者Djava.class.path所指定的目錄下的類和Jar包
CustomClassLoader:通過java.lang.ClassLoader的子類自定義加載Class
Android中ClassLoader的種類
BootClassLoader:Load JRE\lib\rt.jar或者Xbootclasspath選項指定的jar包及Framework層的一些Class字節(jié)碼文件
PathClassLoader:只能加載已經(jīng)安裝到Android系統(tǒng)中的apk文件(/data/app目錄),是Android默認使用的類加載器。
DexClassLoader:可以加載任意目錄下的dex/jar/apk/zip文件,比PathClassLoader更靈活,是實現(xiàn)熱修復的重點。
BaseDexClassLoader:是PathClassLoader與DexClassLoader的父類
一個應用程序最少需要
BootClassLoader、PathClassLoader兩個類加載器
特點及作用
雙親代理模型的特點
(先判斷類是否是被當前的ClassLoader加載過,加載過則返回,沒有加載過在去看其父類ClassLoader是否加載過,加載過返回,否的話,最終由其子類ClassLoader去加載)
這個特點也就意味著一個類被位于事務中的任意一個ClassLoader加載過,那么在今后的整個系統(tǒng)的生命周期中,該類就不會再重新被加載了,大大提高了類加載的一個效率。
類加載的共享功能(Framework里面的一些類一旦被頂層的ClassLoader加載過,就會保存在緩存里面,以后就不要重新加載了)
類加載的隔離功能(不同繼承路徑上的ClassLoader加載的類肯定不是同一個類,可以防止用戶數(shù)據(jù)被篡改,防止自定義類冒充核心類庫,以訪問核心類庫中的成員變量)
雙親委派機制的描述:
當某個特定的類加載器在接到類加載的請求時,首先將加載任務委托給父類加載器,依次遞歸,如果父類加載器可以完成類的加載任務,就成功返回;
只有父類加載器無法完成加載任務時,自己才去加載。
意義:防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼;例如:
比如兩個類A和類B都要加載System類:
如果不用委托而是自己加載自己的,那么類A就會加載一份System字節(jié)碼,然后類B又會加載一份System字節(jié)碼,這樣內(nèi)存中就出現(xiàn)了兩份System字節(jié)碼。
如果使用委托機制,會遞歸的向父類查找,也就是首選用Bootstrap嘗試加載,如果找不到再向下。這里的System就能在Bootstrap中找到然后加載,
如果此時類B也要加載System,也從Bootstrap開始,此時Bootstrap發(fā)現(xiàn)已經(jīng)加載過了System那么直接返回內(nèi)存中的System即可而不需要重新加載,這樣內(nèi)存中就只有一份System的字節(jié)碼了
雙親委派機制也就是熱修復在技術(shù)上可以實現(xiàn)的根本依據(jù),即:多個dex文件組成一個數(shù)組Element[],按照順序加載,對于一個已經(jīng)加載的Class是不會再次加載的。
同一個ClassLoader加載過的類可以稱之為同一個類(還要有相同的包名、類名)
Android ClassLoader加載順序:
ClassLoader.java中 loadClass()方法,在該方法中判斷是否是被加載過,若沒有主要用到了findClass()方法,而在ClassLoader.java中,findClass()是一個空實現(xiàn),于是到PathClassLoader,DexClassLoader中查看
發(fā)現(xiàn)兩者都是共同父類BaseDexClassLoader中實現(xiàn)的,此時調(diào)用到了DexPathList對象的findClass()方法,而DexPathList對象實在BaseDexClassLoader的構(gòu)造方法中通過new實現(xiàn)的,于是找到
DexPathList類的構(gòu)造方法中找到makeDexElements()方法,該方法返回一個Element[]數(shù)組,在該方法makeDexElements()中遍歷所有的Dex文件,調(diào)用loaddexFile()方法,在該方法中
返回了DexFile對象,并最終存到Element[]數(shù)組中。此時,再來看DexPathList對象的findClass()方法中遍歷Element[]數(shù)組,對數(shù)組中的每一個Element元素(DexFile)調(diào)用DexFile對象的
loadClassBinaryName()方法返回一個Class對象,而loadClassBinaryName()最終調(diào)用的是Native方法defineClassNative()該方法是C與C++實現(xiàn)的。
Android動態(tài)加載的難點:
1.有許多組件類需要注冊之后才能使用
2.資源的動態(tài)加載很復雜
3.Android程序運行時需要一個上下文環(huán)境
第四章熱修復詳解:
熱修復的基本概念講解
當前市面上比較流行的幾種熱修復技術(shù)
方案對比及技術(shù)選型
什么是熱修復?
動態(tài)修復、動態(tài)更新
熱修復有哪些好處
熱修復的流行技術(shù):
QQ空間的超級補丁方案
微信的Tinker
阿里的AndFix,dexposed(最新版本Sophix 3.2.0)
美團的Robust,餓了嗎的migo,百度的hotfix
第五章 本章概述
AndFix的基本介紹
AndFix執(zhí)行流程及核心原理
使用AndFix完成線上bug修復
AndFix源碼講解
https://www.cnblogs.com/wetest/p/7595958.html
https://segmentfault.com/a/1190000011365008
http://blog.csdn.net/lostinai/article/details/54694959
AndFix修復即時生效的原理:
騰訊系的方案為什么不能及時運行?
騰訊系的方案是基于類加載機制采用全量替換的原理完成的,由于類加載的雙親代理特點,已加載到內(nèi)存中的Class是不會再次加載的。
因此,采用騰旭系方案,不會及時生效,不重啟則原來的類還在虛擬機中,就無法加載新類,當再次啟動的時候,去加載新合成的dex文件,以完成增量更新。
這樣Tinker將新舊兩個DEx的差異放在補丁包中,下發(fā)到移動端,在本地采用反射的原理修改dexElements數(shù)組,合成完整的dex文件。
但是全量替換會造成補丁包體積過大,因此,采用了Dexdiff算法,大大優(yōu)化下發(fā)差異包的大小。
在Android N中完全廢棄掉PathClassloader,而采用一個新建Classloader來加載后續(xù)的所有類,即可達到將cache無用化的效果。
AndFix的方案采用底層替換方案,即:通過修改一個方法包括方法入口地址在內(nèi)的每一項數(shù)據(jù),使之指向一個新的方法。
具體來講就是:
通過反射機制得到所要替換的Method對象所對應的object,進而找到這個方法底層Java函數(shù)所對應ArtMethod的真實地址,把舊函數(shù)的所有變量都替換為新函數(shù)的
因為,每一個java方法在DVM/ART中都對應著一個底層Java函數(shù),在Art模式中是:ArtMethod,ArtMethod記錄了這個Java方法的所有信息,包括所屬類、訪問權(quán)限、代碼執(zhí)行地址等等。
通過env->FromReflectedMethod,可以由Method對象得到這個方法對應的ArtMethod的真正起始地址。然后就可以把它強轉(zhuǎn)為ArtMethod指針,從而對其所有成員進行修改。
這樣全部替換完之后就完成了熱修復邏輯。以后調(diào)用這個方法時就會直接走到新方法的實現(xiàn)中了
AndFix兼容性的根源所在:
采用的Native替換方案:都是寫死了ArtMethod結(jié)構(gòu)體(Andfix是把底層結(jié)構(gòu)強轉(zhuǎn)為了art::mirror::ArtMethod),AndFix里面的ArtMethod是遵照android虛擬機art源碼里面的ArtMethod構(gòu)建的。
由于Android是開源的,各個手機廠商都可以對代碼進行改造,而Andfix里ArtMethod的結(jié)構(gòu)是根據(jù)公開的Android源碼中的結(jié)構(gòu)寫死的,若是廠商對開源的ArtMethod結(jié)構(gòu)體進行了修改,
和源碼里面的結(jié)構(gòu)體不一致,替換則出現(xiàn)了問題。
優(yōu)化策略:
直接替換ArtMethod而不是直接替換,以解決兼容性問題,
即時生效帶來的限制:只能是方法級別的替換,不能添加新的字段,不能增加方法,以防止在計算sizeOf()時出現(xiàn)差錯。
AndFix支持從2.3到7.0的Android版本,包括ARM和X86架構(gòu),Dalvik和ART運行時,32位和64位
AndFix的實現(xiàn)原理是方法的替換,使得有Bug的方法永遠都不會被執(zhí)行到。(是阿里的熱修復開源版本)當前最新的
熱修復版本是阿里HotFix 3.0(Sophix 3.2.0)
https://help.aliyun.com/document_detail/61082.html?spm=a2c4g.11186623.6.547.3I5XDy Sophix 3.2.0的穩(wěn)健接入
dexposed和andfix是alibaba的開源項目,都是apk增量更新的實現(xiàn)框架,目前dexposed的兼容性較差,只有2.3,4.0~4.4兼容,
其他Android版本不兼容或未測試,詳細可以去dexposed的github項目主頁查看,而andfix則兼容2.3~6.0,所以就拿這個項目來實現(xiàn)增量更新吧。
AndFix的集成階段
Gradle添加依賴 compile 'com.alipay.euler:andfix:0.5.0@aar'
1.https://github.com/hanfengzqh/AndFix AndFix的GitHub地址
1.AndFix的 PatchManager 的初始化
mPatchManager = new PatchManager(context);
mPatchManager.init(Utils.getVersionName(context));//
//進行熱修復之后,為了將SortedSet mPatchs中的Patch每一個文件取出調(diào)用mAndFixManager.fix();
mPatchManager.loadPatch();
2.加載我們的patch文件
if (mPatchManager != null) {
//網(wǎng)絡有熱修復文件(.apatch)時調(diào)用,將.apatch補丁文件,復制到包內(nèi)/apatch/文件夾下,并將
//包內(nèi)文件夾下的.apatch文件Patch文件,轉(zhuǎn)換為調(diào)用loadPatch(Patch)方法
mPatchManager.addPatch(path);
}
AndFix的準備階段
1.build一個有bug的old apk并安裝到手機上
2.分析問題解決bug后,build一個new apk
補丁會生成一個以.apatch結(jié)尾的文件
AndFix bug修復的常用兩條指令:
usage: apkpatch -f -t -o -k -p <***> -a -e <***> 新建
?-a,--alias ? ? keystore entry alias.
?-e,--epassword <***> ? keystore entry password.
?-f,--from ? ? ? ?new Apk file path.
?-k,--keystore ? ?keystore path.
?-n,--name ? ? ? patch name.
?-o,--out ? ? ? ? output dir.
?-p,--kpassword <***> ? keystore password.
?-t,--to ? ? ? ? ?old Apk file path.
usage: apkpatch -m -o -k -p <***> -a -e <***> 合并
?-a,--alias ? ? keystore entry alias.
?-e,--epassword <***> ? keystore entry password.
?-k,--keystore ? ?keystore path.
?-m,--merge ? ?path of .apatch files.
?-n,--name ? ? ? patch name.
?-o,--out ? ? ? ? output dir.
?-p,--kpassword <***> ? keystore password.
Patch安裝階段:
1.將apatch 文件通過adb push 到手機上
2.使用戶已經(jīng)安裝的應用load我們的apatch文件
3.load成功后驗證我們的bug是否被修復
AndFix優(yōu)劣:
1.原理簡單,集成簡單,使用簡單,即時生效(Andfix采用的方法是,在已經(jīng)加載了的類中直接在native層替換掉原有方法,是在原來類的基礎上進行修改的)
2.只能修復方法級別的bug,極大限制了使用場景
http://blog.csdn.net/cocoooooa/article/details/51096613 Android 熱修補方案(AndFix)源碼解析 涉及到JNI層
https://yq.aliyun.com/articles/74598?spm=a2c4g.11186623.2.33.DpP3QL Android熱修復升級探索——追尋極致的代碼熱替換
AndFix劣勢補充:https://www.cnblogs.com/aimqqroad-13/p/5965683.html AndFix的限制因素
1.不支持YunOS在線推送。
2.不支持添加新的類和新的字段,(官網(wǎng))不支持構(gòu)造方法、參數(shù)數(shù)目大于8或者參數(shù)包括long,double,float基本類型的方法。
3.需要使用加固前的apk制作補丁,但是補丁文件很容易被反編譯,也就是修改過的類源碼很容易暴露
4.使用有些加固平臺可能會使熱補丁功能失效(親測:360加固不存在該問題,是在dex外側(cè)加一層殼)
5.andfix不支持布局資源等的修改(即:在熱修復時新舊apk的版本號不可更改,只能一致,否則“.apatch”補丁包 無法生成)
6.潛在問題:加載一次補丁之后,out.apatch文件會copy到getFileDir目錄下的/apatch文件夾中,
在下次補丁更新時,會檢測補丁是否已經(jīng)添加在apatch文件夾下,若是存在就不會復制加載sdcard的out.apatch
7.AndFix并沒有提供多次修復的解決方案,需要自己封裝
(1)http://www.itdecent.cn/p/58fc4c2cb65a andfix 多次修改同一個方法報錯的解決 Gradle引入方式
(2)https://yq.aliyun.com/articles/63774?spm=5176.10695662.1996646101.searchclickresult.49db6001ZX2cgM android 熱修補之a(chǎn)ndfix實踐(多次修復) Module
(2.1)有新補丁.apatch文件,調(diào)用mPatchManager.addPatch(patchPath)之后,將SD卡下下載的.apatch補丁文件刪除掉,以避免啟動都會復制加載一次。
(2.2)在addPatch()內(nèi)部判斷包內(nèi)apatch/文件夾下存在的.apatch文件刪除掉,以便放入新的.patch補丁文件
8.無安全機制
AndFix源碼解析:
1.PatchManager是一個典型的外觀模式,把api封裝在其內(nèi)部,只留下初始化init()/loadPatch()/addPatch()等方法
PatcthManager內(nèi)部幾個重要的成員變量:
AndFixManager 主要的修復工具類(譬如:removeOptFile()移除文件)
SortedSet mPatchs;里面放置了所有的patch補丁文件
在PatchManager構(gòu)造方法里面就是各主要成員變量的初始化操作,以及創(chuàng)建 apatch與apatch_opt文件夾
2.init(appVersion)初始化操作過程中,完成對Patch文件的添加與刪除操作集合mPatchs。
(1)若是版本號不同(即:版本升級了)就會把包內(nèi)目錄下的apatch與apatch_opt文件夾下的.apatch文件刪掉
(2)若是版本號相同就把所有的.apatch文件轉(zhuǎn)化為Patch文件添加到mPatchs里面,以便loadPatch()加載使用。
3.loadPatch()方法加載,調(diào)用loadPatch(Patch)最終調(diào)用mAndFixManager.fix()方法,
在該方法(存在于AndFixManager類里面)里面驗證簽名,若是簽名驗證通過則將Patch中的File轉(zhuǎn)化為DexFile,
在while循環(huán)里面遍歷DexFile文件,通過dexFile.loadClass()方法查找我們真正需要的Class
然后再調(diào)用fixClass()方法,在該方法內(nèi)部通過反射獲取有 MethodReplace.class 注解標記的方法
在調(diào)用 replaceMethod()完成方法替換,在該方法中調(diào)用AndFix.addReplaceMethod(src, dest);
最終調(diào)用native 方法 replaceMethod(),最終完成方法替換。
@AndFix/src/com/alipay/euler/andfix/AndFix.java
private static native void replaceMethod(Method src, Method dest);
src 需要被替換的原有方法;
dest 對應的就是新方法
4.addPatch(patchPath)最終也是調(diào)用了loadPatch(Patch)也是調(diào)用mAndFixManager.fix()方法
JNI層:
@AndFix/src/com/alipay/euler/andfix/AndFix.java
private static native void replaceMethod(Method src, Method dest);
@AndFix/jni/andfix.cpp
static void replaceMethod(JNIEnv* env, jclass clazz, jobject src,
? ? ? ? jobject dest) {
? ? if (isArt) {
? ? ? ? art_replaceMethod(env, src, dest);
? ? } else {
? ? ? ? dalvik_replaceMethod(env, src, dest);
? ? }
}
最終根據(jù)Method對象找到底層Java函數(shù)對應的ArtMethod的真實地址,完成方法的替換
必知必會:
AndFix的原理及執(zhí)行流程
AndFix的集成及基本用法
AndFix組件化的思路和代碼實現(xiàn)
第六章 Tinker的相關學習
1.Tinker的基本介紹
2.Tinker的執(zhí)行原理及流程
3.使用Tinker完成線上bud修復
4.Tinker源碼講解
1.Tinker基本介紹:
開源項目,微信官方的Android熱補丁解決方案,支持動態(tài)下發(fā)代碼、so、資源,在不需要重新安裝的情況下實現(xiàn)更新。
支持2.x-7.x版本
Tinker主要包含以下三個部分:
1.gradle編譯插件:tinker-patch-gradle-plugin
2.核心SDK:tinker-android-lib
3.非gradle編譯用戶的命令行版本:tinker-patch-cli-1.9.1.jar
Tinker的核心原理
基于Android原生的ClassLoader,開發(fā)了自己的ClassLoader
基于Android原生的AAPT,開發(fā)了自己的AAPT
微信團隊基于Dex文件格式,研發(fā)了自己的DexDiff算法
Tinker的優(yōu)劣:
(1)Tinker不支持修改 AndroidManifest.xml,Tinker不支持新增四大組件
(2)由于Google Play的開發(fā)者條款限制,不建議在GP渠道動態(tài)更新代碼
(3)在Android N上,補丁對應用的啟動時間有輕微的影響
(4)不支持部分三星android-21機型
(5)對于資源替換,不支持修改 remoteView。例如transition動畫,notification icon以及桌面圖標。
https://github.com/Tencent/tinker/wiki ?Tinker -- 微信Android熱補丁方案
若出現(xiàn)資源變更,我們需要使用applyResourceMapping方式編譯,這樣不僅可以減少補丁包大小,同時防止remote view id變更造成的異常情況
Tinker集成階段:
Gradle中添加Tinker依賴
在代碼中完成對Tinker的初始化
因為Tinker需要監(jiān)聽Application的生命周期,在ApplicationLike委托對象里面可以完成Tinker對Application生命周期的監(jiān)聽。
通過ApplicationLike完成Tinker的初始化操作
準備階段
1.build一個old.apk并安裝到手機
2.修改一些功能之后,build一個new.apk
patch文件的生成:
1.命令行的方式完成patch的生成
各個文件的作用及命令參數(shù)講解
通過使用tinker-patch命令生成patch文件
2.利用Gradle插件的方式完成patch包的生成
1.patch命令行方式:
java -jar tinker-patch-cli-1.7.7.jar -old old.apk -new new.apk -config tinker_config.xml -out output_path
(1)修改apk前后不一樣的apk,修改資源文件
(2)tinker-config.xml中更改loader標簽為ApplicationLike類中注解 @DefaultLifeCycle中的自定義application全路徑
(3)修改加密文件、密碼及別名及密碼
(4)
? ? ? ? ? ? android:value="tinker_id_b168b32"/>
2.Gradle插件配置生成
(1)gradle中正確配置tinker參數(shù)(非常重要)
(2)在android studio中直接生成patch文件
在ApplicationLike自定義類中的onBaseContextAttached()方法中
TinkerInstaller.install(ApplicationLike);//完成tinker初始化
調(diào)用
if (Tinker.isTinkerInstalled()) {
? ? ? TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), patch);
}
完成patch補丁文件的加載//TinkerInstaller.onReceiveUpgradePatch();
幾個重要的部分:
安全校驗:無論是補丁合成還是補丁加載,我們都要進行必要的安全校驗
版本管理:Tinker支持補丁升級,甚至是多個補丁不停的切換。這里要保證所有進程版本的一致性
補丁加載:通過反射加載我們合成的dex,so,資源文件等
補丁合成:這些都是在單獨的patch進程中完成的,這里包括dex,so,資源,主要完成補丁包的合成與升級。
監(jiān)聽回調(diào):在合成與加載的過程中,出現(xiàn)問題及時回調(diào)。
1.http://www.itdecent.cn/p/6e09f0766af3 微信熱補丁Tinker -- 補丁流程
2.http://blog.csdn.net/tyk9999tyk/article/details/53391519 ?Tinker 常見問題?
Tinker庫中有什么類是不能修改的?
Tinker庫中不能修改的類一共有25個,即com.tencent.tinker.loader.*類。加上你的Appliction類,只有25個類是無法通過Tinker來修改的
Tinker多補丁版本發(fā)布:
Tinker支持對同一基準版本做多次補丁修復,在生成補丁時,oldApk依然是已經(jīng)發(fā)布出去的那個版本。
即補丁版本二的oldApk不能是補丁版本一,它應該依然是用戶手機上已經(jīng)安裝的基準版本。
Tinker的高級功能:
1.Tinker如何支持多渠道打包
2.如何自定義Tinker行為
3.tinker使用過程中需要注意到的哪些問題
1.多渠道打包
(1)命令行方式智能一個渠道一個渠道的打tpatch
(2)gradle方式則只需簡單的修改一下gradle腳本即可
步驟:
(1).在AndroidManifest.xml中添加?
? ? ? ? android:name="MY_CHANNEL"
? ? ? ? android:value="${MY_CHANNEL}" />
或者其他途徑
(2).在build.gradle中添加productFlavor
productFlavors {//多渠道腳本支持
xiaomi {
? ? manifestPlaceholders = [MY_CHANNEL: "xiaomi"]
? ? buildConfigField "String", "AUTO_TYPE", "\"1\""
}
baidu {
? ? manifestPlaceholders = [MY_CHANNEL: "baidu"]
? ? buildConfigField "String", "AUTO_TYPE", "\"2\""
}
_360 {
? ? manifestPlaceholders = [MY_CHANNEL: "_360"]
? ? buildConfigField "String", "AUTO_TYPE", "\"3\""
}
productFlavors.all {
? ? flavor -> flavor.manifestPlaceholders = [MY_CHANNEL: name]
}
? ? }
(3).修改tinker腳本添加多渠道的目錄tinkerBuildFlavorDirectory = "${bakPath}/tinkergradle-0218-12-22-06"
最重要的是拷貝目錄,完成多渠道目錄的拼湊
2.Tinker的自定義行為
(1)自定義PatchListener監(jiān)聽patch receiver事件 ,繼承于 DefaultPatchListener
? ?作用:DefaultPatchListener implements PatchListener
? ?而PatchListener中只有onPatchReceived(String path)方法,因此自定義的必須得實現(xiàn)
? ?在該方法中調(diào)用int returnCode = patchCheck(path,patchMd5);當returnCode==0校驗通過,就會調(diào)用
? ?TinkerPatchService.runPatchService(context, path);開啟一個新的后臺進程進行補丁合成
? ?校驗patch文件是否合法;啟動service去安裝patch文件
(2)自定義TinkerResultService改變patch安裝成功后的行為,繼承于DefaultTinkerResultService extends AbstractResultService extends IntentService
? ?作用:決定在patch安裝完成之后的后續(xù)操作,默認實現(xiàn)是殺死進程
? ?重寫onPatchResult(PatchResult result)方法,刪除殺死進程的方法,提高用戶體驗
Tinker自定義初始化個參數(shù):
LoadReporter loadReporter = new DefaultLoadReporter(getApplicationContext());//patch加載階段各種異常情況的監(jiān)聽回調(diào)
1.回調(diào)運行在加載的進程,它有可能是各個不一樣的進程。我們可以通過tinker.isMainProcess或者tinker.isPatchProcess知道當前是否是主進程,patch補丁合成進程。
2.回調(diào)發(fā)生的時機是我們調(diào)用installTinker之后,某些進程可能并不需要installTinker
PatchReporter patchReporter = new DefaultPatchReporter(getApplicationContext());//patch文件合成階段各種異常情況的監(jiān)聽回調(diào)
isUpgrade:區(qū)分補丁合成的類型。是由于文件丟失而發(fā)起的RepariPatch, 還是收到新的補丁而發(fā)起的UpgradePatch
patchListener = new CustomPatchListener(getApplicationContext());//反饋
PatchListener類是用來過濾Tinker收到的補丁包的修復、升級請求,也就是決定我們是不是真的要喚起:patch進程去嘗試補丁合成。我們?yōu)槟闾峁┝四J實現(xiàn)DefaultPatchListener.java。
一般來說, 你可以繼承DefaultPatchListener并且加上自己的檢查邏輯,例如SamplePatchListener.java。
若檢查成功,我們會調(diào)用TinkerPatchService.runPatchService喚起:patch進程,去嘗試完成補丁合成操作。反之,會回調(diào)檢驗失敗的接口。事實上,你只需要復寫patchCheck函數(shù)即可。
若檢查失敗,會在LoadReporter的onLoadPatchListenerReceiveFail中回調(diào)。
public int patchCheck(String path, boolean isUpgrade)
AbstractPatch ?abstractPatch ?= new UpgradePatch();
TinkerInstaller.install(mAppLike,
loadReporter,patchReporter,
patchListener,CustomResultService.class,
abstractPatch);//完成tinker初始化
http://blog.csdn.net/tyk9999tyk/article/details/53391525 Tinker 自定義擴展?
Tinker的源碼分析:
Tinker初始化過程中:
使用到了外觀模式TinkerInstaller.install()、構(gòu)建者模式Tinker tinker = new Tinker.Builder()、單例模式public static Tinker with(Context context)
Tinker的patch補丁文件的加載合成過程
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), patch);
最核心的方法就是
TinkerPatchService.runPatchService(context, path);開啟一個新的后臺進程進行補丁合成
TinkerPatchService extends IntentService
最終是在 onHandleIntent(Intent intent)方法中完成patch文件的加載與合成
調(diào)用public abstract boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult);方法
在UpgradePatch extends AbstractPatch的tryPatch()方法中分別調(diào)用
?if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
? ? TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
? ? return false;
}
if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
? ? TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed");
? ? return false;
}
if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
? ? TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed");
? ? return false;
}
完成dex,res,lib加載與合并
必知必會
Tinker兩種集成使用方式
使用Tinker完成對應用的動態(tài)更新
如何自定義patch加載及加載完成后的行為
第七章 代碼及版本發(fā)布管理
1.加入動態(tài)更新之后如何管理我們的代碼分支
2.加入動態(tài)更新之后如何管理我們的發(fā)版節(jié)奏
分支管理(普通)
master分支
dev分支--開發(fā)分支
引入動態(tài)更新之后的分支管理
除了master分支和dev分支,引入hot_fix分支
hot_fix分支專門用來管理動態(tài)更新迭代
sourceTree git代碼界面化管理軟件
第八章 插件化講解
1.插件化相關知識介紹
2.插件化原理與實踐
1.插件化相關知識介紹
插件化產(chǎn)生的背景:
(1)app的體積原來越龐大,功能模塊越來越多
(2)模塊耦合度高,協(xié)同開發(fā)溝通成本極大
(3)方法數(shù)可能會超過65535,占用內(nèi)存過大
(4)為了平行高效開發(fā)。
插件化的優(yōu)點:
1)模塊解耦,應用程序擴展性強
2)解除單個dex函數(shù)不能超過 65535的限制
3)動態(tài)升級,下載更新節(jié)省流量
4)高效開發(fā)(編譯速度更快)
基于以上問題 如何解決?
1.將一個大的APK按照業(yè)務分割成多個小的APK
2.每一個小的APK即可以獨立運行又可以作為插件運行
基于Android動態(tài)加載技術(shù)以上是可以實現(xiàn)的
插件化帶來的優(yōu)勢:
業(yè)務模塊基本完全解耦
高效并行開發(fā)(編譯速度更快)
按需加載,內(nèi)存占用更低等等
插件化的相關概念
宿主:主APP,可以加載插件,也稱之為Host
插件:插件APP,被宿主加載的App,可以是與普通app一樣的apk
插件化:將一個應用按照宿主插件的方式改造就叫做插件化
相關概念對比:
(與組件化對比)
組件化是一種編程思想,而插件化是一種技術(shù)
組件化是為了代碼的高度復用性而出現(xiàn)的
插件化是為了解決應用越來越大而出現(xiàn)的
(與動態(tài)更新對比)
與動態(tài)更新一樣,都是動態(tài)加載技術(shù)的應用
動態(tài)更新是為了解決線上bug或者小功能的更新而出現(xiàn)
插件化是為了解決應用越來越龐大而出現(xiàn)的
http://www.itdecent.cn/p/1c5afe686d75 Android組件化框架設計與實踐
https://segmentfault.com/a/1190000009577849 使用ARouter實現(xiàn)組件化
http://www.itdecent.cn/p/c0eecbbf1481 談談App的統(tǒng)一跳轉(zhuǎn)和ARouter
http://www.itdecent.cn/p/7cb2cc9b726a [Alibaba-ARouter] 簡單好用的Android頁面路由框架
http://blog.csdn.net/zhaoyanjun6/article/details/76165252 Android 路由框架ARouter最佳實踐 ??
組件化產(chǎn)生的背景:
1.代碼量膨脹,不利于維護,不利于新功能的開發(fā)及測試
2.項目工程的構(gòu)建速度慢
3.代碼之間的耦合嚴重
4.做不到按需加載打包
組件化的優(yōu)勢:
代碼簡潔,冗余量少,維護方便,易擴展新功能。
提高編譯速度,從而提高并行開發(fā)效率。
避免模塊之間的交叉依賴,做到低耦合、高內(nèi)聚。
引用的第三方庫代碼統(tǒng)一管理,避免版本統(tǒng)一,減少引入冗余庫。
定制項目可按需加載,組件之間可以靈活組建,快速生成不同類型的定制產(chǎn)品。
制定相應的組件開發(fā)規(guī)范,可促成代碼風格規(guī)范,寫法統(tǒng)一。
系統(tǒng)級的控制力度細化到組件級的控制力度,復雜系統(tǒng)構(gòu)建變成組件構(gòu)建。
每個組件有自己獨立的版本,可以獨立編譯、測試、打包和部署。
組件化與模塊化的區(qū)別?
1.模塊化關注點是功能,組件化的關注點是復用性,更加注重分離。
2.通常一個模塊化包含幾個組件
3.兩者劃分的粒度不一樣,組件化粒度更小,組件化更加側(cè)重于單一功能的內(nèi)聚,偏向于解耦
組件化思路:首先就是解耦,形式就是每一個Module自己能夠運行
組件分為兩類:技術(shù)組件和業(yè)務組件
技術(shù)組件:合理封裝,避免技術(shù)組件庫過大;
業(yè)務組件:選擇性依賴技術(shù)組件可以使得業(yè)務組件單獨運行
組件化實施過程:
1.對于技術(shù)組件需要合理封裝,減少之后可能存在的替換成本;
2.同時注意,將技術(shù)組件分為常用和非常用,可以選擇自己需要的技術(shù)組件,避免一個統(tǒng)一的技術(shù)組件庫過大;
3.將業(yè)務代碼根據(jù)模塊進行剝離,剝離成一個個的小模塊;
4.單獨的業(yè)務模塊加上必要的技術(shù)組件,支撐在開發(fā)階段的獨立運行;
組件化難點:
1.技術(shù)組件的整理、抽離
2.一定要有的DisPatcher:提供隱式的跳轉(zhuǎn)和模塊間方法的調(diào)用能力;
3.組件的劃分
4.調(diào)試及集成方式
組件化實戰(zhàn):構(gòu)建中間層,只讓組件對中間層單方面耦合,中間層不對其他模塊發(fā)聲耦合。
組件之間的通信問題:
通過接口+實現(xiàn)的結(jié)構(gòu)進行組件間的通信。即:每個組件聲明自己提供服務的 Service API,這些Service都是一些接口,
組件負責將這些Service 接口實現(xiàn)并注冊到一個統(tǒng)一的路由Router中去。因此,如果要使用某個組件的功能,只需要向Router請求這個Service的實現(xiàn)。
在組件化架構(gòu)設計圖中 Common 組件就包含了路由服務組件,里面包括了每個組件的路由入口和跳轉(zhuǎn)。
組件化的概念就類似于Binder通信
組件化下的UI跳轉(zhuǎn)問題:
UI跳轉(zhuǎn)也是組件通信的一種,一般通過短鏈的方式進行,跳轉(zhuǎn)到具體的Activity。每個組件注冊自己所能處理的短鏈Scheme和Host,并定義傳輸數(shù)據(jù)的格式,
然后注冊到統(tǒng)一的 UIRouter 中,UIRouter 通過 Scheme 和 Host 的匹配關系負責分發(fā)路由。但目前比較主流的做法是通過在每個 Activity 上添加注解,
然后通過 APT 形成具體的邏輯代碼。目前方式是引用阿里的 ARouter 框架,通過注解方式進行頁面跳轉(zhuǎn)。
動態(tài)加載技術(shù):
動態(tài)更新/熱更新/熱修復
插件化
封裝、設計模式----組件化
插件化相關原理講解
相關知識:
android ClassLoader加載class文件原理
Java反射原理
android資源加載原理
四大組件加載原理
Gradle打包原理
一、插件化對Manifest清單文件的處理
宿主 ManiFest/Aar Manifest/Bundle Manifest....------(合并Merge APK Manifest)---->(解析Bundle BundleInfoList)
流程:
1.構(gòu)建期進行全量Merge操作
2.Bundle的依賴單獨Merge,生成Bundle的Merge manifest
3.解析各個Bundle的Merge Manifest,得到整包的BundleInfoList
二、插件化框架對插件類的加載
插件化框架為插件類提供其加載所需的ClassLoader,用來完成插件類的加載
插件化框架提供ClassLoader進行插件類加載,涉及到兩個問題:
1.如何定義ClassLoader加載類文件,繼承DexClassLoader
2.如何調(diào)用插件APK文件中的類
即:創(chuàng)建了ClassLoader并且能夠加載插件中的類,才能算得上是有用的ClassLoader
三、資源加載
1.若資源只有文件名File Name,則需要AssetManager通過文件名直接加載對應的資源。
2.若資源有對應的Resource ID(譬如:圖片、動畫、字符串等是會生成Resource ID的)因此需要使用到
Resources這個類完成對應資源文件的加載,加載完之后再使用AssetManager完成對資源文件的讀寫
以上可以得知:要想完成資源的加載需要用到Resources與AssetManager兩個類
而安裝到系統(tǒng)中的APK通過Context就可以完成對這兩個類的直接引用,從而完成對資源的加載
而插件類的資源文件,因為插件并沒有安裝,因此這些插件類就沒有自己的資源相關的Resources與AssetManager,因此這兩個類就需要插件化框架幫我們完成動態(tài)的去創(chuàng)建
當前流行的插件化框架加載資源文件,是為每一個Bundle(插件)分別創(chuàng)建一個AssetManager完成對應插件的加載。
在插件被調(diào)用的時候?qū)⒉寮腞es,So等的資源文件目錄路徑,通過反射的方式加入到AssetManager,這樣我們的AssetManager就知道了插件res,so資源文件存放路徑,因此
插件化對資源加載的核心原理是:
想辦法為每一個插件創(chuàng)建對應的AssetManager,加載插件中的資源路徑
插件化的核心技術(shù):
1.處理所有插件Apk文件中的Manifest文件(Bundle Manifest 合并到宿主Manifest)
2.管理宿主apk中所有的插件apk信息(因為不止有一個插件)
3.為每一個插件apk創(chuàng)建對應的類加載器,資源管理器,以完成對插件資源的加載
第九章 插件化框架實戰(zhàn)
市面上現(xiàn)有的插件化框架介紹
具體使用一種框架完成插件化改造
市面上插件化框架
360手機助手的DroidPlugin框架
百度的dynamic-load-apk框架
個人開發(fā)者林光亮的Small框架
alibaba開源的Atlas框架
使用Small對項目進行改造
集成:
1.按照規(guī)則創(chuàng)建對應的project
2.在創(chuàng)建好的project build.gradle中集成編譯插件gradle-Small
文件末尾引用gradle-small插件:apply plugin: 'net.wequick.small'
用gradlew small 驗證集成是否正確>
3.在工程的宿主module中初始化Smal在自定義Application中 Small.preSetUp(this);
插件創(chuàng)建階段
一、以指定的規(guī)范來創(chuàng)建插件
Module name 以 app.* 命名的模塊將被 Small 在 編譯時 識別為應用插件模塊。
Package name 以 app* 結(jié)尾的插件將被 Small 在 運行時 識別為應用插件
二、編譯創(chuàng)建好的插件
(1)先編譯公共庫 gradlew buildlib -q (宿主是一個最基礎的公共庫)
(2)再編譯app.main插件 gradlew buildBundle -q Dbundle.arch=arm
(3)查看編譯完成的具體情況 ?gradlew small
至此,我們已經(jīng)完成插件編譯并將之內(nèi)置到宿主包中去了
三、通過宿主應用啟動插件應用
(1)新建assets目錄,在該文件夾下創(chuàng)建路由配置文件 bundle.json;
(2)修改bundle.json,添加路由:
{
? "version": "1.0.0",
? "bundles": [
? ? {
? ? ? "uri": "main",
? ? ? "pkg": "com.example.appmain"
? ? }
? ]
}
這里的:
version,是 bundle.json 文件格式版本,目前始終為 1.0.0
bundles,插件數(shù)組
uri,插件唯一ID
pkg,插件包名
通過這個配置,main 將被路由向 com.example.mysmall.appmain#MainActivity
(3)回到宿主的 app > java > com.example.mysmall > MainActivity,在 onStart 方法中我們通過上述配置的 uri 來啟動 app.main 插件:
@Override
protected void onStart() {
? ? super.onStart();
? ? Small.setUp(this, new Small.OnCompleteListener() {
? ? ? ? @Override
? ? ? ? public void onComplete() {
? ? ? ? ? ? Small.openUri("main", MainActivity.this);
? ? ? ? }
? ? });
}
(4)運行宿主
需要掌握的small集成此基礎:
通過 build.gradle 集成 Small
通過 自定義Application 初始化 Small
通過 buildLib,buildBundle 編譯 Small 插件
通過 bundle.json 配置插件路由
通過 Small.openUri 啟動插件
業(yè)務類插件/公共庫插件創(chuàng)建
(1)創(chuàng)建公共庫插件模塊
Lib.style
Module name: lib.style
Package name: com.example.libstyle
(2)添加公共庫引用
修改 app.main/build.gradle,增加對 lib.style 的依賴:
dependencies {
? ? ...
? ? compile project(':lib.style')
}
(3)添加插件路由
修改 app/assets/bundle.json:
{
? "pkg": "com.example.libstyle"
}
(4)重新編譯插件
清除公共庫:
./gradlew cleanLib -q
編譯公共庫:
./gradlew buildLib -q -Dbundle.arch=x86
編譯業(yè)務單元:
./gradlew buildBundle -q -Dbundle.arch=x86
(5)重新運行程序
Small進階教程
公共庫插件模塊
公共庫插件模塊在 開發(fā)時 可以通過 compile project(':插件模塊名')來被 應用插件模塊 所引用。
同時在 編譯時 (buildLib) 會被打包成為一個可獨立更新的插件。
定義公共庫插件模塊有兩種方式:
指定 Module name 為 lib.*
在 Small DSL 中顯式指明 bundles lib your_module_name
要正確讀取到打包的公共庫插件也有兩種方式:
指定 Package name 為 **.lib.* 或 **.lib*
在 bundle.json 中添加描述 "type": "lib"
應用插件模塊
應用插件模塊在 開發(fā)時 可以獨立運行。
同時在 編譯時 (buildBundle 或 :模塊:aR ) 會被打包成一個可獨立更新的插件。
定義應用插件模塊有兩種方式:
指定 Module name 為 app.*
在 Small DSL 中顯式指明 bundles app your_module_name
要正確讀取到打包的公共庫插件也有兩種方式:
指定 Package name 為 **.app.* 或 **.app*
在 bundle.json 中添加描述 "type": "app"
自定義資源ID分段
在整合插件資源的過程,為避免資源ID沖突,需要為每個插件分配一個ID段。
我們知道默認程序的ID段為 0x7f。由于系統(tǒng)使用了 0x00,0x01,0x02。因此插件允許的范圍在 [0x03, 0x7e] 之間。
但是有些手機廠商占用了一些分段,黑名單如下:
ID 廠商
0x03 HTC
0x10 小米
為此,Small 根據(jù)哈希算法將插件的模塊名散列到 [0x11, 0x7e] 區(qū)間,作為自動分配的插件資源ID段,比如:
模塊名 ID
app.main 0x77
lib.style 0x79
但是,這個算法生成的ID有可能是重復的,所以必要時你可以通過以下方法自定義插件的資源ID。
修改 build.gradle
在插件模塊所在的 build.gradle 腳本里,增加配置:
ext {
? ? packageId = 0x12
}
這里的 0x12 是16進制整型值
將把當前插件的資源ID段分配為 0x12。
或修改 gradle.properties
在插件模塊所在的 gradle.properties 配置里,增加配置:
packageId=3f
properties只接收字符串值
將把當前插件的資源ID段分配為 0x3f。
http://code.wequick.net/Small/cn/home Small開發(fā)文檔
必知必會
Samll的基本用法:集成、插件化生成、宿主配置
將已有項目改造成插件化架構(gòu)
Samll的進階知識,做到對Samll有一個全面的了解
Atlas框架講解
重量級框架 非常復雜
Atlas的基本概念和作用
了解Atlas的整體結(jié)構(gòu)和原理
Alibaba獨立開發(fā)并開源的一種插件化技術(shù)方案,也叫動態(tài)組件化(Dynamic Bundle)框架
目前手機淘寶和優(yōu)酷使用的是這種技術(shù)方案
功能強大但是使用特別復雜,適用于門戶型的App,它主要提供了解耦化、組件化、動態(tài)性的支持
核心原理與Samll是完全一樣的
與插件化框架不同的是,Atlas是一個組件框架,Atlas不是一個多進程的框架,他主要完成的就是在運行環(huán)境中按需地去完成各個bundle的安裝,加載類和資源
Atlas的整體結(jié)構(gòu)和原理
https://alibaba.github.io/atlas/principle-intro/Runtime_principle.html
框架層次:
包含四個層次
1.最下面的一層:hack工具層,主要是進行hack工具類的初始化和校驗工作
2.Bundle Framework 負責bundle的安裝更新以及管理整個bundle的生命周期
3.runtime層:主要包括清單管理、版本管理、系統(tǒng)代理三大模塊,基于不同的觸發(fā)點
按需執(zhí)行bundle的安裝和加載。從delegate層可以看到,最核心的兩個代理點:
一個是DelegateClasssLoader,負責路由由class加載到各個bundle內(nèi)部,
一個是DelegateResource:負責資源查找時能夠找到bundle內(nèi)的資源,這是bundle能夠真正運行起來的的根本點。
4.對外接入層:AtlasBridgeApplication是atlas框架下apk的真正Application,在基于Atlas框架構(gòu)建的過程中會替換原有manifest中的application,AtlasBridgeApplication里面除了完成了Atlas的初始化功能,同時內(nèi)置了multidex的功能,這樣做的原因有兩個:
很多大型的app不合理的初始化導致用multidex分包邏輯拆分的時候主dex的代碼就有可能方法數(shù)超過65536,AtlasBridgeApplication與業(yè)務代碼完全解耦,所以拆分上面只要保證atlas框架在主dex,其他代碼無論怎么拆分都不會有問題;
如果不替換Application,那么atlas的初始化就會在application里面,由于基于Atlas的動態(tài)部署實際上是類替換的機制,那么這種機制就會必然存在包括Application及其import的class等部分代碼在dalvik不支持部署的情況,這個在使用過程中造成一定成本,需要小心的使用以避免dalivk內(nèi)部class resolve機制導致部分class沒成功,替換以后該問題得到最好的解決,除atlas本身以外,所有業(yè)務代碼均可以動態(tài)部署
Bundle的生命周期:
Installed bundle被安裝到storage目錄
Resolved classloader被創(chuàng)建,assetpatch注入DelegateResoucces
Active bundle的安全校驗通過;bundle的dex檢測已經(jīng)成功dexopt(or dex2oat),resource已經(jīng)成功注入
Started bundle開始運行,bundle application的onCreate方法被調(diào)用
每一層都為上一層次提供服務
類加載機制
Atlas里面通常會創(chuàng)建了兩種classLoader,第一個是DelegateClassLoader,他作為類查找的一個路由器而存在,本身并不負責真正類的加載;DelegateClassLoader啟動時被atlas注入LoadedApk中,替換原有的PathClassLoader;第二個是BundleClassLoader,參考OSGI的實現(xiàn),每個bundle resolve時會分配一個BundleClassLoader,負責該bundle的類加載。關系如下圖所示: DelegateClassLoader以PathClassLoader為parent,同時在路由過程中能夠找到所有bundle的classloader;
BundleClassLoader以BootClassLoader為parent,同時引用PathClassLoader,BundleClassLoader自身findClass的順序為
1. findOwn: 查找bundle dex 自身內(nèi)部的class
2. findDependency: 查找bundle依賴的bundle內(nèi)的class
3. findPath: 查找主apk中的class
Bundle的Activity啟動的類加載過程來幫助理解:
ActivityThread從LoadedApk中獲取classloader去load Activity Class;
根據(jù)上面的classloader關系,先去parent里面加載class;
由于class在bundle里面,所以pathclassloader內(nèi)查找失敗,接著delegateclassloader根據(jù)bundleinfo信息查找到classloader在bundle中(假設為bundleA);
從bundleA中加載class,并且創(chuàng)建class;
后面在Activity起來后,如果bundleA對bundleB有依賴關系,那么如果用到了bundleB的class,又會根據(jù)bundlA的bundleClassloader的dependency去獲取bundleB的classloader去加載;?
資源加載機制:
類似ClassLoader,LoadedApk中的Resources被替換成Atlas內(nèi)部的DelegateResources,同時在每個Bundle安裝的過程中,每個bundle的assetspath會被更新到DelegateResources的AssetsManager中;每個bundle的資源特征如圖可知:
bundle構(gòu)建過程中,每個bundle會被獨立進行分區(qū),packageId保證全局唯一,packageID在host的構(gòu)建工程內(nèi)會有個packageIdFile.properties進行統(tǒng)一分配;
雖然每個bundle的manifest都聲明了自己的packagename,但是在aapt過程中,arsc文件里面所有bundle的packagename均被統(tǒng)一為hostApk的package,比如在手淘內(nèi)就都是com.taobao.taobao;這樣改的目的是為了解決在資源查找中一些兼容性問題;
名詞解釋:
Bundle
awb
host
remote bundle
動態(tài)部署
Bundle: 類似OSGI規(guī)范里面bundle(組件)的概念,每個bundle有自己的classloader,與其他bundle相隔離,同時Atlas框架下bundle有自身的資源段(PackageID,打包時AAPT指定);另外與原有OSGI所定義的service格式不同之處是Atlas里面Bundle透出所有定義在Manifest里面的component,隨著service,activity的觸發(fā)執(zhí)行bundle的安裝,運行。
awb: android wireless bundle的縮寫,實際上同AAR類似,是最終構(gòu)建整包前的中間產(chǎn)物。每個awb最終會打成一個bundle。awb與aar的唯一不同之處是awb與之對應有個packageId的定義。
host: 宿主的概念,所有的bundle可以直接調(diào)用host內(nèi)的代碼和資源,所以host常常集合了公共的中間件,UI資源等
基于Atlas構(gòu)建后大致工程的結(jié)構(gòu):
首先有個構(gòu)建整體APK工程Apk_builder,里面管理著所有的依賴(包括atlas)及其版本,Apk_builder本身可能不包含任何代碼,只負責構(gòu)建使用
host內(nèi)部包含獨立的中間件,以及一個Base的工程,里面可能包含應用的Application,應用icon等基礎性內(nèi)容(如果足夠獨立,application也可以直接放在apk_builder內(nèi));
業(yè)務層基本上以bundle為邊界自上而下與host發(fā)生調(diào)用,同時bundle之間允許存在依賴關系;相對業(yè)務獨立的bundle如果存在接口耦合建議封裝成aidl service的方式保證自身封裝性;同時某些中間件如果只存在若干bundle使用的也可以封裝bundle的方式提供出來,以保證host內(nèi)容精簡
remote bundle: 遠程bundle,遠程bundle只是apk構(gòu)建時并未打到apk內(nèi)部,而是單獨放在了云端;同時遠程bundle的限制條件是第一次被觸發(fā)的前提是bundle內(nèi)的Activity需要被start,此時基于Atlas內(nèi)的ClassNotFoundInterceptorCallback可以進行跳轉(zhuǎn)的重定向,提示用戶下載具體bundle,待用戶確定后進行異步下載同時完成后再跳轉(zhuǎn)到目標bundle(此部分代碼由于涉及下載及UI展示等內(nèi)容并未包含在開源倉庫中,有需要可以根據(jù)ClassNotFoundInterceptorCallback自行實現(xiàn))
動態(tài)部署: 基于Atlas的installorUpdate和atlas-update庫及構(gòu)建插件,可以生成與之前發(fā)布的apk diff生成的差異文件,在更新時拉取同時靜默更新到設備上,在用戶下次啟動之后生效新代碼,具體原理可以參考動態(tài)部署章節(jié)的解析
APK結(jié)構(gòu):
基于Atlas構(gòu)建后的APK結(jié)構(gòu)如下圖,host與普通Apk無異,但是Manifest和assets會添加一些額外的內(nèi)容,同時在armeabi目錄中會增加若干個bundle構(gòu)建的產(chǎn)物,取名為String.format(lib%s.so,packagename.replace(".","_"));packagename為bundle的AndroidManifest中的packagename,這些so都是正常的apk結(jié)構(gòu),改為so放入lib目錄只是為了安裝時借用系統(tǒng)的能力從apk中解壓出來,方便后續(xù)安裝
assets/bundleinfo-version.json
構(gòu)建完的apk在host的assets目錄下,會有個bundleinfo-verison.json的文件,其中version為manifest中的versinonname,里面記錄了每個bundle大小,版本,名字以及里面所有的component信息,這些內(nèi)容在構(gòu)建的時候生成,基于這些信息每個bundle可以在component被觸發(fā)的時候去按需的進行安裝,整個過程對開發(fā)者透明(從中也可以看到默認情況下bundle對外暴露的只是基于Android原生的Activity,service,receiver等component)
AndroidManifest
運行期文件結(jié)構(gòu)
/data/data/pkgname/files/bundlelisting
之前打包構(gòu)建時記錄的bundleinfo信息(發(fā)生動態(tài)部署收文件會進行更新)
/data/data/pkgname/files/baselineinfo
存放動態(tài)部署后的版本變化內(nèi)容,以及每次部署發(fā)生更新的bundle的版本,依賴等信息
/data/data/pkgname/files/storage
storage目錄是bundle安裝的目錄,每個bundle的安裝目錄以bundle的packagename為文件夾名,首次啟動后會安裝到version.1目錄下,目錄中可能含有bundle的zip文件,dex文件以及native so等內(nèi)容。如果bundle發(fā)生更新,則可能會有version.2、version.3 等目錄,每次加載bundle的時候選取最高可用版本進行載入??紤]bundle的回滾功能和對空間占用的影響,目前容器內(nèi)最多保留兩個最近可用版本。