很早之前就想深入的研究和學習一下熱修復狭吼,由于時間的原因一直拖著稠通,現(xiàn)在才執(zhí)筆弄起來衬衬。
Android而更新系列:
Android熱更新一:JAVA的類加載機制
Android熱更新二:理解Java反射
Android熱更新三:Android類加載機制
Android熱更新四:熱修復機制
Android熱更新五:四大熱修復方案分析
Android熱更新六:Qzone熱更新原理
Android熱更新七:Tinker熱更新原理
Android熱更新八:AndFix熱更新原理
Android熱更新九:Robust熱更新原理
Android熱更新十:自己寫一個Android熱修復
既然本次主題是熱修復买猖,在深入研究之前,要先搞清楚它涉及到的關(guān)鍵技術(shù)
類加載機制
Java反射
正所謂磨刀不誤砍柴工滋尉,我們這個筆記將對JAVA類加載機制進行初步學習和理解玉控。
一. 類加載機制
我們先了解類在JVM(Java虛擬機)中是如何加載的,這對后面理解java其它機制將有重要作用狮惜。
每個類編譯后產(chǎn)生一個Class對象奸远,存儲在.class文件中,JVM使用類加載器(Class Loader)來加載類的字節(jié)碼文件(.class)讽挟,類加載器實質(zhì)上是一條類加載器鏈懒叛,一般的,我們只會用到一個原生的類加載器耽梅,它只加載Java API等可信類薛窥,通常只是在本地磁盤中加載,這些類一般就夠我們使用了眼姐。如果我們需要從遠程網(wǎng)絡(luò)或數(shù)據(jù)庫中下載.class字節(jié)碼文件诅迷,那就需要我們來掛載額外的類加載器。
一般來說众旗,類加載器是按照樹形的層次結(jié)構(gòu)組織的罢杉,每個加載器都有一個父類加載器。另外贡歧,每個類加載器都支持代理模式滩租,即可以自己完成Java類的加載工作,也可以代理給其它類加載器利朵。
類加載器的加載順序有兩種:
- 父類優(yōu)先策略是比較一般的情況(如JDK采用的就是這種方式)律想,在這種策略下,類在加載某個Java類之前绍弟,會嘗試代理給其父類加載器技即,只有當父類加載器找不到時,才嘗試自己去加載樟遣。
- 自己優(yōu)先的策略與父類優(yōu)先相反而叼,它會首先嘗試子自己加載,找不到的時候才要父類加載器去加載豹悬,這種在web容器(如tomcat)中比較常見葵陵。
二. 類的加載和初始化
需要區(qū)分加載和初始化的區(qū)別,加載了一個類的.class文件屿衅,不意味著該Class對象被初始化埃难,事實上,一個類的初始化包括3個步驟:
- 加載(Loading),由類加載器執(zhí)行涡尘,查找字節(jié)碼忍弛,并創(chuàng)建一個Class對象(只是創(chuàng)建);
- 鏈接(Linking)考抄,驗證字節(jié)碼细疚,為靜態(tài)域分配存儲空間(只是分配,并不初始化該存儲空間)川梅,解析該類創(chuàng)建所需要的對其它類的應用疯兼;
- 初始化(Initialization),首先執(zhí)行靜態(tài)初始化塊static{}贫途,初始化靜態(tài)變量吧彪,執(zhí)行靜態(tài)方法(如構(gòu)造方法)。
1. 動態(tài)加載
不管使用什么樣的類加載器丢早,類姨裸,都是在第一次被用到時,動態(tài)加載到JVM的怨酝。這句話有兩層含義:
- Java程序在運行時并不一定被完整加載傀缩,只有當發(fā)現(xiàn)該類還沒有加載時,才去本地或遠程查找類的.class文件并驗證和加載农猬;
- 當程序創(chuàng)建了第一個對類的靜態(tài)成員的引用(如類的靜態(tài)變量赡艰、靜態(tài)方法、構(gòu)造方法——構(gòu)造方法也是靜態(tài)的)時斤葱,才會加載該類慷垮。Java的這個特性叫做:動態(tài)加載。
2. 鏈接
Java在加載了類之后苦掘,需要進行鏈接的步驟换帜,鏈接簡單地說,就是將已經(jīng)加載的java二進制代碼組合到JVM運行狀態(tài)中去鹤啡。它包括3個步驟:
- 驗證(Verification),驗證是保證二進制字節(jié)碼在結(jié)構(gòu)上的正確性蹲嚣,具體來說递瑰,工作包括檢測類型正確性,接入屬性正確性(public隙畜、private)抖部,檢查final class 沒有被繼承,檢查靜態(tài)變量的正確性等议惰。
- 準備(Preparation)慎颗,準備階段主要是創(chuàng)建靜態(tài)域,分配空間,給這些域設(shè)默認值俯萎,需要注意的是兩點:一個是在準備階段不會執(zhí)行任何代碼傲宜,僅僅是設(shè)置默認值,二個是這些默認值是這樣分配的夫啊,原生類型全部設(shè)為0函卒,如:float:0f,int 0, long 0L, boolean:0(布爾類型也是0),其它引用類型為null撇眯。
- 解析(Resolution)报嵌,解析的過程就是對類中的接口、類熊榛、方法锚国、變量的符號引用進行解析并定位,解析成直接引用(符號引用就是編碼是用字符串表示某個變量玄坦、接口的位置血筑,直接引用就是根據(jù)符號引用翻譯出來的地址),并保證這些類被正確的找到营搅。解析的過程可能導致其它的類被加載云挟。需要注意的是,根據(jù)不同的解析策略转质,這一步不一定是必須的园欣,有些解析策略在解析時遞歸的把所有引用解析,這是early resolution休蟹,要求所有引用都必須存在沸枯;還有一種策略是late resolution,這也是Oracle 的JDK所采取的策略赂弓,即在類只是被引用了绑榴,還沒有被真正用到時,并不進行解析盈魁,只有當真正用到了翔怎,才去加載和解析這個類。
3. 初始化
根據(jù)java虛擬機規(guī)范杨耙,所有java虛擬機實現(xiàn)必須在每個類或接口被java程序首次主動使用時才初始化赤套。
主動使用有以下6種:
- 創(chuàng)建類的實例
- 訪問某個類或者接口的靜態(tài)變量,或者對該靜態(tài)變量賦值(如果訪問靜態(tài)編譯時常量(即編譯時可以確定值的常量)不會導致類的初始化)
- 調(diào)用類的靜態(tài)方法
- 反射(Class.forName(xxx.xxx.xxx))
- 初始化一個類的子類(相當于對父類的主動使用)珊膜,不過直接通過子類引用父類元素容握,不會引起子類的初始化
- Java虛擬機被標明為啟動類的類(包含main方法的)
類與接口的初始化不同,如果一個類被初始化车柠,則其父類或父接口也會被初始化剔氏,但如果一個接口初始化塑猖,則不會引起其父接口的初始化。