熱修復/插件化/組件化-Andfix/Tinker源碼簡單解讀及相關知識剖析筆記

一、知識詳解模塊

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)最多保留兩個最近可用版本。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容