JNI原理
暫且跳過(guò)
Java虛擬機(jī)
概述
我們常說(shuō)的JDK(Java Development Kit)包含了Java語(yǔ)言拔恰、Java虛擬機(jī)和Java API類(lèi)庫(kù)這三個(gè)部分阱扬,是Java程序開(kāi)發(fā)的最小環(huán)境恭取。而JRE(Java Runtime Environment)包含了Java API中的Java SE API子集和Java虛擬機(jī)這兩部分资铡,是Java程序運(yùn)行的標(biāo)準(zhǔn)環(huán)境图仓。
Java虛擬機(jī)是一個(gè)家族项炼,實(shí)際上有很多類(lèi)型的Java虛擬機(jī)均蜜,有幾個(gè)主流的:
1.HotSpot VM : Oracle JDK和 OpenJDK中自帶的虛擬機(jī)李剖,是最主流的和使用范圍最廣的Java虛擬機(jī)。一般不做特殊介紹的話兆龙,我們所說(shuō)的虛擬機(jī)就是這一款杖爽,當(dāng)下屬于Oracle公司。
2.J9 VM:IBM開(kāi)發(fā)的虛擬機(jī)紫皇,目前是其主力發(fā)展的Java虛擬機(jī)慰安。
3.Zing VM :以O(shè)racle的HotSpot VM為基礎(chǔ),改進(jìn)了許多影響延遲的細(xì)節(jié)聪铺。
Android中的Dalvik和ART虛擬機(jī)并不屬于Java虛擬機(jī)
Java虛擬機(jī)執(zhí)行流程
如上圖化焕,java虛擬機(jī)執(zhí)行流程分為兩大部分:
①編譯時(shí)環(huán)境
②運(yùn)行時(shí)環(huán)境
當(dāng)一個(gè)Java編譯器編譯后會(huì)生成Class文件。Java虛擬機(jī)與Java語(yǔ)言并沒(méi)有必然聯(lián)系铃剔,它只與特定的二進(jìn)制文件:Class文件相關(guān)撒桨。因此無(wú)論任何語(yǔ)言只要能夠編譯成Class文件,就可以被Java虛擬機(jī)識(shí)別并執(zhí)行键兜。
比如Java凤类、Kotlin普气、Groovy等。
Java虛擬機(jī)結(jié)構(gòu)
Java虛擬機(jī)結(jié)構(gòu)包括運(yùn)行時(shí)數(shù)據(jù)區(qū)域抖坪、執(zhí)行引擎萍鲸、本地庫(kù)接口和本地方法庫(kù)闷叉。
Class文件格式
Java文件被編譯后生成了Class文件擦俐,這種二進(jìn)制格式文件不依賴于特定的硬件和操作系統(tǒng)。每一個(gè)Class文件中都對(duì)應(yīng)著唯一的類(lèi)或者接口的定義信息握侧,但是類(lèi)或者接口并一定定義在文件中蚯瞧,比如類(lèi)和接口可以通過(guò)類(lèi)加載器來(lái)直接生成。
類(lèi)的生命周期
一個(gè)Java文件被加載到Java虛擬機(jī)內(nèi)存中到從內(nèi)存中卸載的過(guò)程被稱為類(lèi)的生命周期品擎。類(lèi)的生命周期包括的階段分別是:加載埋合、鏈接、初始化萄传、使用和卸載甚颂。其中鏈接分為三個(gè)部分:驗(yàn)證、準(zhǔn)備和解析秀菱。因此類(lèi)的生命周期分為7個(gè)階段振诬。而從廣義來(lái)上來(lái),類(lèi)的加載包含了類(lèi)的生命周期的5個(gè)階段:加載衍菱、鏈接(驗(yàn)證赶么、準(zhǔn)備、初始化)脊串、初始化辫呻。其五個(gè)階段的工作如下:
(1)加載:查找并加載Class文件
(2)鏈接:包括驗(yàn)證、準(zhǔn)備和解析
驗(yàn)證:確保被導(dǎo)入類(lèi)型的正確性
準(zhǔn)備:為類(lèi)的靜態(tài)字段分配字段琼锋,并使用默認(rèn)值初始化這些字段
解析:虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用放闺。
(3)初始化:將類(lèi)變量初始化為正確初始值。
類(lèi)加載子系統(tǒng)
類(lèi)加載子系統(tǒng)通過(guò)多種類(lèi)加載器來(lái)查找和加載Class文件到Java虛擬機(jī)中缕坎,Java虛擬機(jī)有兩種類(lèi)加載器:系統(tǒng)加載器和自定義加載器怖侦。
系統(tǒng)加載器分為三種:
①Bootstrap ClassLoader(引導(dǎo)類(lèi)加載器)
用C/C++實(shí)現(xiàn)的加載器,用于加載執(zhí)行的JDK的核心類(lèi)庫(kù)念赶。比如java.lang,java.uti等础钠。Java虛擬機(jī)的啟動(dòng)就是通過(guò)引導(dǎo)類(lèi)加載器來(lái)創(chuàng)建一個(gè)初始類(lèi)完成的。因?yàn)榇思虞d器是使用與平臺(tái)無(wú)關(guān)的C/C++語(yǔ)言實(shí)現(xiàn)的叉谜,所以java訪問(wèn)不到旗吁。
②Extensions ClassLoader(擴(kuò)展類(lèi)加載器)
加載java的擴(kuò)展類(lèi),比如java.ext.dir下的停局。
③Application ClassLoader(應(yīng)用程序類(lèi)加載器)
又稱System ClassLoader很钓,這個(gè)類(lèi)加載器可以通過(guò)ClassLoader的getSystemClassLoader方法獲取到香府。繼承自java.lang.ClassLoader。
運(yùn)行時(shí)數(shù)據(jù)區(qū)域(【Java內(nèi)存】)
很多人將java內(nèi)存區(qū)域分為堆內(nèi)存(Heap)和棧內(nèi)存(Stack),這種方法不夠準(zhǔn)確码倦。
根據(jù)《Java虛擬機(jī)規(guī)范(Java SE7版)》企孩,將內(nèi)存區(qū)域分為以下幾個(gè)部分:
程序計(jì)數(shù)器、Java虛擬機(jī)棧袁稽、本地方法棧勿璃、Java堆和方法區(qū),下面一一介紹:
1.程序計(jì)數(shù)器
程序計(jì)數(shù)器也叫作PC寄存器推汽,是一塊較小的內(nèi)存空間补疑。Java虛擬機(jī)的多線程是通過(guò)輪流切換并分配處理器執(zhí)行時(shí)間的方式來(lái)實(shí)現(xiàn)的,在一個(gè)確定的時(shí)刻只有一個(gè)處理器執(zhí)行一條線程中的指令歹撒。為了在線程切換后能恢復(fù)到正確的執(zhí)行位置莲组。每個(gè)線程都會(huì)有一個(gè)獨(dú)立的程序計(jì)數(shù)器。程序計(jì)數(shù)器是線程私有的暖夭。
也是java虛擬機(jī)規(guī)范中唯一沒(méi)有規(guī)定任何OutOfMemoryError情況的數(shù)據(jù)區(qū)域锹杈。
2.Java虛擬機(jī)棧
每一條Java虛擬機(jī)線程都有一個(gè)線程私有的Java虛擬機(jī)棧。它的生命周期與線程相同迈着,與線程是同時(shí)創(chuàng)建的县匠。存儲(chǔ)的內(nèi)容是方法調(diào)用的狀態(tài)(就是方法的內(nèi)容)智玻,包括局部變量嘹裂、參數(shù)疗隶、返回值以及運(yùn)算的中間結(jié)果等。
3.本地方法棧
與虛擬機(jī)棧類(lèi)似糕韧,只不過(guò)本地方法棧是用來(lái)支持Native方法的枫振。
4.Java堆
線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域,占用區(qū)域最大萤彩。Java堆用來(lái)存放對(duì)象實(shí)例粪滤,幾乎所有的對(duì)象實(shí)例都會(huì)在這里分配內(nèi)存。是GC機(jī)制管理的主要部分雀扶,也被稱為GC堆杖小。Java堆的容量可以是固定的,也可以動(dòng)態(tài)擴(kuò)展愚墓。
堆分為三個(gè)部分:
①年輕代:又被劃分為Eden區(qū)和Survivor(From Survivor予权、ToSurvivor),空間分配比例為8:1:1.
②老年代
5.方法區(qū)
方法區(qū)是線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域,用來(lái)存儲(chǔ)已經(jīng)被java虛擬機(jī)加載的類(lèi)的結(jié)構(gòu)信息浪册,包括運(yùn)行時(shí)常量池扫腺、字段和方法信息、靜態(tài)變量等信息村象。注意【運(yùn)行時(shí)】
對(duì)象的創(chuàng)建
創(chuàng)建一個(gè)對(duì)象會(huì)進(jìn)行如下操作:
①判斷對(duì)象對(duì)應(yīng)的類(lèi)是否加載笆环、鏈接和初始化
②為對(duì)象分配內(nèi)存
根據(jù)java堆是否完整攒至,分為兩種方式:指針碰撞(內(nèi)存是規(guī)整的)、空閑列表(內(nèi)存不規(guī)整)
③處理并發(fā)安全問(wèn)題
④初始化分配到的內(nèi)存空間
將分配到的內(nèi)存信息躁劣,除了對(duì)象頭之外都初始化為零
⑤設(shè)置對(duì)象的對(duì)象頭
⑥執(zhí)行init方法進(jìn)行初始化
執(zhí)行init方法迫吐,初始化對(duì)象的成員變量、調(diào)用類(lèi)的構(gòu)造方法账忘,這樣一個(gè)對(duì)象就被創(chuàng)建出來(lái)了志膀。
對(duì)象的堆內(nèi)存布局
emm...
oop-klass模型
用來(lái)描述java對(duì)象實(shí)例的一種模型。剩下的就emm...
垃圾標(biāo)記算法
目前有兩種垃圾標(biāo)記算法:引用計(jì)數(shù)法和根搜索算法(可達(dá)路徑法)
java中有四種引用:強(qiáng)引用闪萄、軟引用梧却、弱引用和虛引用。
1.強(qiáng)引用:新建一個(gè)對(duì)象時(shí)就創(chuàng)建了一個(gè)具有強(qiáng)引用的對(duì)象败去,也就是new出來(lái)的對(duì)象,這種引用的對(duì)象烈拒,垃圾收集器絕不會(huì)回收它圆裕。
2.軟引用:當(dāng)內(nèi)存不夠時(shí),會(huì)回收這些對(duì)象的內(nèi)存荆几。對(duì)應(yīng)SoftReference吓妆。
3.弱引用:比軟引用具有更短的生命周期,垃圾回收器一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象吨铸,不管當(dāng)前內(nèi)存是否足夠行拢,都會(huì)回收它的內(nèi)存,對(duì)應(yīng)WeakReference.
4.虛引用:虛引用并不會(huì)決定對(duì)象的生命周期诞吱,如果一個(gè)對(duì)象僅持有虛引用舟奠,這就和沒(méi)有任何引用一樣,在任何時(shí)候都可能被垃圾收集器回收房维。對(duì)應(yīng)PhantomReference類(lèi)沼瘫。
兩種標(biāo)記算法:
1.引用計(jì)數(shù)法
其基本思想就是每個(gè)對(duì)象都有一個(gè)引用計(jì)數(shù)器,當(dāng)對(duì)象在某處被引用的時(shí)候咙俩,它的引用計(jì)數(shù)器就加1耿戚,引用失效時(shí)就減1.當(dāng)值為0時(shí),該對(duì)象就不能被使用阿趁,變成了垃圾膜蛔。
目前主流的Java虛擬機(jī)沒(méi)有選擇引用計(jì)數(shù)算法來(lái)標(biāo)記垃圾,是因?yàn)闆](méi)有解決對(duì)象之間相互循環(huán)引用的問(wèn)題脖阵。
2.根搜索算法
這個(gè)算法的基本思想就是選定一些對(duì)象作為GC Roots皂股,并組成根對(duì)象集合,然后以這些GC Roots的對(duì)象作為起始點(diǎn)独撇,向下搜索屑墨,如果目標(biāo)對(duì)象到GC Roots是連著的躁锁,我們稱該目標(biāo)對(duì)象是可達(dá)的,如果目標(biāo)對(duì)象不可達(dá)則說(shuō)明目標(biāo)對(duì)象時(shí)可以被回收的對(duì)象卵史。解決了計(jì)數(shù)算法無(wú)法解決的問(wèn)題:已經(jīng)死亡的對(duì)象因?yàn)橄嗷ヒ枚荒鼙换厥铡?在Java中战转,可以作為GC Roots的對(duì)象主要有以下幾種:
①Java棧中引用的對(duì)象
②本地方法棧中JNI引用的對(duì)象
③方法區(qū)中運(yùn)行時(shí)常量池引用的對(duì)象
④方法區(qū)中靜態(tài)屬性引用的對(duì)象
⑤運(yùn)行中的線程
⑥由引導(dǎo)類(lèi)加載器加載的對(duì)象
⑦GC控制的對(duì)象
被標(biāo)記為不可達(dá)的對(duì)象會(huì)立即被垃圾回收器回收嗎?
Java對(duì)象在虛擬機(jī)中的生命周期
java對(duì)象被類(lèi)加載器加載到虛擬機(jī)中后以躯,在java虛擬機(jī)中有7個(gè)階段:
①創(chuàng)建階段
②應(yīng)用階段
③不可見(jiàn)階段
在程序中找不到對(duì)象的任何強(qiáng)引用槐秧,但仍可能被特殊的強(qiáng)引用GC Roots持有著,比如對(duì)象被本地方法棧中JNI引用或被運(yùn)行中的線程引用等忧设。
④不可達(dá)階段
垃圾回收器發(fā)現(xiàn)對(duì)象不可達(dá)
⑤收集階段
⑥終結(jié)階段
等待垃圾收集器回收該對(duì)象空間刁标。
⑦對(duì)象空間重新分配階段
垃圾回收算法
1.標(biāo)記-清除算法
分為兩個(gè)階段:
標(biāo)記階段:標(biāo)記出可以回收的對(duì)象
清除階段:回收被標(biāo)記的對(duì)象所占用的空間
缺點(diǎn):標(biāo)記和清除的效率都不高,容易產(chǎn)生大量不連續(xù)的內(nèi)存碎片址晕。
2.復(fù)制算法
將內(nèi)存空間劃為兩個(gè)相等的區(qū)域膀懈,每次只使用其中一個(gè)區(qū)域。在垃圾收集時(shí)谨垃,遍歷當(dāng)前使用的區(qū)域启搂,將存活的對(duì)象復(fù)制到另外一個(gè)區(qū)域中,最后將當(dāng)前使用的區(qū)域的可回收的對(duì)象進(jìn)行回收刘陶。
缺點(diǎn):使用內(nèi)存為原來(lái)的一半胳赌。如果存活對(duì)象很少,復(fù)制算法的效率就很高匙隔,但絕大多數(shù)對(duì)象的生命周期都很短疑苫,并且這些生命周期很短的對(duì)象都存于新生代中,所以復(fù)制算法被廣泛應(yīng)用于新生代中纷责。
- 標(biāo)記-壓縮算法
與標(biāo)記-清除算法不同的是捍掺,在標(biāo)記可回收的對(duì)象后將所有存活的對(duì)象壓縮到內(nèi)存的一端,使它們緊湊地排列在一起碰逸,然后對(duì)邊界以外的內(nèi)存進(jìn)行回收乡小。解決了內(nèi)存碎片的問(wèn)題,被廣泛應(yīng)用于老年代中饵史。
分代收集算法
java堆區(qū)中大部分的對(duì)象生命周期很短满钟,少部分對(duì)象生命周期很長(zhǎng)。應(yīng)該對(duì)不同生命周期的對(duì)象采取不同的收集策略胳喷,根據(jù)生命周期長(zhǎng)短將他們放到不同的內(nèi)存區(qū)域湃番,并在不同的區(qū)域采用不同的收集方法,這就是分代的概念】月叮現(xiàn)在主流的Java虛擬機(jī)的垃圾收集器都是采用的分代收集算法吠撮。Java堆區(qū)基于分代的概念,分為新生代和老年代讲竿。其中新生代再細(xì)分為Eden空間泥兰、From Survivor空間和 To Survivor空間弄屡。Eden空間中覺(jué)大多數(shù)對(duì)象生命周期很短,所以新生代的空間劃分并不是均勻的鞋诗,HotSpot虛擬機(jī)默認(rèn)Eden空間和兩個(gè)Survivor空間的所占的比例為8:1.
分代收集中垃圾收集的類(lèi)型分為兩種:
①新生代垃圾收集
②老年代垃圾收集膀捷,通常情況下會(huì)伴隨至少一次的新生代垃圾收集,它的收集頻率較低削彬,耗時(shí)較長(zhǎng)全庸。
當(dāng)執(zhí)行一次新生代垃圾收集時(shí),Eden空間的存活對(duì)象會(huì)被復(fù)制到To Survivor空間融痛,并且之前經(jīng)過(guò)一次新生代垃圾收集并在From Survivor空間存活的仍年輕的對(duì)象也會(huì)復(fù)制到To Survivor空間壶笼。有兩種情況Eden空間和From Survivor空間存活的對(duì)象不會(huì)被復(fù)制到To Survivor空間,而是晉升到老年代雁刷。一種是存活的對(duì)象的分代年齡超過(guò)-XX:MaxTenuringThreshold所指定閾值覆劈。另一種是To Survivor空間容量達(dá)到閾值。當(dāng)所有存活的對(duì)象都會(huì)復(fù)制到To Survivor空間或晉升到老年代安券,也就意味著Eden空間和From Survivor空間剩下的都是可回收的對(duì)象墩崩。這個(gè)時(shí)候GC執(zhí)行新生代垃圾收集,Eden空間和From Survivor空間都會(huì)被清空侯勉,新生代中存活的對(duì)象都在To Survivor中。接下來(lái)From Survivor和To Survivor空間互換位置铝阐,每次Survivor空間互換都要保證To Survivor是空的址貌,這就是復(fù)制算法在新生代中的應(yīng)用。
老年代則會(huì)采用標(biāo)記-壓縮算法或者標(biāo)記-清除算法徘键。(畢竟收集頻率低练对,耗時(shí)長(zhǎng))
Dalvik和ART
Dalvik虛擬機(jī),簡(jiǎn)稱Dalvik VM或者DVM,Android 4.4及之前默認(rèn)采用的還是DVM吹害,4.4提供了一個(gè)選項(xiàng)開(kāi)啟ART.在Android 5.0版本中被舍棄螟凭,改而使用ART,DVM退出歷史舞臺(tái)。
DVM與JVM區(qū)別
DVM之所以不是一個(gè)JVM它呀,主要原因是DVM沒(méi)有遵循JVM來(lái)實(shí)現(xiàn)螺男,DVM與JVM主要區(qū)別如下:
1.架構(gòu)不同
JVM基于棧,需要去棧中讀寫(xiě)數(shù)據(jù)纵穿;
DVM基于寄存器下隧;
2.執(zhí)行的字節(jié)碼不同
JVM會(huì)通過(guò).class文件和jar文件獲取相應(yīng)的字節(jié)碼。
DVM會(huì)用dx工具將所有的.class文件轉(zhuǎn)換為一個(gè)dex文件谓媒,然后從該.dex文件中讀取指令和數(shù)據(jù)淆院。
3.DVM允許在有限的內(nèi)存中同時(shí)運(yùn)行多個(gè)進(jìn)程
4.DVM由Zygote創(chuàng)建和初始化
5.DVM有共享機(jī)制,JVM不存在共享機(jī)制
6.DVM早期沒(méi)有使用JIT編譯器
ART和DVM的區(qū)別
下面是重點(diǎn)句惯,需要背誦的!
1.DVM應(yīng)用每次運(yùn)行時(shí)土辩,字節(jié)碼都需要通過(guò)JIT編譯器編譯為機(jī)器碼支救,會(huì)使應(yīng)用程序的運(yùn)行效率變低。而ART中拷淘,系統(tǒng)在安裝應(yīng)用程序時(shí)會(huì)進(jìn)行一次AOT(預(yù)編譯)各墨,將字節(jié)碼先編譯成機(jī)器碼并存儲(chǔ)在本地。以空間換時(shí)間辕棚。Android 7.0版本,ART加入了即時(shí)編譯器JIT,這樣應(yīng)用程序安裝時(shí)并不是將字節(jié)碼全部編譯成字節(jié)碼欲主,而是在運(yùn)行中將熱點(diǎn)代碼編譯成機(jī)器碼,節(jié)省了應(yīng)用程序的安裝時(shí)間并節(jié)省了存儲(chǔ)空間逝嚎。
2.DVM是為32位CPU設(shè)計(jì)的扁瓢,而ART支持64位并兼容32位CPU,這也是DVM被淘汰的主要原因之一。
3.ART對(duì)垃圾回收機(jī)制進(jìn)行了改進(jìn)补君,更頻繁地執(zhí)行并行垃圾回收引几,將GC暫停由2次減少為1次等。
4.ART的運(yùn)行時(shí)堆空間劃分和DVM不同挽铁。
ART的運(yùn)行時(shí)堆默認(rèn)是由4個(gè)Space和多個(gè)輔助數(shù)據(jù)結(jié)構(gòu)組成的伟桅;DVM有兩個(gè)Space,分別是Zygote Space叽掘、Allocation Space楣铁;ART新增了兩個(gè):Image Space和Large Object Space。前者存放一些預(yù)加載類(lèi)更扁,后者用來(lái)分配一些大對(duì)象盖腕。
此外,ART的GC日志和DVM不同浓镜,ART會(huì)為那些主動(dòng)請(qǐng)求的垃圾收集事件或者認(rèn)為GC速度慢時(shí)才會(huì)打印GC日志溃列,GC速度慢指的是GC暫停超過(guò)5ms或者GC持續(xù)時(shí)間超過(guò)100ms。
引起GC的原因
原因很多膛薛,只例舉幾個(gè)
①Concurrent:并發(fā)GC听隐,不會(huì)使App的線程暫停,該GC在后臺(tái)運(yùn)行雅任,不會(huì)阻止內(nèi)存分配。
②Alloc:堆內(nèi)存已滿時(shí)成玫,App嘗試分配內(nèi)存引起GC
③Explict:App顯式的請(qǐng)求垃圾收集,例如調(diào)用System.gc()
④NativeAlloc:Native內(nèi)存分配時(shí)陋葡,比如為Bitmap分配內(nèi)存
等等憔晒。
ART & DVM啟動(dòng)時(shí)機(jī)
Zygote在啟動(dòng)時(shí)會(huì)調(diào)用app_main.cpp文件中的main方法蒂萎;
main方法中會(huì)調(diào)用AppRuntime的start函數(shù)堤如,其實(shí)現(xiàn)在其父類(lèi)的AndroidRuntime中,其中會(huì)調(diào)用startVm方法來(lái)創(chuàng)建Java虛擬機(jī)杰标。然后會(huì)調(diào)用jin_invocation的init函數(shù)袜漩,其中調(diào)用getLibrary方法來(lái)加載ART,傳參為libart.so說(shuō)明用的是ART,傳參為libdvm.so說(shuō)的用的是DVM.
Binder原理
Binder設(shè)計(jì)與實(shí)現(xiàn)
Binder圖文詳解
根據(jù)Android系統(tǒng)的分層递惋,Binder機(jī)制可以分為:
Java Binder
Native Binder
Kernel Binder
其中Native Binder指的是Native 層的Binder。
學(xué)習(xí)Binder的前置知識(shí)點(diǎn)
本小節(jié)主要有三部分:
①Linux 和 Binder的IPC通信原理
②使用Binder的原因
③學(xué)習(xí)Binder的原因
Linux和Binder的IPC通信原理
先附上一張進(jìn)程通信簡(jiǎn)單模型
為了保護(hù)用戶進(jìn)程绒净,不能直接操作內(nèi)核视事,以保證內(nèi)核的安全蚌吸,操作系統(tǒng)從邏輯上將虛擬空間劃分為:用戶空間、內(nèi)核空間族购。
內(nèi)核空間是Linux內(nèi)核的運(yùn)行空間,用戶空間是用戶程序的運(yùn)行空間站削。為了保證內(nèi)核的安全珊肃,他們是隔離的,即使用戶程序崩潰了妇拯,內(nèi)核也不會(huì)受影響丹弱。內(nèi)核空間的數(shù)據(jù)數(shù)據(jù)是可以進(jìn)程間共享的,而用戶空間的數(shù)據(jù)則不可以。
進(jìn)程隔離指的是一個(gè)進(jìn)程不能直接操作或者訪問(wèn)另一個(gè)線程滑凉,也就是進(jìn)程A不能直接訪問(wèn)進(jìn)程B的數(shù)據(jù)帚湘。
系統(tǒng)調(diào)用:
用戶空間如果需要訪問(wèn)內(nèi)核空間捅厂,只有一種方式,那就是:系統(tǒng)調(diào)用资柔。
進(jìn)程A和進(jìn)程B的用戶空間可以通過(guò)如下系統(tǒng)函數(shù)和內(nèi)核空間交互::
①copy_from_user
:將用戶空間的數(shù)據(jù)復(fù)制到內(nèi)核空間
②copy_to_user
:將內(nèi)核空間的數(shù)據(jù)復(fù)制到用戶空間
內(nèi)存映射
應(yīng)用程序不能直接操作設(shè)備硬件地址焙贷,所以操作系統(tǒng)提供了一種機(jī)制:內(nèi)存映射,將設(shè)備地址映射到進(jìn)程虛擬內(nèi)存區(qū)贿堰。
如果不用內(nèi)存映射辙芍,就需要在內(nèi)核空間新建一個(gè)頁(yè)緩存,頁(yè)緩存區(qū)復(fù)制磁盤(pán)中的文件羹与,然后用戶空間再去復(fù)制頁(yè)緩存的中文件故硅,這就需要兩次復(fù)制。而如果采用內(nèi)存映射纵搁,映射模型如下:
由于新建了虛擬內(nèi)存區(qū)域吃衅,磁盤(pán)文件和虛擬內(nèi)存區(qū)域就可以直接映射,少了一次復(fù)制腾誉。
在Linux中通過(guò)系統(tǒng)調(diào)用函數(shù)mmap函數(shù)來(lái)實(shí)現(xiàn)內(nèi)存映射徘层。將用戶空間的一塊內(nèi)存區(qū)域映射到內(nèi)核空間。映射關(guān)系建立后利职,用戶對(duì)這塊內(nèi)存區(qū)域的修改就可以直接反映到內(nèi)核空間趣效。
Linux IPC通信原理
內(nèi)核程序在內(nèi)核空間分配內(nèi)存并開(kāi)辟一塊內(nèi)核緩存區(qū),發(fā)送進(jìn)程通過(guò)copy_from_user()函數(shù)將數(shù)據(jù)復(fù)制到內(nèi)核空間的緩沖區(qū)中猪贪。同樣英支,接收進(jìn)程接收數(shù)據(jù)時(shí)在自己的用戶空間開(kāi)辟了一塊內(nèi)核緩存區(qū),然后內(nèi)核程序調(diào)用copy_to_user()函數(shù)將數(shù)據(jù)從內(nèi)核緩存區(qū)復(fù)制到接收進(jìn)程哮伟。這樣就是一次Linux進(jìn)程間通信干花。
Linux的IPC通信有兩個(gè)問(wèn)題:
①一次數(shù)據(jù)數(shù)據(jù)傳遞需要經(jīng)歷:用戶空間->內(nèi)核空間->用戶空間,需要兩次數(shù)據(jù)復(fù)制楞黄。
②接收進(jìn)程不知道需要多大的空間存放將要傳遞過(guò)來(lái)的數(shù)據(jù)池凄,只能開(kāi)辟盡可能大的空間,或者調(diào)用API接收消息頭獲取消息體的大小鬼廓,浪費(fèi)了空間或者時(shí)間肿仑。
Binder通信原理
Binder基于內(nèi)存映射。
Binder通信步驟如下:
①Binder驅(qū)動(dòng)在內(nèi)核空間創(chuàng)建一個(gè)數(shù)據(jù)接收緩存區(qū)碎税。
②在內(nèi)核空間開(kāi)辟一塊內(nèi)核緩存區(qū)尤慰,建立內(nèi)核緩存區(qū)與數(shù)據(jù)接收緩存區(qū)之間的映射關(guān)系。
③發(fā)送方通過(guò)copy_from_user()
函數(shù)將數(shù)據(jù)復(fù)制到內(nèi)核緩存區(qū)中雷蹂,由于內(nèi)核緩存區(qū)與數(shù)據(jù)接收緩存區(qū)也就是接收進(jìn)程的用戶空間存在內(nèi)存映射伟端。因此也就相當(dāng)于把數(shù)據(jù)發(fā)送到了接收進(jìn)程的用戶空間,這樣便完成了一次進(jìn)程間的通信匪煌。
整個(gè)過(guò)程只使用了一次復(fù)制责蝠。
使用Binder的原因
1.性能方面:
性能方面主要影響是數(shù)據(jù)復(fù)制次數(shù)。Socket/管道/消息隊(duì)列都復(fù)制了兩次萎庭,共享內(nèi)存不需要復(fù)制霜医,Binder復(fù)制一次,性能僅次于共享內(nèi)存驳规。(共享內(nèi)存有很多缺點(diǎn)肴敛,下面介紹)
2.穩(wěn)定性方面:
Binder基于C/S架構(gòu),這個(gè)架構(gòu)采用兩層結(jié)構(gòu)吗购,技術(shù)上已經(jīng)十分成熟了医男。共享內(nèi)存沒(méi)有分層,難以控制巩搏,還有可能產(chǎn)生死鎖昨登。[不用共享內(nèi)存原因之一]
3.安全方面
傳統(tǒng)的IPC接收方無(wú)法獲得對(duì)象可靠的用戶進(jìn)程ID(UID)/進(jìn)程ID(PID).無(wú)法鑒別對(duì)身份。
4.語(yǔ)言方面
Linux是基于C語(yǔ)言的贯底,C語(yǔ)言是面向過(guò)程的丰辣。而Java是面向?qū)ο蟮摹inder本身符合面向?qū)ο蟮乃枷搿?/p>
系統(tǒng)中并不是所有進(jìn)程通信都是用了Binder禽捆,而是根據(jù)場(chǎng)景來(lái)選擇最合適的笙什,比如Zygote進(jìn)程與AMS通信采用的Socket,Kill Process采用的是信號(hào)胚想。
學(xué)習(xí)Binder的原因(Binder的作用)
Binder在Android中的作用舉足輕重琐凭,很多原因都與Binder 相關(guān)。
①系統(tǒng)中的各個(gè)進(jìn)程是如何通信的
②Android啟動(dòng)流程
③AMS/PMS原因
④四大組件的原理浊服、比如Activity如何啟動(dòng)
⑤插件化原理
⑥系統(tǒng)服務(wù)的客戶端和服務(wù)端是如何通信的统屈。(比如MediaPlayer和MediaPlayerService)
SystemServer進(jìn)程啟動(dòng)后會(huì)創(chuàng)建Binder線程池胚吁,其目的是通過(guò)Binder可以使SystemServer進(jìn)程中的服務(wù)能夠和其他進(jìn)程間通信。
ServiceManager中的Binder機(jī)制
Binder框架中定義了四個(gè)角色:Binder驅(qū)動(dòng)愁憔、Client腕扶、Server、ServiceManager(用于管理系統(tǒng)中的服務(wù))吨掌。
Binder驅(qū)動(dòng):
和路由器一樣半抱,Binder驅(qū)動(dòng)雖然默默無(wú)聞,卻是通信的核心膜宋。盡管名叫‘驅(qū)動(dòng)’窿侈,實(shí)際上和硬件設(shè)備沒(méi)有任何關(guān)系,只是實(shí)現(xiàn)方式和設(shè)備驅(qū)動(dòng)程序是一樣的:它工作于內(nèi)核態(tài)秋茫,提供open()史简,mmap(),poll()学辱,ioctl()等標(biāo)準(zhǔn)文件操作乘瓤,以字符驅(qū)動(dòng)設(shè)備中的misc設(shè)備注冊(cè)在設(shè)備目錄/dev下,用戶通過(guò)/dev/binder訪問(wèn)該它策泣。驅(qū)動(dòng)負(fù)責(zé)進(jìn)程之間Binder通信的建立衙傀,Binder在進(jìn)程之間的傳遞,Binder引用計(jì)數(shù)管理萨咕,數(shù)據(jù)包在進(jìn)程之間的傳遞和交互等一系列底層支持统抬。驅(qū)動(dòng)和應(yīng)用程序之間定義了一套接口協(xié)議,主要功能由ioctl()接口實(shí)現(xiàn)危队,不提供read()聪建,write()接口,因?yàn)閕octl()靈活方便茫陆,且能夠一次調(diào)用實(shí)現(xiàn)先寫(xiě)后讀以滿足同步交互金麸,而不必分別調(diào)用write()和read()。Binder驅(qū)動(dòng)的代碼位于linux目錄的drivers/misc/binder.c中簿盅。
接下來(lái)是第二篇參考文章的分析挥下,更加明了,重點(diǎn)來(lái)了:
定義
一種虛擬設(shè)備驅(qū)動(dòng)
作用
連接Service進(jìn)程桨醋、Client進(jìn)程和ServerManager進(jìn)程的橋梁棚瘟。
Binder驅(qū)動(dòng)職責(zé):
①創(chuàng)建了一塊內(nèi)存緩存區(qū)
②實(shí)現(xiàn)了內(nèi)存映射關(guān)系:將內(nèi)核緩存區(qū)和接收進(jìn)程內(nèi)存緩存區(qū)映射到同一個(gè)內(nèi)存映射緩沖區(qū)中(調(diào)用mmap()函數(shù))
Binder驅(qū)動(dòng)工作原理
(1)注冊(cè)服務(wù)
①Server進(jìn)程向Binder驅(qū)動(dòng)發(fā)送服務(wù)注冊(cè)請(qǐng)求
②Binder驅(qū)動(dòng)將注冊(cè)請(qǐng)求轉(zhuǎn)發(fā)給ServiceManager進(jìn)程
③ServiceManager進(jìn)程添加該Server進(jìn)程(即已注冊(cè)該服務(wù))
(2)獲取服務(wù)
①Client向Binder驅(qū)動(dòng)發(fā)起獲取服務(wù)的請(qǐng)求,傳遞要獲取的服務(wù)名稱
②Binder驅(qū)動(dòng)將該請(qǐng)求傳遞給ServiceManager進(jìn)程
③ServiceManager查找到Client需要的Server對(duì)應(yīng)的服務(wù)信息
④通過(guò)Binder驅(qū)動(dòng)將上述服務(wù)信息返回給Client進(jìn)程
(3)使用服務(wù)
①Binder驅(qū)動(dòng)為跨進(jìn)程做做準(zhǔn)備喜最,調(diào)用mmap函數(shù)進(jìn)行內(nèi)存映射
②Client進(jìn)程通過(guò)Binder驅(qū)動(dòng)將參數(shù)信息傳遞給Server進(jìn)程
③Server進(jìn)程通過(guò)Client進(jìn)程要求調(diào)用目標(biāo)方法
④Server進(jìn)程將運(yùn)行結(jié)果返回給Client進(jìn)程
有一點(diǎn)需要注意:
Client進(jìn)程偎蘸、Server進(jìn)程和ServiceManager進(jìn)程通信都必須通過(guò)Binder驅(qū)動(dòng)(使用open()和ioctl()函數(shù)),而非直接交互
Client進(jìn)程、Server進(jìn)程 & Service Manager進(jìn)程屬于進(jìn)程空間的用戶空間迷雪,不可進(jìn)行進(jìn)程間交互
Binder驅(qū)動(dòng) 屬于 進(jìn)程空間的 內(nèi)核空間限书,可進(jìn)行進(jìn)程間 & 進(jìn)程內(nèi)交互
還有一點(diǎn)也需要注意:
Binder驅(qū)動(dòng)和ServiceManager屬于Android基礎(chǔ)架構(gòu),是已經(jīng)實(shí)現(xiàn)好的章咧;
而Client和Server屬于應(yīng)用層蔗包,需要客戶端自己實(shí)現(xiàn)。
ServiceManager:
以MediaPlayer為例理解ServiceManager慧邮。在Android系統(tǒng)啟動(dòng)時(shí),MediaServer也被啟動(dòng)舟陆。在MediaServer的main方法中误澳,會(huì)獲得ProcessState實(shí)例。然后獲取到IServierManager秦躯,通過(guò)IServiceManager忆谓,其他進(jìn)程就可以和當(dāng)前的ServiceManager交互,交互方式就是Binder通信踱承。ProcessState實(shí)例代表進(jìn)程的狀態(tài)倡缠,是一個(gè)單例(保證每個(gè)進(jìn)程只有一個(gè)ProcessState實(shí)例),創(chuàng)建參數(shù)是dev/binder茎活,也就是Binder驅(qū)動(dòng)昙沦。在其構(gòu)造方式中,打開(kāi)了Binder驅(qū)動(dòng)载荔,并調(diào)用mmap()函數(shù)盾饮。mmap函數(shù)會(huì)在內(nèi)核虛擬地址空間中申請(qǐng)一塊與用戶空間內(nèi)存相同大小的內(nèi)存,然后申請(qǐng)物理內(nèi)存懒熙,將同一塊物理內(nèi)存分別映射到內(nèi)核虛擬地址空間和用戶虛擬內(nèi)存空間中丘损,實(shí)現(xiàn)內(nèi)核虛擬空間和用戶虛擬內(nèi)存空間數(shù)據(jù)同步操作,也就是內(nèi)存映射工扎。在打開(kāi)Binder驅(qū)動(dòng)后徘钥,會(huì)調(diào)用ioctl()函數(shù)和Binder設(shè)備進(jìn)行參數(shù)的傳遞,并且將Binder支持的最大線程數(shù)設(shè)定為15.
總的來(lái)說(shuō)肢娘,ProcessState實(shí)例有兩個(gè)重要作用:
①打開(kāi)/dev/binder設(shè)備也就是Binder驅(qū)動(dòng)并設(shè)定Binder支持的最大線程數(shù)呈础。
②通過(guò)mmap()函數(shù)為Binder分配一塊虛擬內(nèi)存空間,達(dá)到內(nèi)存映射的目的蔬浙。
ServiceManager中的Binder機(jī)制
ServiceManager中不但使用了Binder通信猪落,而且它本身就是Binder體系內(nèi)的。
(1)在ServiceManager中創(chuàng)建了BpBinder畴博,BpBinder和BBinder和Binder通信的"雙子星"笨忌,BpBinder是客戶端與服務(wù)端交互的代理類(lèi),而B(niǎo)Binder則代表了服務(wù)端俱病。BpBinder和BBinder是一一對(duì)應(yīng)的官疲,BpBinder會(huì)通過(guò)handle來(lái)找到對(duì)應(yīng)的BBinder袱结。
其通信流程圖如下:
(2)BpBinder和BBinder負(fù)責(zé)Binder的通信,而IServiceManager用于處理ServiceManager的業(yè)務(wù)途凫。IServiceManager繼承IInterface是牢,其內(nèi)部定義了一些常量和一些操作Service的操作。其具體實(shí)現(xiàn)是BpServiceManager从诲,此類(lèi)通過(guò)Binder來(lái)實(shí)現(xiàn)通信员萍。
(3)簡(jiǎn)單總結(jié):
BpBinder和BBinder都和通信有關(guān),他們都繼承自IBinder犀盟。
BpServiceManager派生自IServiceManager而晒,他們都和業(yè)務(wù)有關(guān)。
Native Binder的原理的核心就是ServiceManager的原理阅畴。
Binder機(jī)制在Android中的具體實(shí)現(xiàn)原理
Binder機(jī)制在Android中的具體實(shí)現(xiàn)主要依靠Binder類(lèi)倡怎,其實(shí)現(xiàn)了IBinder接口。
TODO://手?jǐn)]Binder代碼贱枣,寫(xiě)一個(gè)簡(jiǎn)單的Demo监署。
理解ClassLoader
DVM和ART加載的是dex文件,而JVM加載的是.class文件纽哥,因此它們的類(lèi)加載器ClassLoader是有區(qū)別的钠乏。
Java中的ClassLoader
類(lèi)加載子系統(tǒng)的主要作用就是通過(guò)多種類(lèi)加載器(ClassLoader)來(lái)查找和加載Class文件到Java虛擬機(jī)中。
ClassLoader的類(lèi)型:
Java中的類(lèi)加載器主要有兩種類(lèi)型:
①系統(tǒng)類(lèi)加載器
②自定義類(lèi)加載器
系統(tǒng)類(lèi)加載器包括3種:
①Bootstrap ClassLoader
②Extensions ClassLoader
③Application ClassLoader
1.Bootstrap ClassLoader(引導(dǎo)類(lèi)加載器)
Java VM的啟動(dòng)就是通過(guò)Bootstrap ClassLoader創(chuàng)建一個(gè)初始類(lèi)來(lái)完成的昵仅。由于Bootstrap ClassLoader是使用C/C++語(yǔ)言實(shí)現(xiàn)的缓熟,所以該類(lèi)加載器不能被Java代碼訪問(wèn)到。Bootstrap ClassLoader并不繼承java.lang.ClassLoader
2.Extensions ClassLoader(擴(kuò)展類(lèi)加載器)
Java中的實(shí)現(xiàn)類(lèi)是ExtClassLoader摔笤,用于加載Java的擴(kuò)展類(lèi)够滑,提供除系統(tǒng)類(lèi)之外的額外功能。加載內(nèi)容包括系統(tǒng)屬性java.ext.dir所指定的目錄吕世;父jiazai
3.Application ClassLoader(應(yīng)用程序類(lèi)加載器)
Java中的實(shí)現(xiàn)類(lèi)為AppClassLoader彰触,也被稱為System ClassLoader,因?yàn)槠淇梢酝ㄟ^(guò)ClassLoader的getSystemClassLoader方法獲取到命辖。父類(lèi)是ExtClassLoader
4.Custom ClassLoader(自定義類(lèi)加載器)
java還有其他的一些類(lèi)加載器况毅,比如SecureClassLoader,繼承自ClassLoader尔艇,擴(kuò)展了權(quán)限方面的功能尔许,加強(qiáng)了ClassLoader的安全性。URLClassLoader繼承自SecureClassLoader终娃,可以通過(guò)URL路徑從jar文件和文件夾中加載類(lèi)和資源味廊。
雙親委托機(jī)制
類(lèi)加載器查找Class所采用的是雙親委托機(jī)制。就是先判斷Class是否已經(jīng)加載,如果沒(méi)有則不是自身去查找而是委托給父加載器進(jìn)行查找余佛,這樣一次遞歸柠新,直到委托到最頂層的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了該Class辉巡,就會(huì)直接返回恨憎,如果沒(méi)有找到,則繼續(xù)依次向下查找郊楣,如果還沒(méi)有找到則最后交給自身去查找憔恳。
有以下兩點(diǎn)好處:;
①避免重復(fù)加載净蚤,如果已經(jīng)加載過(guò)一次Class喇嘱,就不需要再次加載,而是直接讀取已經(jīng)加載的Class塞栅。
②更加安全,如果不用雙親委托腔丧,就可以自定義一個(gè)String類(lèi)用自己的類(lèi)加載器來(lái)替代系統(tǒng)的String類(lèi)放椰,顯然會(huì)造成安全隱患。
自定義類(lèi)加載器
只需要兩個(gè)步驟:
(1)定義一個(gè)自定義ClassLoader并集成抽象類(lèi)ClassLoader
(2)復(fù)寫(xiě)findClass方法愉粤,并在findClass方法中調(diào)用defineClass方法砾医。
Android中的ClassLoader
繼承關(guān)系如下圖:
Java中的ClassLoader可以加載jar文件和Class文件(本質(zhì)是加載Class文件),這一點(diǎn)在Android中并不適用衣厘,因?yàn)锳ndroid中加載的不是class文件如蚜,而是dex文件。
Android中的ClassLoader也分為兩種:
系統(tǒng)類(lèi)加載器和自定義加載器影暴。
系統(tǒng)類(lèi)加載器分為三種:
BootClassLoader/PathClassLoader/DexClassLoader
1.BootClassLoader
Android系統(tǒng)啟動(dòng)時(shí)會(huì)使用PathClassLoader而不是BootClassLoader來(lái)預(yù)加載常用類(lèi)错邦,與SDK中的BootstrapClassLoader不同,它并不是C/C++代碼實(shí)現(xiàn)的型宙,而是由Java實(shí)現(xiàn)的撬呢。這是一個(gè)單例類(lèi),修飾符是默認(rèn)的妆兑,只有包內(nèi)可以訪問(wèn)到魂拦。
是PathClassLoader的父類(lèi),是ClassLoader的靜態(tài)內(nèi)部類(lèi)搁嗓;我們的Class文件都是PathClassLoader加載的芯勘。如果沒(méi)有傳ClassLoader,默認(rèn)采用BootClassLoader腺逛,比如Class.forName中的實(shí)現(xiàn)荷愕。
2.DexClassLoader
DexClassLoader可以加載dex文件以及包含dex的壓縮文件(apk和jar文件)。
DexClassLoader的參數(shù)有四個(gè):
①dexPath:dex相關(guān)文件路徑集合,多個(gè)路徑用分隔符":"分隔
②optimizedDirectory:解壓的dex文件存儲(chǔ)路徑(8.0之后就已經(jīng)沒(méi)有用了)
③librarySearchPath:包含C/C++庫(kù)的路徑集合路翻,多個(gè)路徑用文件分隔符分隔狈癞,可以為null。
④parent:父加載器
3.PathClassLoader
Android系統(tǒng)使用PathClassLoader用來(lái)加載系統(tǒng)類(lèi)和應(yīng)用程序的類(lèi)真竖。沒(méi)有參數(shù)optimizedDirectory,無(wú)法定義加載的dex文件存儲(chǔ)路徑讨韭,所以用來(lái)加載已經(jīng)安裝的dex文件。但是8.0之后濒生,optimizedDirectory參數(shù)已經(jīng)沒(méi)有用了!>跻濉K场厉碟!傳到BootClassLoader后并沒(méi)有繼續(xù)傳入DexFile文件中箍鼓,所以8.0之后款咖,DexClassLoader和PathClassLoader作用已經(jīng)一樣了:M荨!!
DexClassLoader和PathClassLoader的父類(lèi)均是BaseDexClassLoader
ClassLoader的加載過(guò)程
Android的ClassLoader同樣遵循了雙親委托機(jī)制浮入,ClassLoader的加載方法是loadClass方法。其中會(huì)先檢查類(lèi)是否已經(jīng)加載秽晚,如果已經(jīng)加載就返回該類(lèi)筒愚,如果沒(méi)有加載就判斷父加載器是否存在菩浙,存在就調(diào)用父加載器的loadClass方法,委托給父類(lèi)檢查是否加載劲蜻。如果父加載器沒(méi)有加載,就調(diào)用findClass加載類(lèi)先嬉。findClass交由子類(lèi)實(shí)現(xiàn):
其內(nèi)部調(diào)用了DexPathList的findClass的方法轧苫,DexPathList是BaseDexClassLoader的構(gòu)造方法中構(gòu)造的疫蔓。
DexPathList中有個(gè)重要的數(shù)組:dexElements衅胀,數(shù)組承載的元素是Element嘿歌,這是DexPathList中定義的靜態(tài)內(nèi)部類(lèi),內(nèi)部封裝了DexFile,可以理解為一個(gè)Element對(duì)應(yīng)一個(gè)DexFile募闲,對(duì)應(yīng)一個(gè)dex文件步脓。
ElementDexList中就是遍歷所有的Element,調(diào)用Element的findClass方法去加載每一個(gè)dex文件蝇更。此findClass方法最終會(huì)調(diào)用到:
native方法defineClassNative來(lái)加載dex相關(guān)文件沪编。到了native的話,就先到此為止年扩。
BootClassLoader的創(chuàng)建
需要從Zygote進(jìn)程說(shuō)起蚁廓,ZygoteInit的main方法中調(diào)用了preload方法
preload方法中又調(diào)用了preloadClasses方法:
preloadClasses方法用于Zygote進(jìn)程初始化時(shí)預(yù)加載常用類(lèi)。
這個(gè)方法中會(huì)將/system/etc/preloaded-classes文件封裝成FileInputStream厨幻,preloaded-classes文件中存有預(yù)加載類(lèi)目錄相嵌。接著將輸入流包裝成BufferedReader,讀取所有預(yù)加載類(lèi)的名字况脆。每讀一行饭宾,就調(diào)用Class.forName,通過(guò)反射來(lái)創(chuàng)建預(yù)加載類(lèi),看一下forName的具體實(shí)現(xiàn):
forName中會(huì)調(diào)用BootClassLoader.getInstance創(chuàng)建BootClassLoader的單例(懶漢模式格了,靜態(tài)加鎖看铆,全局唯一)。接著調(diào)用的classForName是native方法盛末,由c/c++實(shí)現(xiàn)弹惦。
簡(jiǎn)而言之:BootClassLoader是在Zygote進(jìn)程的Zygote入口方法中被創(chuàng)建的,用于加載preloaded-classes文件中存有的預(yù)加載類(lèi)悄但。
PathClassLoader的創(chuàng)建過(guò)程
PathClassLoader是在SystemServer進(jìn)程中采用工廠模式創(chuàng)建的棠隐。
第十三章 熱修復(fù)原理
熱修復(fù)框架雖然很多,但其核心技術(shù)主要有三類(lèi):
代碼修復(fù)檐嚣、資源修復(fù)和動(dòng)態(tài)鏈接庫(kù)修復(fù)(so庫(kù))
資源修復(fù)
很多熱修復(fù)框架的資源修復(fù)都參考了Instant Run的資源修復(fù)的原理(Instant Run在Android Studio 3.5已經(jīng)廢除助泽,取而代之的是Apply Changes),Instant Run是AS 2.0新增的運(yùn)行機(jī)制嚎京。
Instant Run部署有三種方式嗡贺,Instant Run會(huì)根據(jù)代碼的情況來(lái)決定采用哪種部署方式。無(wú)論哪種方式都不需要重新安裝App鞍帝。三種部署方式如下:
①Hot Swap:效率最高的部署方式暑刃,不需要重啟App,也不需要重啟當(dāng)前的Activity膜眠。修改一個(gè)現(xiàn)有方法中的代碼時(shí)會(huì)采用Hot swap岩臣。
②Warm Swap:App不需要重啟溜嗜,但是Activity需要重啟。修改或刪除一個(gè)現(xiàn)有的資源文件時(shí)會(huì)采用Warm Swap架谎。
③Cold Swap:App需要重啟炸宵,但是不需要重新安裝。采用Cold Swap的情況很多谷扣,比如添加土全、刪除或者修改一個(gè)字段和方法、添加一個(gè)類(lèi)等会涎。
instant run的資源修復(fù)原理
Instant Run資源修復(fù)的核心邏輯在MonkeyPatcher的monkeyPatchExistingResources(Context context,String externalResourceFile,Collection<Activity> activities)方法中裹匙。Instant Run資源熱修復(fù)可以簡(jiǎn)單的分為兩個(gè)步驟:
(1)創(chuàng)建新的AssetManager,通過(guò)反射調(diào)用addAssetPath方法加載外部的資源末秃,這樣新創(chuàng)建的AssetManager就含有了外部資源概页。
(2)將AssetManager類(lèi)型的mAsset字段的引用全部替換為新創(chuàng)建的AssetManager。(遍歷所有的Activity练慕,拿到每一個(gè)activity的Resources類(lèi)里的mAsset然后替換為新創(chuàng)建的AssetManager)
代碼修復(fù)
代碼修復(fù)主要有三個(gè)方案惰匙,分別是:
底層替換方案学少、類(lèi)加載方案和Instant Run方案
(1)類(lèi)加載方案
類(lèi)加載方案基于Dex分包方案匀归。
Dex分包是因?yàn)橛袃蓚€(gè)限制:
①65535限制:
應(yīng)用中引用的方法數(shù)不能超過(guò)65535。( 單個(gè)Dex文件中耻陕,method個(gè)數(shù)采用使用原生類(lèi)型short來(lái)索引劲阎,即2個(gè)字節(jié)最多65536個(gè)method绘盟,field、class的個(gè)數(shù)也均有此限制)
②LinearAlloc限制
在安裝應(yīng)用的時(shí)候可能會(huì)提示INSTALL_FAILED_DEXOPT,產(chǎn)生的原因就是LinearAlloc限制悯仙,DVM的LinearAlloc是一個(gè)固定的緩存區(qū)龄毡,當(dāng)方法數(shù)超過(guò)緩存區(qū)的大小時(shí)會(huì)報(bào)錯(cuò)。
為了解決上面兩個(gè)限制雁比,從而產(chǎn)生了Dex分包方案。
Dex分包方案主要做的是在打包時(shí)將應(yīng)用代碼分成多個(gè)Dex撤嫩,將應(yīng)用啟動(dòng)時(shí)必須用到的類(lèi)和這些類(lèi)的直接引用類(lèi)放到主Dex中偎捎,其他代碼放到次Dex中。當(dāng)應(yīng)用啟動(dòng)時(shí)先加載主Dex序攘,等到應(yīng)用啟動(dòng)后再動(dòng)態(tài)的加載次Dex茴她。
Dex分包方案主要有兩種:
①Dex自動(dòng)拆包(Google官方方案)
②動(dòng)態(tài)加載方案
主要看動(dòng)態(tài)加載方案。
之前說(shuō)過(guò)ClassLoader的加載過(guò)程程奠,其中一個(gè)環(huán)節(jié)就是調(diào)用DexPathList的findClass方法丈牢,然后調(diào)用Element的findClass方法,Element中封裝了DexFile瞄沙,DexFile用于加載dex文件己沛,因此每個(gè)dex文件對(duì)應(yīng)一個(gè)Element慌核。多個(gè)Element組成了有序的Element數(shù)組dexElements。當(dāng)要查找類(lèi)的時(shí)候申尼,會(huì)遍歷Element數(shù)組dexElements(相當(dāng)于遍歷dex文件數(shù)組)垮卓,Element的findClass方法會(huì)調(diào)用DexFile的loadClassBinaryName方法查找類(lèi),如果在Element(dex文件)中找到該類(lèi)就返回师幕,如果沒(méi)有就接著在下一個(gè)Element中進(jìn)行查找粟按。
現(xiàn)在我們就可以模擬這個(gè)加載流程,我們將有Bug的類(lèi)Key.class進(jìn)行修改霹粥,再將Key.class打包成包含dex的補(bǔ)丁包Patch.jar灭将,放在Element數(shù)組dexElements的第一個(gè)元素,這樣就首先找到Patch.dex中的Key.class去替換之前存在Bug的Key.class.排在數(shù)組后面的dex文件中存在bug的Key.class根據(jù)ClassLoader的雙親委托機(jī)制就不會(huì)被加載后控,這就是類(lèi)加載方案庙曙。
類(lèi)加載方案需要重啟App后讓ClassLoader重新加載新的類(lèi),為什么需要重啟忆蚀?這是因?yàn)轭?lèi)是無(wú)法被卸載的矾利,要想重新加載新的類(lèi)就需要重啟App,因此采用類(lèi)加載方案的熱修復(fù)框架是不能即時(shí)生效的馋袜。
雖然都是采用類(lèi)加載方案男旗,但是具體的實(shí)現(xiàn)方案還是有一定區(qū)別的。比如 QQ空間的超級(jí)補(bǔ)丁和Nuwa是和上面一樣將補(bǔ)丁包放在Element數(shù)組的第一個(gè)元素得到優(yōu)先加載欣鳖。微信Tinker是將新舊Apk做了diff察皇,得到patch.dex,再將patch.dex與手機(jī)中的classes.dex做合并泽台,生成新的classes.dex什荣,然后在運(yùn)行時(shí)通過(guò)反射將classes.dex放在Element數(shù)組中的第一個(gè)元素。
采用類(lèi)加載方案的主要是騰訊系為主怀酷。
底層替換方案
與類(lèi)加載方案不同稻爬,底層替換方案不會(huì)再次加載新類(lèi),而是直接在Native層修改原有類(lèi)蜕依。這種方案限制比較多桅锄,且不能增減原有類(lèi)的方法和字段,如果增加了方法數(shù)样眠,那么方法索引數(shù)也會(huì)增加友瘤,這樣訪問(wèn)方式時(shí),就不能通過(guò)索引找到正確的方法檐束。
底層替換方案和反射的原理有些關(guān)聯(lián)辫秧,這里拿方法反射來(lái)說(shuō),方法反射可以調(diào)用java.lang.Class.getDeclaredMethod獲取對(duì)應(yīng)方法被丧,調(diào)用的invoke方法是native方法盟戏,其本質(zhì)是替換Native的ArtMethod結(jié)構(gòu)體中的字段或者整個(gè)ArtMethod結(jié)構(gòu)體绪妹,這就是底層替換方法的底層實(shí)現(xiàn)。這種實(shí)現(xiàn)由比較大的局限性抓半,就是各個(gè)廠商可能會(huì)修改ArtMethod結(jié)構(gòu)體喂急,導(dǎo)致方法替換失敗。Sophix采用的是直接替換整個(gè)ArtMethod結(jié)構(gòu)體笛求,這樣就不存在兼容問(wèn)題廊移。底層替換方法直接替換了方法,可以立即生效不需要重啟探入。
采用底層替換方案主要以阿里系為主狡孔,包括AndFix、Dexposed蜂嗽、Sophix苗膝、阿里百川。
Instant Run方案
這種方案主要借助ASM(還有Aspectj)這個(gè)字節(jié)碼操控框架植旧,它能夠動(dòng)態(tài)生成類(lèi)或者增強(qiáng)現(xiàn)有類(lèi)的功能辱揭。可以直接產(chǎn)生class文件病附,也可以在類(lèi)被加載到虛擬機(jī)之前動(dòng)態(tài)改變類(lèi)的行為问窃。
動(dòng)態(tài)鏈接庫(kù)的修復(fù)
Android平臺(tái)的動(dòng)態(tài)鏈接庫(kù)主要就是指so庫(kù),熱修復(fù)框架so的修復(fù)主要就是重新加載so完沪,因此so的修復(fù)的基礎(chǔ)原理就是加載so域庇。
加載so主要用到的就是System類(lèi)的load和loadLibrary方法。目前so的修復(fù)都是基于這兩個(gè)方法覆积。
load方法和loadLibrary方法最終都會(huì)調(diào)用native層的nativeLoad方法听皿。nativeLoad加載so庫(kù)的最終實(shí)現(xiàn)是LoadNativeLibrary函數(shù),此函數(shù)主要做了三個(gè)工作:
①判斷so是否被加載過(guò)宽档,兩次ClassLoader是否是同一個(gè)尉姨,避免so重復(fù)加載。
②打開(kāi)so并得到so句柄吗冤,如果so句柄獲取失敗又厉,就返回false。
③查找JNI_OnLoad函數(shù)指針欣孤,根據(jù)不同情況設(shè)置was_successful的值馋没,最終返回該值昔逗,通知上層是成功還是失敗降传。
總結(jié)一下,so修復(fù)的兩個(gè)方案:
①將so補(bǔ)丁插入到NativeLibraryElement數(shù)組的前部勾怒,讓so補(bǔ)丁的路徑先被返回和加載婆排。
②調(diào)用System的load方法來(lái)接管so的加載入口声旺。
第十四章 Hook技術(shù)
Hook技術(shù)主要用到逆向工程中。逆向分析分為靜態(tài)分析和動(dòng)態(tài)分析段只。靜態(tài)分析指的是一種在不執(zhí)行程序的情況下對(duì)程序行為進(jìn)行分析的技術(shù)腮猖;動(dòng)態(tài)分析是指在程序運(yùn)行時(shí)對(duì)程序進(jìn)行調(diào)試的技術(shù)。Hook技術(shù)就屬于動(dòng)態(tài)分析赞枕。
正常的A調(diào)用B澈缺,B處理后將結(jié)果返回個(gè)A.而Hook技術(shù)可以攔截事件。這里的Hook可以是一個(gè)方法或者一個(gè)對(duì)象炕婶,它想鉤子一樣掛在A和B之間姐赡。當(dāng)A調(diào)用B,B返回結(jié)果給A柠掂。Hook就可以在中間起到“欺上瞞下”的作用项滑。
為了保證Hook的穩(wěn)定性,Hook點(diǎn)一般選擇容易找到并且不易變化的對(duì)象枪狂,靜態(tài)變量和單例就符合這一條件宋渔。(因?yàn)樵创a容易改變,而單例和靜態(tài)變量因?yàn)橐玫牡胤捷^多,所以不容易被sdk更新迭代掉钾恢。)
Hook的分類(lèi)
(1)按照API語(yǔ)言劃分
Hook Java :主要通過(guò)反射和代理實(shí)現(xiàn),應(yīng)用SDK開(kāi)發(fā)環(huán)境中修改Java代碼。
Hook Native:應(yīng)用于NDK開(kāi)發(fā)棚贾,修改Native代碼塘偎。
(2)進(jìn)程劃分
應(yīng)用程序Hook:只能Hook當(dāng)前所在的應(yīng)用程序進(jìn)程
全局Hook:對(duì)Zygote進(jìn)行Hook,可以實(shí)現(xiàn)Hook系統(tǒng)所有的應(yīng)用程序進(jìn)程。(因?yàn)閼?yīng)用程序進(jìn)程都是Zygote fork出來(lái)的。)
(3)實(shí)現(xiàn)劃分
通過(guò)反射和代理實(shí)現(xiàn)欠雌,只能Hook當(dāng)前的應(yīng)用程序進(jìn)程蝗拿。
通過(guò)Hook框架實(shí)現(xiàn),比如Xposed蒿涎,可以實(shí)現(xiàn)全局Hook哀托,但是需要root。
插件化原理
1.Activity插件化
Activity插件化視頻地址
![QN`XCC$AEGS)5M0JXT4O.png
要想跳轉(zhuǎn)插件里的Activity劳秋,如果我們通過(guò)正常的startActivity去跳的話仓手,是可行的,因?yàn)樘D(zhuǎn)的時(shí)候玻淑,AMS會(huì)檢測(cè)要跳轉(zhuǎn)的Activity是不是我們項(xiàng)目中清單文件中注冊(cè)的Activity俗或,如果不是則會(huì)拋出異常。(主要是沒(méi)有注冊(cè)插件的Activity岁忘,也沒(méi)辦法跨項(xiàng)目注冊(cè))
而插件化的思想就是檢測(cè)的時(shí)候辛慰,我們將插件的Activity替換成我們項(xiàng)目中代理的一個(gè)Activity,在通過(guò)校驗(yàn)后干像,再將代理Activity替換稱為我們插件的Activity帅腌,這就是Activity插件化的基本思想。(注意前后要保留intent中參數(shù))
Activity插件化主要有3種實(shí)現(xiàn)方式:
①反射實(shí)現(xiàn)
②接口實(shí)現(xiàn)
③Hook技術(shù)實(shí)現(xiàn)
反射會(huì)對(duì)性能有影響麻汰,目前主流的插件化框架沒(méi)有采用這種方式速客。
接口的可以參照dynamic-load-apk源碼。
這里我們就可以通過(guò)Hook技術(shù)來(lái)實(shí)現(xiàn)這種"偷天換日"的操作五鲫。這里就要用到兩次Hook技術(shù)溺职。目前有兩種方案:
①Hook IActivityManager來(lái)實(shí)現(xiàn)。
②Hook Instrumentation實(shí)現(xiàn)位喂。
先跟著視頻看Hook IActivityManager實(shí)現(xiàn):
我們可以借助Intent浪耘,setComponet方法來(lái)進(jìn)行隱式跳轉(zhuǎn)。而如果想把PluginActivity替換成ProxyActivity塑崖,實(shí)際上只要把Intent的packgeName和ActivityName替換成代理Activity的包名和類(lèi)名就可以七冲。
我們我們的目的就是找到方便替換intent的地方。
接下來(lái)我們就查一下源碼规婆,看看到底哪些地方適合修改intent:
這兩個(gè)地方都可以替換澜躺,第一個(gè)點(diǎn)就是替Instrumentation蝉稳;第二個(gè)點(diǎn)就是替換IActivityManager;第二張圖可以看到調(diào)用了AMS掘鄙,而且這里還是一個(gè)靜態(tài)的耘戚,這里就是用使用動(dòng)態(tài)代理去替換intent。
代理的就是這里的startActivity方法操漠。這里我們就需要?jiǎng)?chuàng)建一個(gè)對(duì)象收津,替換掉ActivityManager.getService()這個(gè)對(duì)象。
這里就可以用動(dòng)態(tài)代理來(lái)進(jìn)行操作了颅夺,
接下來(lái)還要用這個(gè)代理的對(duì)象替換系統(tǒng)這個(gè)對(duì)象;這個(gè)稍后補(bǔ)充蛹稍;我們先做一下invoke里替換intent的操作:
簡(jiǎn)單說(shuō)一下第一步Hook對(duì)intent的處理吧黄;
①需要遍歷startActivity方法,拿到我們所需要的intent的參數(shù)唆姐;這個(gè)intent是插件的intent拗慨,因?yàn)槲覀兊谝徊紿ook的目的就是將插件的intent替換成代理的intent,以通過(guò)AMS的檢查
②新建代理的intent奉芦,設(shè)置代理的包名和類(lèi)名赵抢,并替換掉startActivity的intent參數(shù)
③需要將原有的intent也就是插件的intent保留,因?yàn)槲覀兊诙紿ook需要將intent替換回插件的intent声功,這樣我們才可以跳轉(zhuǎn)到插件的activity中烦却,這樣原有intent里的內(nèi)容就不會(huì)丟失,putExtra放進(jìn)去先巴。
下面就是用動(dòng)態(tài)代理的這個(gè)對(duì)象替換掉系統(tǒng)中原有的IActivityManager對(duì)象其爵,這樣我們才能執(zhí)行到這個(gè)動(dòng)態(tài)代理對(duì)象的邏輯,所以我們就需要找到系統(tǒng)中這個(gè)IActivityManager對(duì)象的field伸蚯,通過(guò)反射拿到摩渺,再用動(dòng)態(tài)代理替換掉。
所以我們要替換掉的就是這里的ActivityManager.getService()
可以看到這個(gè)對(duì)象獲取是在:
IActivityManagerSingleton.get();
這個(gè)方法中剂邮,我們看一下這個(gè)方法:可以看到我們需要做的就是拿到mInstance摇幻,然后替換掉它。
要拿mInstance挥萌,就要拿到這個(gè)Field绰姻,因?yàn)槭欠庆o態(tài)的,所以我們還需要拿到這個(gè)字段的對(duì)象引瀑,也就是Singleton對(duì)象:
那么我們就要看Singleton對(duì)象是在哪創(chuàng)建的龙宏,回到上一步:
這個(gè)IActivityManagerSingleton就是我們的Singleton對(duì)象,而且這還是這個(gè)靜態(tài)的對(duì)象I烁怼(當(dāng)然還是要通過(guò)ActivityManager這個(gè)類(lèi))
接下來(lái)我們就是具體獲取的代碼了银酗,分以為幾步:
①通過(guò)反射獲取到ActivityManager對(duì)象
②拿到該類(lèi)中的IActivityManager對(duì)象辆影,因?yàn)槭庆o態(tài)的,所以Field get的時(shí)候直接傳入null即可
③拿到Singleton類(lèi)
④拿到對(duì)應(yīng)的Field也就是mInstanceField黍特,因?yàn)檫@個(gè)是非靜態(tài)的蛙讥,我們需要傳入具體對(duì)象,也就是IActivityManager對(duì)象灭衷,從而拿到mInstance
(這四步就是為了拿到系統(tǒng)的IActivityManager對(duì)象)
⑤拿到系統(tǒng)的mInstance對(duì)象后次慢,我們invoke方法中的method.invoke的第一個(gè)參數(shù)就可以傳入mInstance
⑥這時(shí)候,我們的反射和代理都做完了翔曲,接下來(lái)就是將代理對(duì)象替換掉我們系統(tǒng)的對(duì)象迫像,也就是將mInstanceField的值由singleton對(duì)象替換稱為我們的代理對(duì)象。
第一步將PluginActivity替換稱為ProxyActivity以跳過(guò)AMS的檢查我們已經(jīng)做好了瞳遍。下面就是第二步闻妓,在檢測(cè)過(guò)后打開(kāi)Activity的時(shí)候,再將ProxyActivity替換稱為我們的PluginActivity掠械。
這里就涉及到Activity的啟動(dòng)流程了:
首先調(diào)用ActivityThread里的handleLaunchActivity由缆,因?yàn)橐鎿Qintent,所以我們性需要找到intent所在的地方:
可以看到這個(gè)intent是存儲(chǔ)在ActivityClientRecord里的猾蒂。
接下來(lái)我們就一下看這個(gè)intent從哪來(lái)的均唉,回溯:
不要搞錯(cuò)了,我們要找到的intent是ActivityClientRecord里的intent肚菠,而不是這個(gè)customIntent徐许》税可以看到這個(gè)ActivityClientRecord是msg.obj里帶過(guò)來(lái)的:
如果我們能拿到msg,就可以成功操作intent。所以我們需要拿到這個(gè)msg肯污。
這里我們看一下handler:
這里的mCallback實(shí)際上一般都是空的册养,因?yàn)槠滟x值的地方在它的三參構(gòu)造方法中亏吝,而我們一般使用Handler是用的無(wú)參構(gòu)造方法:
所以說(shuō)抗俄,這個(gè)mCallback正常情況下是空的,如果不為空的話奢讨,在dispatchMessage里就會(huì)直接走mCallback.handleMessage的情況稚叹,然后return掉,不會(huì)調(diào)用下面的handleMessage方法拿诸。
所以我們的思路就是創(chuàng)建一個(gè)Callback對(duì)象替換系統(tǒng)的Callback(通過(guò)反射替換系統(tǒng)的對(duì)象),這樣一來(lái)扒袖,替換的步驟就大致出來(lái)了:
替換Callback.msg-->handleMessage(msg)-->msg.obj-->獲取ActivityClientRecord對(duì)象-->Intent
剛才我們替換的思路就是想要替換intent,那就必須拿到ActivityClientRecord對(duì)象亩码,要拿到這個(gè)對(duì)象季率,就要拿到msg.obj,為了拿到msg.obj,就要去修改dispatchMessage里的msg描沟,為了修改飒泻,就需要?jiǎng)?chuàng)建一個(gè)Callback替換掉Handler里空的Callback鞭光。
具體步驟:
①新建Handler.Callback,進(jìn)行intent的替換:
將代理的intent替換為插件的intent。
②獲取對(duì)應(yīng)的Handler泞遗,替換調(diào)用Handler里的Callback惰许。
所以我們要替換的就是這里的mH,而這個(gè)mH又是ActivityThread里的對(duì)象史辙,所以我們?cè)俅酥斑€需要獲取ActivityThread對(duì)象:
我們要獲取的就是這個(gè)sCurrentActivityThread.(有個(gè)疑問(wèn)汹买,為什么不直接反射ActivityThread,直接調(diào)用mH呢聊倔?)
具體操作:
這樣一來(lái)mH這個(gè)Handler的mCallback字段就被替換成我們自己實(shí)現(xiàn)的Handler.Callabck了晦毙。
當(dāng)下還有問(wèn)題,就是所有的startActivity都會(huì)走這個(gè)耙蔑,我們應(yīng)該做一下進(jìn)程過(guò)濾见妒,只有進(jìn)程不同的時(shí)候,才走這塊邏輯纵潦。
版本適配
6.0和9.0底層實(shí)現(xiàn)有所不同徐鹤,我們需要做一下具體的版本適配:
可以看到6.0沒(méi)有ActivityManagerService.getService(),而是
ActivityManagerService.getDefault().startActivity...
區(qū)別只在于這個(gè)api調(diào)用的名字不太一樣垃环,
主要看一下Android9.0,9.0從AMS出來(lái)后邀层,并不會(huì)走H類(lèi)的handleMessage,而是走到了ActivityThread里的
EXECUTE_TRANSACTION
消息處理中去了:時(shí)序圖:
從ActivityStackSupervisor的realStartActivityLocked入手:
可以看到我們實(shí)際要處理的就是這個(gè)EXECUTE_TRANSACTION
這個(gè)事件:
9.0適配邏輯如下遂庄,不做詳情分析了就:
一般Activity啟動(dòng)流程(不是根Activity)
Activity的啟動(dòng)流程寥院,可以從Context的startActivity說(shuō)起,其實(shí)現(xiàn)是ContextImpl的startActivity涛目。然后內(nèi)部會(huì)通過(guò)Instrumentation來(lái)嘗試啟動(dòng) Activity秸谢,它會(huì)調(diào)用AMS的startActivity方法,這是一個(gè)跨進(jìn)程過(guò)程霹肝,當(dāng)AMS校驗(yàn)完activity的合法性后估蹄,會(huì)通過(guò)ApplicationThread回調(diào)到我們的進(jìn)程中,這也是一個(gè)跨進(jìn)程過(guò)程沫换,而applicationThread就是一個(gè)binder臭蚁,回調(diào)邏輯是在binder線程池中完成的,所以需要Handler H將其切換到ui線程讯赏,第一個(gè)消息是LAUNCH_ACTIVITY,它對(duì)應(yīng)handleLaunchActivity垮兑,在這個(gè)方法里完成了Activity的創(chuàng)建和啟動(dòng)。(8.0)
資源加載插件化
我們分三步進(jìn)行分析:
①Resources和AssetManager的關(guān)系
②資源加載流程源碼分析
③手寫(xiě)實(shí)現(xiàn)插件的資源加載
Resources和AssetManager的關(guān)系
可以看到漱挎,Resources類(lèi)實(shí)際上也是通過(guò)AssetManager類(lèi)來(lái)訪問(wèn)那些被編譯過(guò)的應(yīng)用程序資源文件的系枪,不過(guò)在訪問(wèn)之前,它會(huì)先根據(jù)資源ID查找得到對(duì)應(yīng)的資源文件名磕谅。而AssetManager對(duì)象即可以通過(guò)文件名訪問(wèn)那些被編譯過(guò)的私爷,也可以范文沒(méi)有被編譯過(guò)的應(yīng)用程序資源文件雾棺。
這樣我們就可以通過(guò)AssetManager來(lái)加載插件中的資源
面試題:raw文件夾和assets文件夾有什么區(qū)別?
raw:Android會(huì)自動(dòng)為這個(gè)目錄中的所有文件生成一個(gè)ID当犯,這一味這很容易就可以訪問(wèn)到這個(gè)資源垢村,甚至在xml中都是可以訪問(wèn)的,使用ID訪問(wèn)是最快的嚎卫。
assets:不會(huì)生成ID嘉栓,只能通過(guò)AssetManager訪問(wèn),xml中不能訪問(wèn)拓诸,訪問(wèn)速度會(huì)慢些侵佃,不過(guò)操作更加方便。
getResources方法是context的方法奠支,而Context又分為Activity的Context和Application的Context馋辈。
所以我們花開(kāi)兩朵各表一枝:
我們先從Activity的Context去找,查找路徑如下:
Activity--Context--Resources--AssetManager
接下來(lái)我們需要找到Context和Resources是在哪綁定的倍谜,在Activity啟動(dòng)流程中迈螟,AMS會(huì)走到ActivityThread的handleLaunchActivity中,接著調(diào)用performLaunchActivity尔崔,在這個(gè)方法中答毫,會(huì)調(diào)用:
createBaseContextForActivity
方法創(chuàng)建ContextImpl,也就是Activity的Context:最終會(huì)調(diào)用context.setResources方法將Resource綁定到context中季春,而這里的Resource就是通過(guò)
resourcesManager.createBaseActivityResource
方法創(chuàng)建Resource的洗搂。這里創(chuàng)建了ResourceKey,傳入了resDir參數(shù)载弄,這個(gè)參數(shù)就是資源目錄耘拇。
這個(gè)key會(huì)作為參數(shù)傳入getOrCreateResource方法中去:
可以看到在ResourceManager的createAssetManager方法中,會(huì)調(diào)用:
assets.addAssetPath(key.mResDir)
方法將資源放到AssetManager里面去宇攻。所以我們Hook點(diǎn)就找到了惫叛,就是這個(gè)addAssetPath方法,這里加載的宿主的資源逞刷。如果我們調(diào)用:
assets.addAssetPath(插件的資源),這里加載的就是插件的資源了嘉涌。
這里就有兩個(gè)方法:
①創(chuàng)建:不會(huì)有資源的沖突,可能會(huì)有代碼的沖突(再創(chuàng)建一個(gè)AssetManager亲桥,專(zhuān)門(mén)加載插件的資源)
②合并:插件+宿主的資源洛心,可能會(huì)有資源的沖突(AAPT處理)
這里我們采用創(chuàng)建方案。
接下來(lái)就是擼碼來(lái)實(shí)現(xiàn)資源的插件化了:
大致思路如上圖所示题篷。