Android高階之Dalvik初識

liuzxgeek發表於2016-06-07


 本文來自http://blog.csdn.net/liuxian13183/ ,引用必須註明出處!

研究安卓已多年,一直在應用層做開發,Framework層只是看過,也就是大家常說的”底層”,而高階一點的功能如熱載入、處理器型別配置,必須得了解再深入些才好,Library、Runtime、Kernel層等;當然瞭解底層的原因,不是在於去做底層開發,而是更好的做應用層,使其功能更強大和完善。這裡的底層是除應用層以外其他層。而Dalvik是什麼,就是把各層串起來,處理安卓系統和服務的一個計算機。

Dalvik這個名字,是創始人的祖先生存過一個小村莊的名字,大概為紀念先人的原因,這個村莊位於冰島。前面也有講這個虛擬機器是專門為移動裝置,即記憶體小、計算能力低、執行標準要求高提供的嵌入式裝置,正因為克服如此多的問題,以其優良的效能出現在世人面前,才受到眾多廠商的追捧,前有HTC、摩托羅拉,後來阿里雲、小米;越來越追求程式碼層的優化,更好的解決方案,更快的解釋、載入速度,更好的體驗和相容性,來抵消硬體裝置帶來的價格衝擊-這也是國內廠商,應對眾多街機的一種戰略手段,以數十萬上百萬的出廠裝置*單利-有限的研發力量價格=最終廠家利潤,假設每件研發投入100萬,價格降低5塊,則收益至少達到平衡或500萬以上;個人不太認同這種惡性競爭,逼死開發的策略,商品應該優質高價,讓消費者使用安全放心,帶給的是心理的安全感、舒適感、品牌感知度,以及強大的功能,像IPhone一樣,讓生活歸生活,功能歸功能;總之從廠商競爭情況來說,個人覺得華為做的最好。但萬變不離其宗,產品總是不完善的,需要維護和升級,以維持其對於公眾的領先地位,因此理解虛擬機器,有助於從全域性出發,提出更好的優化或解決方案,為消費者提供更好的產品。

閒話這麼多,進入正題。Dalvik來自於JVM,大多數的java程式都可以執行(為什麼說大多數,因為它退出JSF標準,按說1.6以後是否全面的支援Java,這事能肯定)。前面對JVM有過講解,它基於堆疊,處理程式碼指令,需要不斷的從主記憶體複製到臨時記憶體,然後操作完再存回去,有暫存器但僅限於虛擬機器自己使用;而Dalvik基於暫存器,程式碼的執行本身就依賴於暫存器,且避免過多的存取,位元組碼指令減少47%,訪問次數減少37%,因此執行速度更快。但暫存器有一個缺陷,只有65536個,每個32位,即總共256k,因此它還需要另一種檔案Odex的支援。說到它,就得先講dex檔案,我們都知道java檔案被編譯後形成.class檔案,這種檔案符合JVM的執行規則;而Dalvik則把.class檔案使用dx工具壓縮形成dex檔案,它符合暫存器的執行規則,同時又是對原資料進行過優化,共享共同資料,降低冗餘,使用程式碼結構緊湊,因而包大小大約為同型別jar檔案的50%。

Dex檔案的格式與前面講的類檔案格式類似,可以對比著看:有基本資料類,如byte佔8位,表示1byte的有符號數,ubyte同樣,但表示無符號數;也有檔案結構,也有字串索引,欄位索引,方法索引,型別索引,類定義,多的是原型資料索引,連線資料區等;同時header資訊裡也存著魔數,檔案總長度,以上各種索引及型別的個數、地址等,索引除公用的是絕對位置,其他均為相對位置。資料的存放。Dalvik位元組碼總共有226條,使用時會用一個hash指令表來儲存,格式如 B|A|op 12x 第一個是指令,第二個是縮寫,用來說明它的操作邏輯。

Odex檔案是為了提高程式執行速度而做的本地快取,一般存在data/dalvik-cache/xxx.odex,這個檔案已經由暫存器載入時幫忙做過位元組碼校驗、空方法消除、替換優化等工作,執行時優化解釋odex,沒有再執行dex,把載入過的位元組碼快取入odex,這就是它的工作原理。它在Dex檔案前新增了頭部資訊,尾部拼接了依賴庫、暫存器對映關係、類hash索引等輔助資訊,索引再介紹一下,它包括檔案的偏移地址和類的偏移地址,通過hash定位可以儘快查詢到資源並載入。講到這裡,dex載入的原理已經明瞭,由Dalvik載入,處理器解釋執行,快取放入odex。而Odex不斷增大,也是安卓手機記憶體變小的原因,因而對於解除安裝的App的odex檔案可以刪除,不常用的選擇刪除,常用的保留,不然每次載入解釋後仍然會增大,而手機速度更慢。降低Android手機記憶體最好的方法就是找到data/dalvik-cache裡不使用的odex刪除。

