Android插件化基礎(chǔ)1-----加載SD上APK

Android插件化基礎(chǔ)的主要內(nèi)容包括

Android插件化基礎(chǔ)1-----加載SD上APK

Android插件化基礎(chǔ)2----理解Context

Android插件化基礎(chǔ)3----Android的編譯打包APK流程詳解

Android插件化基礎(chǔ)4----APK安裝流程詳解0——前言

Android插件化基礎(chǔ)5----Rsources.arsc詳解(請期待)

Android插件化基礎(chǔ)6----Android的資源系統(tǒng)(請期待)

Android插件化基礎(chǔ)7----Activity的啟動流程(請期待)

Android插件化基礎(chǔ)8----如何啟動一個(gè)沒有注冊過的Activity(請期待)

Android插件化基礎(chǔ)9----Service的啟動流程(請期待)

Android插件化基礎(chǔ)10----BroadcastReceiver源碼解析(請期待)


本文是第一篇文章,主要是講解如何加載SD卡上的apk中的class

本文涉及的內(nèi)容如下:

1.java的類加載與雙親委托

2.android apk安裝簡述

3.demo演示

4.demo背后的故事----android的類加載流程(重點(diǎn))

5.總結(jié)

6.github地址

一、Java類加載介紹

先來復(fù)習(xí)下Java類加載的事情,對Java類加載很熟悉的朋友可以直接略過第一部分,直接從第二部分開始

(一)什么是ClassLoader:

我們寫完一個(gè)java程序后,通過編譯,形成若干個(gè).class文件,而這些若干個(gè).class文件組織成一個(gè)完成的java程序,當(dāng)程序運(yùn)行時(shí),都會調(diào)用一個(gè)入口函數(shù)來調(diào)用系統(tǒng)的相關(guān)功能,而這些功能都被封裝在不同的class文件當(dāng)中,所以經(jīng)常要從一個(gè)class文件調(diào)用到另外一個(gè)class文件的某個(gè)方法,如果另外一個(gè)class不存在,則會引發(fā)系統(tǒng)異常。而在程序啟動的時(shí)候,不會一次性加載程序所有的class文件,而是根據(jù)程序的需要,通過java類加載機(jī)制(ClassLoader)來動態(tài)加載某個(gè)class文件到內(nèi)存中,從而只有class被記載到內(nèi)存之后,才能被其他class所引用。所以ClassLoader就是用來動態(tài)加載class文件到內(nèi)存當(dāng)中用到的

(二)ClassLoader的作用:

1 負(fù)責(zé)將Class加載到JVM中

2 審查每個(gè)類由誰加載(父類優(yōu)先的等級加載機(jī)制)

3 將Class字節(jié)碼重新解析成JVM統(tǒng)一要求的對象格式

(三)ClassLoader類的結(jié)構(gòu)分析

為了更好的理解類加載機(jī)制,我們來深入研究下ClassLoader和他的方法

ClassLoader 類是一個(gè)抽象類

public abstractclassClassLoader/**? * A classloader isan object thatisresponsible forloading classes. The

* classClassLoader isan abstract class.? Given thebinary nameofa

*class, a classloader should attempt to* locate orgenerate data that

*constitutes a definition fortheclass.? A

* typical strategy istotransform thenameintoa filenameandthenreada

* "class file"ofthatnamefroma filesystem. **/

大致的意思是: ClassLoader 是一個(gè)負(fù)責(zé)加載classes的對象,ClassLoader類是一個(gè)抽象類,需要給出類的二進(jìn)制名稱,ClassLoader嘗試定位或者產(chǎn)生一個(gè)class數(shù)據(jù),一個(gè)典型的策略是把二進(jìn)制名字轉(zhuǎn)換成文件名然后到文件系統(tǒng)中找到該文件

以下是ClassLoader常用到的幾個(gè)方法及其重載方法:

1 ClassLoader

2 defineClass(byte[] ,int ,int )把字節(jié)數(shù)組b中的內(nèi)容轉(zhuǎn)換成Java類,返回* 的結(jié)果是java.lang.Class類的實(shí)例,這個(gè)方法被聲明為final的

3 findClass(String name)查找名稱為name類,返回結(jié)果java.lang.Class類的實(shí)例

4 loadClass(String name) 加載名稱為name的類,返回的結(jié)果是java.lang.Class類的實(shí)例

5 resolveClass(Class)? 鏈接指定Java類

其中defineClass方法用來將byte字節(jié)流解析成JVM能夠識別的Class對象,有了這個(gè)方法意味著我們不僅僅可以通過class文件實(shí)例化對象,還可以通過其他方式實(shí)例化對象,如果我們通過網(wǎng)絡(luò)接受到一個(gè)類的字節(jié)碼,拿到這個(gè)字節(jié)碼流直接創(chuàng)建類的Class對象形式實(shí)例化對象。如果直接調(diào)用這個(gè)方法生成類的Class對象,這個(gè)對象Class對象還沒有resolve,這個(gè)resolve將會在這個(gè)對象真正實(shí)例化時(shí)才進(jìn)行

