編輯:Android開發實例
Java 源代碼經過編譯後會生成後綴為class的文件,也即字節碼文件。然後在Android中使用dx工具將其轉換為後綴為jar 的dex類型文件。Dalvik 虛擬機負責解釋並執行編譯後的字節碼。在解釋執行字節碼之前,當然要讀取文件,分析文件的內容,得到字節碼,然後才能解釋執行之。在整個的加載過程中,最為重要的就是對Class的加載 – Class包含Method,Method 又包含code。通過對Class的加載,我們即可獲得所需執行的字節碼。
本文從dexfile 文件分析及Class加載中的數據結構入手,結合主要流程,對整個加載過程進行分析,期望對大家有所幫助。 1. DexFile 在內存中的映射 在Android 系統中,java源文件會被編譯為後綴為jar的dex類型文件,在代碼中稱為dexfile。在加載Class之前,必先讀取相應的jar文件。通常我們使用read函數來讀取文件中的內容。但在Dalvik中使用mmap函數,和read不同,mmap函數會將dex文件映射到內存中,這樣通過普通的內存讀取操作即可訪問dex file 中的內容。 Dexfile 的文件格式如下圖所示,主要有三部分組成:頭部,索引,數據。通過頭部可知索引的位置和數目,可知數據區的起始位置。其中classDefsOff指定了ClassDef在文件的起始位置,dataOff指定了數據在文件的起始位置,ClassDef即可理解為Class的索引。通過讀取ClassDef可獲知Class的基本信息,其中classDataOff指定了Class數據在數據區的位置。 在將dexfile文件映射到內存後,即會調用dexFileParse函數對其分析,分析的結果存放於名為DexFile 的數據結構中。DexFile中的baseAddr指向映射區的起始位置,pClassDefs指向ClassDefs(即class索引)的起始位置。由於在查找class時,都是使用class的名字進行查找,為了加快查找速度,創建了一個hash表。在hash表中對class名字進行hash,並生成index。這些操作都是在對文件解析時所完成的,這樣雖然在加載過程中比較耗時,但是在運行過程中確可節省大量查找時間。 2. ClassObject - Class在加載後的表現形式 在對文件解析完成後就要加載Class的具體內容了!在Dalvik中,由ClassObject 這個數據結構負責存放加載的信息。如下圖所示,加載過程會在內存中alloc幾個區域,分別存放directMethods, virtualMethods, sfields, ifields。這些信息正是從dex 文件的數據區中讀取。首先會讀取Class的詳細信息,從中獲知directMethod, virtualMethod, sfield, ifield等的信息,然後再讀取。下圖為加載完成後的示意。這裡並未介紹加載的每個細節,感興趣的同學可通過此二圖自行分析。 還請大家注意的是在ClassObject結構中有個名為super的成員。通過super成員來指向它的超類。3. findClassNoInit – 負責加載Class並生成相應ClassObject的函數。 第二節介紹了加載後的數據結構,本節會分析負責加載工作的函數- findClassNoInit。請注意在獲取Class索引時,會分為基本類庫文件和用戶類文件兩種情況。在Dalvik分析之准備篇中,grund.sh中有一語句 “export BOOTCLASSPATH=$bootpath/core.jar:$bootpath/ext.jar:$bootpath/framework.jar:$bootpath/android.police.jar” 。這條語句指定了Dalvik所需的基本庫文件,如果沒有此語句,Dalvik在啟動過程中就會報錯退出。 LoadClassFromDex 函數先會讀取Class的具體數據(從ClassDataoff處),然後按圖索骥,分別加載 directMethod, virtualMethod,ifield,sfield。 作為業界TOP公司出品的東東,當然要關注執行效率了^_^。首先,在加載後我們要將其緩存起來,以便以後使用方便。其次,在查找過程中,如果我們順序查找的話,當然是很慢的拉。這當然是俺們senior engineer所不能容忍的,所以gDvm.loadedClasses這個Hash表就隆重出場了。什麼,這位同學說沒有幾個Class,至於這麼大動干戈麼。讓我們看看,通過在准備篇中介紹的方法,我們在main.c 249行設置斷點,這個時候基本庫已加載完畢。當程序停下時,我們看看gDvm的值,可以看到numLoadedClasses這個成員的值為212 !也即意味著這個時候我們什麼也沒做,用戶類也未加載,Dalvik 已加載的Class數目已達到212。 dvmLinkClass,好長,但是其最終好像會再次調用findClassNoInit。嗯,也是可以理解的嘛。如果一個子類需要調用超類的函數,那它當然要先加載超類了,可能的話,甚至會加載超類的超類^_^。 眼見為虛,實踐出真知,拿gdb調試下。 在findClassNoInit函數處設置斷點(在gdb提示符後輸入”b findClassNoInit”),在gdb提示符後連續幾次執行”c”和”bt”。可出現如下信息,可以看到在函數調用棧上可以多次看到findClassNoInit的身影。 (gdb) bt #0 findClassNoInit (descriptor=0xfef4c7f4 "??????%", loader=0x0, pDvmDex=0x0) at dalvik/vm/oo/Class.c:1373 #1 0xf6fc4d53 in dvmFindClassNoInit (descriptor=0xf5046a63 "Ljava/lang/Object;", loader=0x0) at dalvik/vm/oo/Class.c:1194 #2 0xf6fc6c0a in dvmResolveClass (referrer=0xf5837400, classIdx=290, fromUnverifiedConstant=false) at dalvik/vm/oo/Resolve.c:94 #3 0xf6fc3476 in dvmLinkClass (clazz=0xf5837400, classesResolved=false) at dalvik/vm/oo/Class.c:2537 #4 0xf6fc1b67 in findClassNoInit (descriptor=0xf6ff0df6 "Ljava/lang/Class;", loader=0x0, pDvmDex=0xa04c720) at dalvik/vm/oo/Class.c:1489 現在從另一個角度去觀察。在class.c 2575行設置斷點,然後等待程序停下。看下clazz的內容。 (gdb) p clazz->super->descriptor $6 = 0xf5046a63 "Ljava/lang/Object;" (gdb) p clazz->descriptor $7 = 0xf5046121 "Ljava/lang/Class;" 4. 基本類庫文件的加載 先在findClassNoInit函數處設置斷點,然後運行程序,等待程序的停下。 (gdb) b findClassNoInit Breakpoint 2 at 0xf6fc13e0: file dalvik/vm/oo/Class.c, line 1373. (gdb) c Continuing. 看看誰是第一個加載的Class,及其調用關系。 (gdb) bt #0 findClassNoInit (descriptor=0x0, loader=0x0, pDvmDex=0x0) at dalvik/vm/oo/Class.c:1373 #1 0xf6fc32a1 in dvmLinkClass (clazz=0xf5837350, classesResolved=false) at dalvik/vm/oo/Class.c:2491 #2 0xf6fc1b67 in findClassNoInit (descriptor=0xf6ff1ded "Ljava/lang/Thread;", loader=0x0, pDvmDex=0xa04c720) at dalvik/vm/oo/Class.c:1489 #3 0xf6f92692 in dvmThreadObjStartup () at dalvik/vm/Thread.c:328 #4 0xf6f800e6 in dvmStartup (argc=2, argv=0xa041190, ignoreUnrecognized=false, pEnv=0xa0411a0) at dalvik/vm/Init.c:1155 #5 0xf6f8b8e3 in JNI_CreateJavaVM (p_vm=0xf6ff0df6, p_env=0xf6ff0df6, vm_args=0xfef4d0b0) at dalvik/vm/Jni.c:4198 #6 0x08048893 in main (argc=3, argv=0xfef4d168) at dalvik/dalvikvm/Main.c:212 函數調用順序清晰可見:main -> JNI_CreateJavaVM-> dvmStartup-> dvmThreadObjStartup-> dvmFindSystemClassNoInit-> findClassNoInit 觀察仔細的同學可能會問在調用棧中沒有看到dvmFindSystemClassNoInit啊,為何你這樣寫啊?我估計編譯器將其作為inline優化了,導致gdb看不到有dvmFindSystemClassNoInit的棧。從回溯棧中也可清晰的看到 5. 用戶類文件的加載 用戶類文件的加載頗為曲折,它會先加載一個Class。然後這個Class去負責用戶類文件的加載,當然了,這個Class又會通過JNI的方式去曲折調用到findClassNoInit。這個就留給大家自己分析了,其實樓主自己也不是很明白為什麼要這麼大費周折^_^。
1、下載 進入官網(http://opencv.org/)下載OpenCV4Android並解壓。目錄結構如下圖所示。 其中,sdk目錄即是我們開發openc
自從Fragment出現,曾經有段時間,感覺大家談什麼都能跟Fragment談上關系,做什麼都要問下Fragment能實現不~~~哈哈,是不是有點過~~~ 為了
JSON代表JavaScript對象符號。它是一個獨立的數據交換格式,是XML的最佳替代品。本章介紹了如何解析JSON文件,並從中提取所需的信息。Android提供了四個
最近寫了一個簡單的朋友圈程序,包含了朋友圈的列表實現,視頻的錄制、預覽與上傳,圖片可選擇拍照或者從相冊選取,從相冊選取可以一次選擇多張照片,並且限制照片的張數,想擁有真正