dex是重點,而apk的組成還有主配置檔案,資原始檔,通過aapt工具打包而成,用jarsigner簽名。而dex的載入跟class檔案載入非常類似,把Dex(class)與一個DexFile(ClassFile)檔案關聯,建立起資料結構,再把各個類依次載入生成物件,把指標交給直譯器引用執行,而4.0.4直譯器又有兩種,C語言和彙編,移動和快速型;但虛擬機器除了支援java語言以外,還支援c語言編寫的程式,按照java介面進行呼叫,執行效率更高;當然使用JIT補丁的方法,會更便捷,載入更快,移植性較強,後面我們將介紹熱載入的原理和常用的幾種框架。

Apk內部的檔案包含以下幾種:

resources.arsc:二進位制檔案,資源id和路徑對映組成

res:二進位制,專案中的res目錄

class.dex:class編譯成的可執行檔案,反編譯後可以看到原始碼結構

AndroidManifest.xml:二進位制檔案,反編譯後可看到部分內容

講完dex檔案,接下來可以講講安卓系統的啟動過程,之前有過介紹,但本次希望以更通俗的語法來說明。系統按鍵觸動驅動,觸發init.rc的init程式啟動,這是系統啟動後的首個程式,再建立各種守護程式,然後啟動Zygote程式,也就是使用者程式,其他應用都是由它建立-fork子程式共享記憶體和資源,啟動系統服務的SystemServer程式(相當於服務端)監聽socket指令操作。x86初始化,執行dalvik/dalvikvm/Main.c中的main方法,執行JNI_CreateJavaVM函式建立虛擬機器;而Arm平臺初始化是從Zygote程式開始的,執行/jni/AndroidRuntime.cpp的startVm方法,通過JNI_CreateJavaVM函式建立虛擬機器。然後呼叫startReg註冊JNI函式,用來呼叫java類;呼叫ZygoteInit類的main方法:1、建立socket介面(相當於客戶端),接收應用請求 2、呼叫預載入類和資源 3、啟動SystemServer並分裂出sytem_server,啟動各項系統服務,最終將執行緒加入Bind通訊系統,此程式退出則Zygote退出重啟4、呼叫runSelectLoopMode監聽程式請求 注:子程式同樣可以再fork出一個Zygote程式,所以在實際操作時,要區分一下。在子程式中fork函式返回0,在父程式返回負值 或此程式的pid,可以用來區別執行的是否是子程式或者建立成功與否。

虛擬機器的功能可以分為以下幾部分

1、程式管理:每個虛擬機器都有一個程式,依賴於Zygote機制實現

2、Zygote程式管理:會fork出來一個子Zygote程式,一個SystemServer程式,一個非Zygote程式的Speciallize程式

3、類載入:先載入所有類庫,然後載入位元組碼,放入類資料結構,供類直譯器執行,如果有關聯的超類、介面等也一併載入

4、記憶體管理:使用新老生代結合的方式,使用複製+標記-清除的演算法

4、本地介面:JNI

5、反射機制:通過類結構的載入,檢視、呼叫、修改類中的方法和屬性

6、直譯器:負責執行Dex位元組碼

7、即時編譯:JIT


Dalvik編譯的原理在於:開發者編譯專案成dex,在安裝時轉成odex,減少了JIT時再進行位元組碼校驗等工作;

ART在Android4.4版本出現,相比較Dalvik的優勢在於,直接將dex編譯成可執行的機器碼(而不再JIT將位元組碼轉換為機器碼),這樣加速App的執行,減少電量的消耗,使用者體驗更加流暢;當然劣勢也是有的,App安裝時比較慢,快取檔案變大;較之IOS一次開發者編譯,即在使用者手機執行有先天的弊端,優勢在於Android熱修復和動態載入方便,而IOS相對則比較古板。


Dex較之class檔案,新增新操作碼支援,進行檔案優化和壓縮,加快解析速度

簽名好處:1、順利升級2、熱修復 3、程式級資料共享
Meta-Inf裡的檔案Manifest.MF是對Apk包中非Meta-Inf的所有檔案進行資料校驗,而Cert.Sf對Manifest.MF和其他檔案進行校驗,Cert.Rsa中儲存簽名資訊


Dalvik執行在Arm的CPU上,那麼有必要了解一下Arm

Arm:一家1990年成立的英國公司,主要進行Cpu授權,是一種智慧財產權公司,佔據移動95%的市場,主要靠裝置抽成盈利。

產權領域:指令集架構、微處理器、GPU、互連架構等

