網(wǎng)站首頁 編程語言 正文
一、Java中的類加載器
首先花點(diǎn)時(shí)間回顧一下Java中的三種類加載器:
- BootStrap ClassLoader 啟動(dòng)類加載器,它是實(shí)現(xiàn)自C/C++的類加載器,用于加載JDK的核心類庫,例如
java.lang
、java.util
等系統(tǒng)類。JVM的啟動(dòng)需要通過BootStrap ClassLoader來創(chuàng)建一個(gè)初始類來完成。 - Extensions ClassLoader 擴(kuò)展類加載器,ExtClassLoader,它用于加載一些系統(tǒng)之外的額外功能。
- Application ClassLoader 應(yīng)用類加載器,也稱作系統(tǒng)類加載器,它可以通過getSystemClassLoader拿到,也可以稱為系統(tǒng)類加載器。
- Custom ClassLoader 自定義類加載器,我們可以通過繼承
java.lang.ClassLoader類
來實(shí)現(xiàn)自己的類加載器,注意,ExtClassLoader和AppClassLoader也是繼承自java.lang.ClassLoader類
的。
總體的加載關(guān)系是:null->ExtClassloader->AppClassLoader,需要注意的是,BootStrap ClassLoader無法在Java中通過.class.getClassLoader()訪問它的父加載器。
同時(shí),這只代表加載關(guān)系,而不代表繼承關(guān)系,ClassLoader的繼承關(guān)系如下:
其中:
- ClassLoader本身是一個(gè)抽象類,定義了主要的一些功能。
- SecureClassLoader擴(kuò)展實(shí)現(xiàn)了ClassLoader方面加入權(quán)限方面的功能,加強(qiáng)了ClassLoader的安全性。
- URLClassLoader通過URL路徑,從Jar文件和文件夾下加載類和資源。
- ExtClassLoader和AppClassLoader,本身都是Launcher應(yīng)用的內(nèi)部類,Launcher是Java虛擬機(jī)的入口應(yīng)用,二者都在Launcher中初始化;
雙親委派機(jī)制,大致意思就是當(dāng)一個(gè)類加載器去加載某個(gè)類時(shí),會(huì)優(yōu)先委托給父加載器去進(jìn)行加載,而不是自己加載。這樣能夠有效地保護(hù)加載類的安全性,比如我們希望加載一個(gè)
java.lang.String
類,在雙親委派機(jī)制下,我們就會(huì)優(yōu)先由父類進(jìn)行加載,而不是我們自己的類加載器去做加載,父類加載器會(huì)從指定的,安全的目錄下查找String類。如果是我們自己的類加載器中加載該類那么可能會(huì)出現(xiàn)一些安全方面的問題。換句話說,你自己定義的java.lang.String
類是無法被加載的。當(dāng)然,這個(gè)前提是,你得遵循雙親委派機(jī)制,如果你重新寫個(gè)類加載器,自定義了loadClass并且不遵循雙親委派機(jī)制,那么雙親委派機(jī)制就被打破了。這也是為什么,JVM認(rèn)為兩個(gè)類相等不僅僅要類名相等,而且要類加載器相等才是同一個(gè)類。
二、Android中的類加載器
區(qū)別于標(biāo)準(zhǔn)JVM以Class文件為輸入的字節(jié)碼文件,Android虛擬機(jī)采用更為緊湊的Dex文件作為輸入文件,無論是Dalvik VM還是ART VM。這樣一來,類加載器自然也會(huì)有所改變。
Android中的類加載器類型被分為兩類:
- 系統(tǒng)類加載器:PathClassLoader、DexClassLoader、BootClassLoader
- 自定義類加載器
2.1 BootClassLoader
Android 系統(tǒng)啟動(dòng)時(shí),會(huì)使用BootClassLoader來加載常用的類,與SDK中的BootStrap ClassLoader不同的是,它本身不是由C/C++實(shí)現(xiàn)的,?而是采用Java實(shí)現(xiàn)的,它是ClassLoader的內(nèi)部類,繼承自ClassLoader,本身是一個(gè)單例類。應(yīng)用中無法直接訪問到BootClassLoader。
BootClassLoader是在ZygoteInit的入口方法中,間接調(diào)用了preloadClasses方法中,進(jìn)行創(chuàng)建的,Android在/framework/base/preloaded-classes
中封裝了一系列的預(yù)加載類的目錄,一些常用類,例如:ContextImpl、Fragment、Dialog等等都在列。預(yù)加載之后,應(yīng)用程序啟動(dòng)時(shí),就不用額外去做加載了。
2.2 PathClassLoader
PathClassLoader它通常被系統(tǒng)用來加載Apk中自帶的Dex文件,它的構(gòu)造函數(shù)中少了一個(gè)參數(shù):optimizedDirectory,這是因?yàn)镻athClassLoader定義了默認(rèn)的optimizedDirectory參數(shù):/data/dalvik-cache/
,因此,我們無法自定義Dex文件的解壓路徑,所以我們加載類時(shí),一般都使用DexClassLoader。
PathClassLoader是Zygote進(jìn)程在fork SystemServer進(jìn)程時(shí)創(chuàng)建的,當(dāng)Zygote進(jìn)程在新創(chuàng)建SystemServer時(shí),通過調(diào)用forkSystemServer方法時(shí),會(huì)調(diào)用到handleSystemServerProcess(),然后調(diào)用createPathClassLoader()去創(chuàng)建PathClassLoader。
2.3 DexClassLoader
可以在磁盤中加載.dex或者是.apk文件,但是本質(zhì)上都是加載屬于Android 的字節(jié)碼文件:Dex文件。
它的構(gòu)造方法有四個(gè)參數(shù):
- dexPath:相關(guān)的文件路徑;支持多個(gè)路徑,使用「:」分割
- optimizedDirectory:Dex文件解壓后的文件存儲(chǔ)路徑,一般情況下使用當(dāng)前文件的私有路徑。
"this parameter is deprecated and has no effect since API level 26."
注意:optimizedDirectory參數(shù)在API26之后被廢棄了
- librarySearchPath:包含C/C++庫的路徑集合,可以為null
- parent:父加載器
我們通常在App啟動(dòng)時(shí),我們通常使用DexClassLoader動(dòng)態(tài)加載Dex的方式來實(shí)現(xiàn)應(yīng)用程序Java代碼層面的熱修復(fù)。
2.4 InMemoryDexClassLoader
Android8.0 中新增的用于加載內(nèi)存中的類加載器。和PathClassLoader、DexClassLoader一樣,都是BaseDexClassLoader的實(shí)現(xiàn)類。
三、Dex文件
3.1 Android內(nèi)存中的Dex文件
BaseDexClassLoader有三個(gè)子類:DexClassLoader、PathClassLoader、InMemoryDexClassLoader,它們?nèi)齻€(gè)主要任務(wù)就是:加載外部的Dex文件,獲取其中定義的類信息。同樣,Android的類加載機(jī)制也遵循雙親委派機(jī)制。
BaseDexClassLoader有一個(gè)特殊的結(jié)構(gòu):DexPathList類型的pathList?,它內(nèi)部維護(hù)了一個(gè)Element類型的數(shù)組,用來存儲(chǔ)被加載的Dex文件信息:
private Element[] dexElements;
每當(dāng)我們要使用一個(gè)類時(shí),類加載器就會(huì)先檢索:DexPathList中的所有Dex文件,逐個(gè)遍歷,看看其中是否含有所需要的類:
Element內(nèi)部有一個(gè)對象:DexFile,在調(diào)用Element#findClass時(shí),會(huì)按照如下規(guī)則去查找:
而最終,loadClassBinaryName會(huì)調(diào)用Native代碼在本地內(nèi)存上創(chuàng)建一個(gè)指向Dex文件的對象,這樣?,我們知道Dex文件在內(nèi)存中的引用是類加載下的DexPathList中的一個(gè)個(gè)Element。
這個(gè)Element數(shù)組可以作為一個(gè)熱修復(fù)的接入點(diǎn),我們知道,類加載只會(huì)被加載一次,如果此時(shí)我們有多個(gè)Dex文件,那么Dex文件的引用在Element中會(huì)按照加載的順序排列,這樣一來,排在前面的Dex類中的Class就會(huì)被優(yōu)先加載,由此我們就可以將熱修復(fù)后重新生成的Patch.dex
或Patch.apk
加載到用戶手機(jī)存儲(chǔ)空間當(dāng)中,然后自行使用DexPathClassLoader進(jìn)行加載,并通過反射,Hook掉PathClassLoader,將Patch.dex
對應(yīng)的Element反射插入到其DexPathList當(dāng)中去。這樣一來,加載時(shí)就會(huì)優(yōu)先從Patch.dex
中加載了,原理大致如下圖。修復(fù)后加載原先出錯(cuò)的類ClassE將會(huì)從Patch.dex中優(yōu)先加載,而出錯(cuò)的Class E由于類加載的特性,將不會(huì)被加載出來。
3.2 Dex文件的生成
我們所編寫的Java代碼,使用Java自帶的編譯器編譯完成之后默認(rèn)的輸出一定是.class文件,而在ART或者Dalvik虛擬機(jī)中需要輸入Dex文件,那么在其中必然存在Class -> Dex文件的過程。
該過程是由d8工具完成的,在我們的SDK目錄下:/Library/Android/sdk/build-tools/28.0.3
,有非常多和我們Android 構(gòu)建相關(guān)的一些工具,例如aapt2工具會(huì)負(fù)責(zé)將res.xml中的文件在R.java中生成對應(yīng)的ID引用、負(fù)責(zé)將二進(jìn)制資源、資源表resources.arsc、classes.dex以及assets集成打包進(jìn)一個(gè)未簽名的.apk文件內(nèi)。
d8工具會(huì)將需要打包的.class文件、額外依賴的Jar文件一同參與編譯。比如我們需要對appt2下的一個(gè)MainActivity.class文件進(jìn)行編譯,那么我們可以在一個(gè)新項(xiàng)目中,點(diǎn)擊Android Studio的編譯或者上面的綠色小錘子,然后在:MyApplication2/app/build/intermediates/javac/debug/classes/com/red/myapplication
?下我看到MainActicity.class文件,然后在該目錄下執(zhí)行如下的指令,注意,將MainActivity的AppcompatActivity換成Activity,因?yàn)榍罢呤菍儆贏ndroidX系列的的AAR包中的依賴,需要額外添加。
d8 aapt/MainActivity.class --lib /Library/Android/sdk/platforms/android-29/android.jar --output ./
其中,--lib
指定了一些額外的依賴,因?yàn)镸ainActivity中會(huì)依賴android.jar
中的一些文件(比如Activity類),完成后,我們就得到了一個(gè)classes.dex文件,結(jié)構(gòu)如下:
如上的步驟都在Android提供的Gradle套件中,幫我們完成了,Gradle插件依賴的本質(zhì),就是插件文件的下載(Gradle同步)和引用。
原文鏈接:https://juejin.cn/post/7061609849292849166
相關(guān)推薦
- 2022-07-09 Python如何通過變量ID得到變量的值_python
- 2023-11-12 ip link set eno2 down后centos無法聯(lián)網(wǎng);centos7.0,二次啟動(dòng)后無法
- 2022-05-25 Spring Security 中的權(quán)限注解很神奇嗎?
- 2022-08-16 R語言繪制維恩圖ggvenn示例詳解_R語言
- 2024-07-15 Redis底層數(shù)據(jù)結(jié)構(gòu)-鏈表
- 2022-11-30 使用jQuery實(shí)現(xiàn)簡單穿梭框方式_jquery
- 2022-04-23 C語言字符串替換空格實(shí)例詳解_C 語言
- 2024-01-27 什么是消息隊(duì)列
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支