(三)Java默認(rèn)提供的三個(gè)ClassLoader

1 BootStrap ClassLoader:稱為啟動類加載器,是java類加載層次中最高層次的類加載器,負(fù)責(zé)加載JDK中的核心類庫,如:rt.jar,resource.jar,charsets.jar等,可通過如下程序獲得該類加載器從哪些地方加載了相關(guān)jar或clas

2 Extension ClassLoader:稱為擴(kuò)展類加載器,負(fù)責(zé)加載java的擴(kuò)展類苦苦,java虛擬機(jī)的實(shí)現(xiàn)會提供一個(gè)擴(kuò)展目錄,該類加載器在此目錄里面查找并加載java類。默認(rèn)加載JAVA_HOME/jre/lib/ext/目錄下的所有jar

3 App ClassLoader:稱為系統(tǒng)類加載器,負(fù)責(zé)加載應(yīng)用程序classpath目錄下所有jar和class文件,一般來說,Java應(yīng)用的類都是由它們來完成加載的。可以通過ClassLoader.getSystemClassLoader()來獲取它。

除了系統(tǒng)提供的類加載器以外,開發(fā)人員也可以通過集成java.lang.ClassLoader類的方式實(shí)現(xiàn)自己的類加載器,以滿足一些特殊的需求。

(四)ClassLoader加載類的原理----雙親委托模型:

1原理介紹:

ClassLoader使用的是雙親委托模型來搜索類的,每個(gè)ClassLoader實(shí)例都有一個(gè)父類加載器的引用(不是繼承關(guān)系,是一個(gè)包含的關(guān)系),虛擬機(jī)內(nèi)置的類加載器(Bootstrap ClassLoader)本身沒有父類加載器,但是可以作用于其他ClassLoader實(shí)例的父類加載器。當(dāng)一個(gè)ClassLoader實(shí)例需要加載某個(gè)類時(shí),它會試圖親自搜索某個(gè)類之前,先把這個(gè)任務(wù)委托給它的父類加載器,這個(gè)過程是由上到下依次檢查的,首先由最頂層的類加載器,這個(gè)過程是由上至下依次檢查的,首先由最頂層的類加載器Bootstrap ClassLoader試圖加載,如果沒有加載到,則把任務(wù)轉(zhuǎn)交給Extension ClassLoader試圖加載,如果也沒加載到,則轉(zhuǎn)交給app ClassLoader進(jìn)行加載。如果他沒有家在得到的話,則返回給委托的發(fā)起者,由它到制定的文件系統(tǒng)或者網(wǎng)絡(luò)等URL加載該類,如果他們都沒有加載這個(gè)類,則票拋出ClassNotFoundException異常,否則將這個(gè)找到的類生成一個(gè)類的定義,并將它加載到內(nèi)存當(dāng)中,最后返回這個(gè)類在內(nèi)存中的Class實(shí)例對象。如下圖

class_image.png

2為什么要使用雙親委托這種模型?:

因?yàn)檫@樣可以避免重復(fù)加載,當(dāng)父親已經(jīng)加載了該類的時(shí)候,就沒有必要ClassLoader再加載一次了,考慮到安全因素,我們試想一下,如果不使用這種委托模式,那我們就可以隨時(shí)使用自定義的String來動態(tài)替代java核心api中定義的類型,這樣會存在非常大安全隱患,而雙親委托可以避免這種情況,因?yàn)镾tring已經(jīng)在啟動的時(shí)候就被引導(dǎo)類加載器(Bootstrap ClassLoader)加載,所以用戶自定義的ClassLoader永遠(yuǎn)也無法加載一個(gè)自己寫的String,除非你改變JDK中的ClassLoader的搜索類默認(rèn)算法。

二.android apk安裝簡述

(一)android 打包簡述

Android應(yīng)用打包成apk時(shí),class文件會被打包成一個(gè)或者多個(gè)dex文件,將一個(gè)apk文件后綴改成.zip格式后解壓;里面有class.dex文件,由于android64K方法數(shù)的問題,使用MultiDex就會生成多個(gè)dex文件。如下圖

image.png

當(dāng)Android 系統(tǒng)安裝包安裝一個(gè)應(yīng)用的時(shí)候,會針對不同的平臺對Dex進(jìn)行優(yōu)化,這個(gè)過程由一個(gè)專門的工具來處理叫DexOpt。DexOpt是第一次加載Dex文件的時(shí)候執(zhí)行,該過程會生成一個(gè)ODEX文件,即Optimised Dex,執(zhí)行ODEX的效率會比直接執(zhí)行Dex文件的效率要高很多,加快App的啟動和響應(yīng)。