特點:低功耗、低成本,使用RISC(Reduced Instruction Set Computer,通常僅執行1或2個指令即可完成意圖),

和big.LITTLE架構(適配高效能-64位的核心和低效能-32位的核心切換),方便其他品牌貼標生產

授權方式:架構、核心、使用三種形式,分別針對大、中、小型公司

合作伙伴:高通、聯發科等螞蟻聯盟

合作形式:實行OEM(Original Equipment Manufacture)

對手劣勢:1968年成立的Intel使用CISC(Complex Instruction Set Computer,通常僅執行3或4個指令即可完成意圖)架構的x86功耗高、掉電快

適配執行:從上面介紹可以看出,Arm佔據絕對多數市場,因此mips和x86基礎不需要考慮適配,而且它們會主動解析Arm指令成自己能使用的指令(使得自身效率更低,不得不讚嘆規模效應的強大),big.LITTLE的高低效能核心切換,可以進行有效省電(計算量小用低效能、大用高效能,效能越高越耗電)。而64位暫存器的使用,減少記憶體存取的次數,提高CPU處理效率,用於RISC架構高效能的處理器。

Mips:1998年成立,同樣依據RISC架構,中科院設計的“龍芯”與它95%類似,涉及侵權,而準備收購其估值1億的20%股份。

在Android Studio下,則可以通過以下的構建方式指定需要型別的SO庫。

productFlavors {
    flavor1 {
        ndk {
            abiFilters "armeabi-v7a"
            abiFilters "x86"
            abiFilters "armeabi"
        }
    }
    flavor2 {
        ndk {
            abiFilters "armeabi-v7a"
            abiFilters "x86"
            abiFilters "armeabi"
            abiFilters "arm64-v8a"
            abiFilters "x86_64"
        }
    }
}

更多:http://blog.csdn.net/reboot123/article/details/17918447

案例:

  • 如果你使用的是 1.7.4 版 (或更高版本) Agora Native SDK:

    Agora Native SDK 目前支援 64 位的 ARM 架構,只需將 arm64-v8a 裡面的檔案(位於SDK包內)拷貝至專案對應的 arm64-v8a 路徑下。目前不支援 x86_64 架構直接執行,不過可以以 x86 方式相容執行。具體做法就是在64位的 x86 裝置上保證 APK 裡面沒有 x86_64 目錄。

  • 如果你使用的是 1.7.4 版之前的 Agora Native SDK:

    Agora Native SDK 目前只提供 32 位 native 庫(armeabi-v7a),在 64 位裝置上,Android 支援以 32 位程式模式啟動 APK,因此 Agora Native SDK 也可以在 64 位 Android 裝置上使用,但必須保證 APK 的 arm64 目錄為空。否則 Android 系統將以 64 位程式模式載入 APK,由於 Agora Native SDK 沒有提供 64 位庫,APK 啟動會失敗。 由於目前 Android 的主流還是 32 位裝置,一般各廠商都會提供 32 位庫,因此一般來說,在 64 位 Android 裝置以 32 位程式模式啟動一般不會有問題。但是如果在某些 64 位平臺上出現了這樣的錯誤: java.lang.UnsatisfiedLinkError: dlopen failed: ”libHDACEngine.so”

但是如果 64 位平臺上出現以下報錯:

java.lang.UnsatisfiedLinkError: dlopen failed: "libHDACEngine.so"

可能的原因:

在安裝 APK 的時候,系統會按照 Build.SUPPORTED_ABIS 去查詢 APK 的 lib 目錄下的 native 庫的目錄(現有的 ABI: armeabi, armeabi-v7a,arm64-v8a, x86, x86_64, mips64, mips)。

如果在 app 中有相容 64-bit 的目錄但是又缺少庫檔案的話,並不會使用其他 ABI 目錄下的庫檔案替換所缺少的庫檔案進行安裝,這些庫不混合使用,也就是說需要為每個架構提供對應的庫檔案。

Android 在載入 native 庫的時候有回退(fallback)機制,在64位系統上如果 app 並不存在 arm64-v8a 的目錄,則會嘗試尋找armeabi-v7a下面的庫進行載入,一般來說是向下相容的。

解決方案:

  • 方法 1: 在構建應用程式的時候,在工程 (project) 裡刪除所有 arm64-v8a 下面的庫以及該目錄;在生成 app 後,確認 apk 的包內 lib 下沒有 arm64-v8a 的目錄。
  • 方法 2: 在 gradle 構建檔案中設定 abiFilters,只打包 32 位架構的庫:
 android {
  ...
  defaultConfig {
    ...
    ndk {
      abiFilters "armeabi-v7a","x86"
    }
  }
}


相關文章