PS:

1 odex優(yōu)化有什么用:

ODEX的用途是分離程序資源和可執(zhí)行文件,達(dá)到快速軟件加載速度和開機(jī)速度的目的。

2 棒棒糖與ART帶來的問題

很多人會有疑問,Android5.0開始,默認(rèn)已經(jīng)使用ART,棄用Dalvik了,應(yīng)用程序會在安裝時(shí)被編譯成OAT文件,(ART上運(yùn)行的格式)ODEX還有什么用那?

這里我們引用google的權(quán)威回答:

Dex file compilation uses a tool called dex2oatandtakes more time than dexopt. The increaseintime varies, but2-3x increasesincompile time arenotunusual. For example, apps that typically take a second to install using dexopt might take2-3seconds.

這里解釋下:DEX轉(zhuǎn)換成OAT的這個(gè)過程是在用戶安裝程序或者刷入ROM,OTA更新后首次啟動時(shí)執(zhí)行的,按照google的說法,相比做過ODEX優(yōu)化,未做過優(yōu)化的DEX轉(zhuǎn)成成OAT要花費(fèi)更長的時(shí)間,比如2-3倍。比如安裝一個(gè)odex優(yōu)化過的程序假設(shè)需要1秒鐘,未做過優(yōu)化的程序就需要2-3秒。由此次可見,雖然dalvik被棄用了,但是ODEX優(yōu)化在Android棒棒糖上依舊擁有顯著的優(yōu)化效果。首先ODEX優(yōu)化不僅僅只是針對應(yīng)用程序,還會對內(nèi)核鏡像,jar庫文件等進(jìn)行優(yōu)化。其次,資源和可執(zhí)行文件分離帶來的性能提升無論是運(yùn)行在ART還是Dalvik,都有效。

(二)android 安裝

下載好的Android apk, 在安裝過程中,其中文件內(nèi)容是這樣處理的:

1? 先把a(bǔ)pk拷貝到/data/app下, 沒錯(cuò),就是完整的apk, 例如

com.test.demo-2.apk

2 解壓apk,把其中的classes.dex 拷貝到/data/dalvik-cache, 其命名規(guī)則是 apk路徑+classes.dex, 如: data/app/com.test.demo2.apk/classes.dex

3 在/data/data下創(chuàng)建對應(yīng)的目錄,用于存儲程序的數(shù)據(jù),例如cache, database等, 目錄名稱與包名相同, 如com.test.demo.

要注意的是, 安裝過程并沒有把資源文件, assets目錄下文件拷貝出來,他們還在apk包里面呆著,所以,當(dāng)應(yīng)用要訪問資源的時(shí)候,其實(shí)是從apk包里讀取出來的。其過程是,首先加載apk里的resources(這個(gè)文件是存儲資源Id與值的映射文件),根據(jù)資源id讀取加載相應(yīng)的資源。

由于本文主要是講解android類加載,android apk安裝過程就不詳細(xì)描述了

三 Demo演示 :

(一)先看下demo目錄

項(xiàng)目.jpeg

1 其中dexclassloaderapp是用來演示的運(yùn)行的app

2AppTest是用來打包成apk的項(xiàng)目

看下AppTest里面的目錄結(jié)構(gòu)

顯示.jpeg

分別看下 IDexTest,IDexTestImpl和string.xml

publicinterfaceIDexTest{StringgetText();}

publicclassIDexTestImplimplementsIDexTest{@OverridepublicStringgetText(){return"我是SD卡上的APK";? ? }}

AppTest我是SD上的字符串

當(dāng)AppTest被打包成apk的時(shí)候,我們要在dexclassloaderapp里面獲取這些數(shù)據(jù)? 。現(xiàn)在開始打包apk,名字為"AppTest-release.apk",然后把這個(gè)apk放到sd上。

現(xiàn)在rundexclassloaderapp項(xiàng)目會出現(xiàn)下面的顯示

device-1.png

點(diǎn)擊 測試加載類? 上面的textview會有原來的"類信息!"轉(zhuǎn)變"我是SD卡上的APK",證明已經(jīng)成功加載到SD卡上的apk

(ps:6.0手機(jī)注意權(quán)限,有的手機(jī)沒有開通權(quán)限會報(bào)找不到類)

類加載.gif

四demo背后的故事----android的類加載流程:

先看下dexclassLoaderapp是如何實(shí)現(xiàn)在家外部APK class的

具體實(shí)現(xiàn)是在load方法里面

privatevoidload(){// 獲取到包含 class.dex 的 jar 包文件finalFile apkFile =newFile(Environment.getExternalStorageDirectory().getPath() + File.separator +"apptest-release.apk");if(!apkFile.exists()) {? ? ? ? ? ? Log.e("LGC","文件不存在");? ? ? ? ? ? mHandler.post(newRunnable() {@Overridepublicvoidrun(){? ? ? ? ? ? ? ? ? ? pd.dismiss();? ? ? ? ? ? ? ? ? ? Toast.makeText(MainActivity.this,"文件不存在", Toast.LENGTH_LONG);? ? ? ? ? ? ? ? }? ? ? ? ? ? });return;? ? ? ? }if(!apkFile.canRead()) {// 如果沒有讀權(quán)限,確定你在 AndroidManifest 中是否聲明了讀寫權(quán)限// 如果是6.0以上手機(jī)要查看手機(jī)的權(quán)限管理,你的這個(gè)app是否具有讀寫權(quán)限Log.d("LGC","apkFile.canRead()= "+ apkFile.canRead());? ? ? ? ? ? mHandler.post(newRunnable() {@Overridepublicvoidrun(){? ? ? ? ? ? ? ? ? ? pd.dismiss();? ? ? ? ? ? ? ? ? ? Toast.makeText(MainActivity.this,"沒有讀寫權(quán)限", Toast.LENGTH_LONG);? ? ? ? ? ? ? ? }? ? ? ? ? ? });return;? ? ? ? }// getCodeCacheDir() 方法在 API 21 才能使用,實(shí)際測試替換成 getExternalCacheDir() 等也是可以的// 只要有讀寫權(quán)限的路徑均可Log.i("LGC","getExternalCacheDir().getAbsolutePath()="+ getExternalCacheDir().getAbsolutePath());? ? ? ? Log.i("LGC","apkFile.getAbsolutePath()="+ apkFile.getAbsolutePath());try{? ? ? ? ? ? DexFile dx = DexFile.loadDex(apkFile.getAbsolutePath(), File.createTempFile("opt","dex", getApplicationContext().getCacheDir()).getPath(),0);// Print all classes in the DexFilefor(Enumeration classNames = dx.entries(); classNames.hasMoreElements(); ) {? ? ? ? ? ? ? ? String className = classNames.nextElement();if(className.equals("com.yibao.test.IDexTestImpl")) {? ? ? ? ? ? ? ? ? ? Log.d("LGC","#########################################################"+ className);? ? ? ? ? ? ? ? ? ? Log.d("LGC", className);? ? ? ? ? ? ? ? ? ? Log.d("LGC","#########################################################"+ className);? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? Log.d("LGC","Analyzing dex content, fonud class: "+ className);? ? ? ? ? ? }? ? ? ? }catch(IOException e) {? ? ? ? ? ? Log.d("LGC","Error opening "+ apkFile.getAbsolutePath(), e);? ? ? ? }? ? ? ? DexClassLoader dexClassLoader =newDexClassLoader(apkFile.getAbsolutePath(), getExternalCacheDir().getAbsolutePath(),null, getClassLoader());try{// 加載 com.test.IDexTestImpl 類Class clazz = dexClassLoader.loadClass("com.test.IDexTestImpl");? ? ? ? ? ? Object dexTest = clazz.newInstance();? ? ? ? ? ? Method getText = clazz.getMethod("getText");finalString result = getText.invoke(dexTest).toString();? ? ? ? ? ? mHandler.post(newRunnable() {@Overridepublicvoidrun(){? ? ? ? ? ? ? ? ? ? pd.dismiss();if(!TextUtils.isEmpty(result)) {? ? ? ? ? ? ? ? ? ? ? ? tv.setText(result);? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }? ? ? ? ? ? });? ? ? ? }catch(ClassNotFoundException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }catch(InstantiationException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }catch(IllegalAccessException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }catch(NoSuchMethodException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }catch(InvocationTargetException e) {? ? ? ? ? ? e.printStackTrace();? ? ? ? }? ? }

大體的流程是先new 一個(gè)DexFile,然后loadClass就獲取了這個(gè)SD卡上的apk資源了,為什么可以這樣那?

因?yàn)锳ndroid Framework提供了DexClassLoader這個(gè)類,簡化了『通過一個(gè)類的全限定名獲取描述次類的二進(jìn)制字節(jié)流』這個(gè)過程;我們只需要告訴DexClassLoader一個(gè)dex文件或者apk文件的路徑就能完成類的加載。

詳細(xì)敘述請看下面

Android的Dalvik/ART虛擬機(jī)如果標(biāo)準(zhǔn)Java的JVM虛擬機(jī)一樣,在運(yùn)行程序時(shí)首先需要將對應(yīng)的類加載到內(nèi)存中。因此我們可以利用這一點(diǎn),在程序運(yùn)行時(shí)手動加載Class,從而達(dá)到代碼動態(tài)加載可執(zhí)行文建的目的。Android的Dalvik/ART虛擬機(jī)雖然與標(biāo)準(zhǔn)java的JVM不一樣,所以ClassLoader具體的加載細(xì)節(jié)不一樣,但是工作機(jī)制是類似的,也就是說在Android中同樣可以采用類似動態(tài)加載插件的功能,只是在Android應(yīng)用中動態(tài)加載一個(gè)插件的工作要比java復(fù)雜的多。

(一)android 類加載設(shè)計(jì)圖

結(jié)構(gòu)圖.jpg

(二)android類加載 類圖

image1.png

SecureClassLoader的子類是URLClassLoader,其只能用來加載jar文件,在android的Dal/ART上是沒法使用的,這里就不過多的介紹了!

classloader.jpg

這里面有兩個(gè)重要的類

PathClassLoader和DexClassLoader他們分別繼承BaseDexClassLoader,那他們的區(qū)別是什么?那么看下他們的構(gòu)造函數(shù)

PathDexClassLoader

publicclassPathClassLoader{publicPathClassLoader(String dexPath, ClassLoader parent){super(dexPath,null,null, parent);? }publicPathClassLoader(String dexPath, String libraryPath,

? ? ? ? ? ? ClassLoader parent){super(dexPath,null, libraryPath, parent);? ? }}

DexClassLoader

publicclassDexClassLoaderextendsBaseDexClassLoader{publicDexClassLoader(String dexPath , String? optimizedDirectory,String libraryPath, ClassLoader parent){super(dexPath,newFile(optimizedDirectory),? libraryPath,? parent);? }}

他們都是調(diào)用父類BaseDexClassLoader的構(gòu)造函數(shù)

BaseDexClassLoader

publicBaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent){super(parent);this.originalPath = dexPath;this.originalLibraryPath = libraryPath;this.pathList =newDexPathList(this, dexPath, libraryPath, optimizedDirectory); }

這四個(gè)參數(shù)分別的含義:

1 String dexPath

包含class.dex的apk、jar文件路徑,多個(gè)用文件分隔符(默認(rèn)是:)分隔

2 String? optimizedDirectory

用來緩存優(yōu)化的dex文件的路徑,即從apk或者jar文件中提取出來的dex文件。該路徑不可以為空,且應(yīng)該是應(yīng)用私有,有讀寫權(quán)限(實(shí)際上也可以使用外部存儲空間,但是這樣的話,有代碼注入風(fēng)險(xiǎn)),可以通過方式來穿件一個(gè)這樣的路徑.

3 String libraryPath

存儲C/C++庫文件的路徑集

4 ClassLoader parent

父類加載器,遵從雙親委托模型

很明顯,對比PathClassLoader只能加載已經(jīng)安裝應(yīng)用的dex或者Apk文件,DexClassLoader則沒有這個(gè)限制,可以從SD卡上加載包含class.dex的jar和.apk文件,也是插件化和熱修復(fù)的基礎(chǔ),在不需要安裝應(yīng)用的情況下,完成需要使用的dex的加載。DexClassLoader 的源碼里面只有一個(gè)構(gòu)造函數(shù),也是遵從雙親委托模型

簡單介紹了PathClassLoader和DexClassLoader,但這兩者都是對BaseDexClassLoader的一層簡單的封裝,真正的實(shí)現(xiàn)都在BaseClassLoader內(nèi),那么咱們看下BaseClassLoader內(nèi)的具體實(shí)現(xiàn)

(三)android 類加載類從類的角度來先看流程

通過上面的BaseDexClassLoader的構(gòu)造函數(shù),咱們知道了BaseDexClassLoader構(gòu)造的時(shí)候創(chuàng)建了一個(gè)DexPathList類的對象

那咱們就繼續(xù)跟蹤看下DexPathList這個(gè)的類的構(gòu)造函數(shù)

DexPathList

publicDexPathList(ClassLoader definingContext, String dexPath,

? ? ? ? ? ? ? ? ? ? ? String libraryPath, File optimizedDirectory){if(definingContext ==null) {thrownewNullPointerException("definingContext == null");? ? ? ? }if(dexPath ==null) {thrownewNullPointerException("dexPath == null");? ? ? ? }if(optimizedDirectory !=null) {if(!optimizedDirectory.exists())? {thrownewIllegalArgumentException("optimizedDirectory doesn't exist: "+ optimizedDirectory);? ? ? ? ? ? }if(!(optimizedDirectory.canRead()? ? ? ? ? ? ? ? ? ? && optimizedDirectory.canWrite())) {thrownewIllegalArgumentException("optimizedDirectory not readable/writable: "+ optimizedDirectory);? ? ? ? ? ? }? ? ? ? }this.definingContext = definingContext;this.dexElements =? ? ? ? ? ? ? ? makeDexElements(splitDexPath(dexPath), optimizedDirectory);this.nativeLibraryDirectories = splitLibraryPath(libraryPath);? ? }

代碼里面調(diào)用了makeDexElements()這個(gè)方法,其中一個(gè)參數(shù)是調(diào)用splitLibraryPath()方法的返回值。所以先看下splitLibraryPath()方法

privatestaticArrayListsplitDexPath(String path){returnsplitPaths(path,null,false);? ? }privatestaticArrayListsplitPaths(String path1, String path2,booleanwantDirectories){? ? ? ? ArrayList result =newArrayList();? ? ? ? splitAndAdd(path1, wantDirectories, result);? ? ? ? splitAndAdd(path2, wantDirectories, result);returnresult;? ? }privatestaticvoidsplitAndAdd(String path,booleanwantDirectories,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ArrayList resultList){if(path ==null) {return;? ? ? ? }? ? ? ? String[] strings = path.split(Pattern.quote(File.pathSeparator));for(String s : strings) {? ? ? ? ? ? File file =newFile(s);if(!(file.exists() && file.canRead())) {continue;? ? ? ? ? ? }/*

? ? ? ? ? ? * Note: There are other entities in filesystems than

? ? ? ? ? ? * regular files and directories.

? ? ? ? ? ? */if(wantDirectories) {if(!file.isDirectory()) {continue;? ? ? ? ? ? ? ? }? ? ? ? ? ? }else{if(!file.isFile()) {continue;? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? ? ? resultList.add(file);? ? ? ? }? ? }

splitDexPath這個(gè)方法里面調(diào)用splitPaths()方法,而splitPaths方法調(diào)用了splitAndAdd()方法,通過代碼查看,大概能明白,這個(gè)一系列的方法主要作用是過濾,過濾掉不可讀的file和不存在的file,即剩下的都是canRead且是exists的,然后吧這些files? add進(jìn)一個(gè)ArrayList,然把這個(gè)這個(gè)ArrayList作為參數(shù),調(diào)用makeDexElements這個(gè)方法,那么咱么一起看下這個(gè)方法

privatestaticfinalString DEX_SUFFIX =".dex";privatestaticfinalString JAR_SUFFIX =".jar";privatestaticfinalString ZIP_SUFFIX =".zip";privatestaticfinalString APK_SUFFIX =".apk";//上面是支持的后綴,由于在下面這個(gè)方法用到了,我就放到到這里privatestaticElement[] makeDexElements(ArrayList files, File optimizedDirectory) {? ? ? ? ArrayList elements =newArrayList();for(File file : files) {? ? ? ? ? ? ZipFile zip =null;? ? ? ? ? ? DexFile dex =null;? ? ? ? ? ? String name = file.getName();if(name.endsWith(DEX_SUFFIX)) {// Raw dex file (not inside a zip/jar).try{? ? ? ? ? ? ? ? ? ? dex = loadDexFile(file, optimizedDirectory);? ? ? ? ? ? ? ? }catch(IOException ex) {? ? ? ? ? ? ? ? ? ? System.logE("Unable to load dex file: "+ file, ex);? ? ? ? ? ? ? ? }? ? ? ? ? ? }elseif(name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)? ? ? ? ? ? ? ? ? ? || name.endsWith(ZIP_SUFFIX)) {try{? ? ? ? ? ? ? ? ? ? zip =newZipFile(file);? ? ? ? ? ? ? ? }catch(IOException ex) {? ? ? ? ? ? ? ? ? ? System.logE("Unable to open zip file: "+ file, ex);? ? ? ? ? ? ? ? }try{? ? ? ? ? ? ? ? ? ? dex = loadDexFile(file, optimizedDirectory);? ? ? ? ? ? ? ? }catch(IOException ignored) {? ? ? ? ? ? ? ? }? ? ? ? ? ? }else{? ? ? ? ? ? ? ? System.logW("Unknown file type for: "+ file);? ? ? ? ? ? }if((zip !=null) || (dex !=null)) {? ? ? ? ? ? ? ? elements.add(newElement(file, zip, dex));? ? ? ? ? ? }? ? ? ? }returnelements.toArray(newElement[elements.size()]);? ? }

上面的方法大概的意思是,遍歷剛上傳入的ArrayListM,如果是.dex結(jié)尾的直接調(diào)用loadDexFile方法,如果是.apk或者.zip或者.jar結(jié)尾的用這個(gè)File去構(gòu)造一個(gè)ZipFile對象,然后還是把這個(gè)ZipFile作為參數(shù)調(diào)用loadDexFile這個(gè)方法,那么咱們就去這個(gè)方法里面去看看

privatestaticDexFileloadDexFile(File file, File optimizedDirectory)throwsIOException{if(optimizedDirectory ==null) {returnnewDexFile(file);? ? ? ? }else{? ? ? ? ? ? String optimizedPath = optimizedPathFor(file, optimizedDirectory);returnDexFile.loadDex(file.getPath(), optimizedPath,0);? ? ? ? }? ? }privatestaticStringoptimizedPathFor(File path,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? File optimizedDirectory){? ? ? ? String fileName = path.getName();if(!fileName.endsWith(DEX_SUFFIX)) {intlastDot = fileName.lastIndexOf(".");if(lastDot <0) {? ? ? ? ? ? ? ? fileName += DEX_SUFFIX;? ? ? ? ? ? }else{? ? ? ? ? ? ? ? StringBuilder sb =newStringBuilder(lastDot +4);? ? ? ? ? ? ? ? sb.append(fileName,0, lastDot);? ? ? ? ? ? ? ? sb.append(DEX_SUFFIX);? ? ? ? ? ? ? ? fileName = sb.toString();? ? ? ? ? ? }? ? ? ? }? ? ? ? File result =newFile(optimizedDirectory, fileName);returnresult.getPath();? ? }

上面代碼有個(gè)設(shè)置如果optimizedDirectory==null(PS:PathClassLoader其中的optimizedDirectory就是null)則直接new一個(gè)DexFile,如果不為空則調(diào)用optimizedPathFor方法,optimizedPathFor就是把復(fù)制一份file放到

optimizedDirectory目錄下,最后把這個(gè)文件返回回去。

得到這個(gè)DexFile以后,用這個(gè)DexFile構(gòu)造一個(gè)Element對象

在makeDexElements的for循環(huán)里面依照上面的方法獲取一組DexFile,然后用這一組DexFile去組成Element數(shù)組對象放到內(nèi)存中。

上述僅僅是構(gòu)造DexClassLoader流程,下面咱們看下具體導(dǎo)入類的流程

loadClass()方法,由于DexClassLoader類本身就一個(gè)構(gòu)造函數(shù),所以知道這個(gè)方法是父類的方法,那么找下DexClassLoader的父類BaseDexClassLoader.java,結(jié)果發(fā)現(xiàn)BaseDexClassLoader.java也沒有這個(gè)方法,所以應(yīng)該在BaseDexClassLoader.java的父類里面,那么繼續(xù)尋找BaseDexClassLoader.java的父類ClassLoader里面有這個(gè)方法

publicClass loadClass(String name)throwsClassNotFoundException {returnloadClass(name,false);? ? }protectedClass loadClass(String name,booleanresolve)throwsClassNotFoundException{// First, check if the class has already been loadedClass c = findLoadedClass(name);if(c ==null) {longt0 = System.nanoTime();try{if(parent !=null) {? ? ? ? ? ? ? ? ? ? ? ? c = parent.loadClass(name,false);? ? ? ? ? ? ? ? ? ? }else{? ? ? ? ? ? ? ? ? ? ? ? c = findBootstrapClassOrNull(name);? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }catch(ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if(c ==null) {// If still not found, then invoke findClass in order// to find the class.longt1 = System.nanoTime();? ? ? ? ? ? ? ? ? ? c = findClass(name);// this is the defining class loader; record the stats}? ? ? ? ? ? }returnc;? ? ? }protectedClass findClass(String name)throwsClassNotFoundException {thrownewClassNotFoundException(name);? ? ? }

看上面的源代碼發(fā)現(xiàn)是先調(diào)用findLoadedClass(name) 看注釋應(yīng)該可以理解為檢查下是否已經(jīng)加載了,如果已經(jīng)加載了,則直接返回Class,如果沒有加載,則看看有沒有父類加載器,如果有父類加載器,則調(diào)用父類加載器的loadClass()方法,如果沒有父類加載器即根類加載器,通過根加載器加載(想下是不是上面說的雙親委托模型)。如果都沒有,則通過findClass()方法查找,那么進(jìn)入findClass()進(jìn)去看看,通過上面源代碼發(fā)現(xiàn)ClassLoader是個(gè)空方法,而DexClassLoader大家也知道,就一個(gè)構(gòu)造函數(shù),所以可以確定這個(gè)方法的具體實(shí)現(xiàn)在BaseClassLoader里面,那么咱們現(xiàn)在進(jìn)去BaseClassLoader里面看看

@OverrideprotectedClass findClass(String name)throwsClassNotFoundException {? ? ? ? List suppressedExceptions =newArrayList();? ? ? ? Class c = pathList.findClass(name, suppressedExceptions);if(c ==null) {? ? ? ? ? ? ClassNotFoundException cnfe =newClassNotFoundException("Didn't find class \""+ name +"\" on path: "+ pathList);for(Throwable t : suppressedExceptions) {? ? ? ? ? ? ? ? cnfe.addSuppressed(t);? ? ? ? ? ? }throwcnfe;? ? ? ? }returnc;? ? }

可以看到BaseDexClassLoader是通過pathList對象的findClass()方法來獲取類的,那么咱們繼續(xù)進(jìn)去DexPathList.java的findClass方法里面去看看

publicClassfindClass(String name, List suppressed){for(Element element : dexElements) {? ? ? ? ? ? DexFile dex = element.dexFile;if(dex !=null) {? ? ? ? ? ? ? ? Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);if(clazz !=null) {returnclazz;? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }if(dexElementsSuppressedExceptions !=null) {? ? ? ? ? ? suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));? ? ? ? }returnnull;? ? }

最后調(diào)用兩個(gè)DexFile的loadClassBinaryName來導(dǎo)入類的,現(xiàn)在進(jìn)入DexFIle.java中的loadClassBinaryName()方法中去看下

publicClassloadClassBinaryName(String name, ClassLoader loader, List suppressed){returndefineClass(name, loader, mCookie, suppressed);? ? }privatestaticClassdefineClass(String name, ClassLoader loader,intcookie,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? List suppressed){? ? ? ? Class result =null;try{? ? ? ? ? ? result = defineClassNative(name, loader, cookie);? ? ? ? }catch(NoClassDefFoundError e) {if(suppressed !=null) {? ? ? ? ? ? ? ? suppressed.add(e);? ? ? ? ? ? }? ? ? ? }catch(ClassNotFoundException e) {if(suppressed !=null) {? ? ? ? ? ? ? ? suppressed.add(e);? ? ? ? ? ? }? ? ? ? }returnresult;? ? }privatestaticnativeClassdefineClassNative(String name, ClassLoader loader,intcookie)throwsClassNotFoundException, NoClassDefFoundError;

通過上面可以看到DexFile的loadClassBinaryName()方法里面調(diào)用了defineClass()方法,而defineClass里面又調(diào)用natvie方法defineClassNative()在C層去加載類,由于涉及到底層的業(yè)務(wù),由于涉及到比較大的內(nèi)容,這里就不過多的敘述了,后續(xù)有時(shí)間單獨(dú)再出一個(gè)C層的分析文章。defineClassNative大家可以先理解為通過底層去加載類,如果有這個(gè)類,就加載出來,至此整個(gè)流程已經(jīng)完全跑完。不知道大家理解沒有。

由上述可以歸結(jié)出android類 加載的時(shí)序圖,如下圖:

(第一次畫時(shí)序圖,畫的不好,大家將就的看下,(__) 嘻嘻……)

(四)android 類加載時(shí)序圖

時(shí)序圖.png

loadClass.png

可以看出BaseDexClassLoader中有個(gè)pathList對象,pathList中包含一個(gè)DexFile數(shù)組,由上面分析知道,dexPath傳入的原始(.apk,.zip,jar等)文件在optimizedDirectory文件夾生成相應(yīng)的優(yōu)化后的odex文件,dexElements數(shù)組就是這些odex文件的集合,如果不分包,一般這個(gè)數(shù)組只有一個(gè)Element元素,也就只有一個(gè)DexFile文件,而對于這個(gè)類加載,就是遍歷這個(gè)集合,通過DexFile去尋找。最終調(diào)用native方法的defineClass

五總結(jié)

DexClassLoader和PathClassLoader都屬于雙親委托模型的類加載器。也就是說,它們在加載一個(gè)類之前,會去檢查自己及自己以上的類加載器是否已經(jīng)加載過這個(gè)類,如果加載過,就會直接將之返回,而不會重復(fù)加載

PathClassLoader是通過構(gòu)造函數(shù)new DexFile(path)來產(chǎn)生DexFile對象的;而DexClassLoader則是通過靜態(tài)方法loadDex(path,outpath,0)得到DexFile對象。這兩者的區(qū)別在于DexClassLoader需要提供一個(gè)可寫的outputpath路徑,用來釋放apk包或者jar包中的dex文件。換個(gè)說法來說,就是PathClassLoader不能從zip包中釋放dex,因此只支持直接操作Dex格式的文件,或者已經(jīng)安裝apk(因?yàn)橐呀?jīng)安裝的apk在cache中存在緩存的dex文件)。而DexClassLoader可以支持apk.jar,dex文件,并且會制定的outpath路徑釋放dex文件

六github地址

項(xiàng)目地址

作者:隔壁老李頭

鏈接:http://www.lxweimin.com/p/ce71e096ebdf

來源:簡書

著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容