Dalvik虛擬機

Dalvik虛擬機的內(nèi)存大體上可以分為Java Object Heap鸠真、Bitmap Memory和Native Heap三種携冤。

Java Object Heap是用來分配Java對象的争剿,也就是我們在代碼new出來的對象都是位于Java Object Heap上的锋勺。Dalvik虛擬機在啟動的時候,可以通過-Xms和-Xmx選項來指定Java Object Heap的最小值和最大值。為了避免Dalvik虛擬機在運行的過程中對Java Object Heap的大小進行調(diào)整而影響性能阳柔,我們可以通過-Xms和-Xmx選項來將它的最小值和最大值設置為相等。

Java Object Heap的最小和最大默認值為2M和16M蚓峦,但是手機在出廠時舌剂,廠商會根據(jù)手機的配置情況來對其進行調(diào)整,例如暑椰,G1霍转、Droid、Nexus One和Xoom的Java Object Heap的最大值分別為16M一汽、24M避消、32M 和48M。我們可以通過ActivityManager類的成員函數(shù)getMemoryClass來獲得Dalvik虛擬機的Java Object Heap的最大值召夹。

ActivityManager類的成員函數(shù)getMemoryClass的實現(xiàn)如下所示:

這個函數(shù)定義在文件frameworks/base/core/java/android/app/ActivityManager.java中岩喷。

Dalvik虛擬機在啟動的時候,就是通過讀取系統(tǒng)屬性dalvik.vm.heapsize的值來獲得Java Object Heap的最大值的监憎,而ActivityManager類的成員函數(shù)getMemoryClass最終也通過讀取這個系統(tǒng)屬性的值來獲得Java Object Heap的最大值纱意。

這個Java Object Heap的最大值也就是我們平時所說的Android應用程序進程能夠使用的最大內(nèi)存。這里必須要注意的是鲸阔,Android應用程序進程能夠使用的最大內(nèi)存指的是能夠用來分配Java Object的堆偷霉。

Bitmap Memory也稱為External Memory,它是用來處理圖像的隶债。在HoneyComb之前腾它,Bitmap Memory是在Native Heap中分配的跑筝,但是這部分內(nèi)存同樣計入Java Object Heap中死讹,也就是說,Bitmap占用的內(nèi)存和Java Object占用的內(nèi)存加起來不能超過Java Object Heap的最大值曲梗。這就是為什么我們在調(diào)用BitmapFactory相關的接口來處理大圖像時赞警,會拋出一個OutOfMemoryError異常的原因:

public class ActivityManager {

......

/**

* Return the approximate per-application memory class of the current

* device.? This gives you an idea of how hard a memory limit you should

* impose on your application to let the overall system work best.? The

* returned value is in megabytes; the baseline Android memory class is

* 16 (which happens to be the Java heap limit of those devices); some

* device with more memory may return 24 or even higher numbers.

*/

public int getMemoryClass() {

return staticGetMemoryClass();

}

/** @hide */

static public int staticGetMemoryClass() {

// Really brain dead right now -- just take this from the configured

// vm heap size, and assume it is in megabytes and thus ends with "m".

String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m");

return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));

}

......

}


這個函數(shù)定義在文件frameworks/base/core/java/android/app/ActivityManager.java中妓忍。

Dalvik虛擬機在啟動的時候,就是通過讀取系統(tǒng)屬性dalvik.vm.heapsize的值來獲得Java Object Heap的最大值的愧旦,而ActivityManager類的成員函數(shù)getMemoryClass最終也通過讀取這個系統(tǒng)屬性的值來獲得Java Object Heap的最大值世剖。

這個Java Object Heap的最大值也就是我們平時所說的Android應用程序進程能夠使用的最大內(nèi)存。這里必須要注意的是笤虫,Android應用程序進程能夠使用的最大內(nèi)存指的是能夠用來分配Java Object的堆旁瘫。

Bitmap Memory也稱為External Memory,它是用來處理圖像的琼蚯。在HoneyComb之前酬凳,Bitmap Memory是在Native Heap中分配的,但是這部分內(nèi)存同樣計入Java Object Heap中遭庶,也就是說宁仔,Bitmap占用的內(nèi)存和Java Object占用的內(nèi)存加起來不能超過Java Object Heap的最大值。這就是為什么我們在調(diào)用BitmapFactory相關的接口來處理大圖像時峦睡,會拋出一個OutOfMemoryError異常的原因:

在HoneyComb以及更高的版本中翎苫,Bitmap Memory就直接是在Java Object Heap中分配了,這樣就可以直接接受GC的管理榨了。

Native Heap就是在Native Code中使用malloc等分配出來的內(nèi)存煎谍,這部分內(nèi)存是不受Java Object Heap的大小限制的,也就是它可以自由使用阻逮,當然它是會受到系統(tǒng)的限制粱快。但是有一點需要注意的是,不要因為Native Heap可以自由使用就濫用叔扼,因為濫用Native Heap會導致系統(tǒng)可用內(nèi)存急劇減少事哭,從而引發(fā)系統(tǒng)采取激進的措施來Kill掉某些進程,用來補充可用內(nèi)存瓜富,這樣會影響系統(tǒng)體驗鳍咱。

此外,在HoneyComb以及更高的版本中与柑,我們可以在AndroidManifest.xml的application標簽中增加一個值等于“true”的android:largeHeap屬性來通知Dalvik虛擬機應用程序需要使用較大的Java Object Heap谤辜。事實上,在內(nèi)存受限的手機上价捧,即使我們將一個應用程序的android:largeHeap屬性設置為“true”丑念,也是不能增加它可用的Java Object Heap的大小的,而即便是可以通過這個屬性來增大Java Object Heap的大小结蟋,一般情況也不應該使用該屬性脯倚。為了提高系統(tǒng)的整體體驗,我們需要做的是致力于降低應用程序的內(nèi)存需求,而不是增加增加應用程序的Java Object Heap的大小推正,畢竟系統(tǒng)總共可用的內(nèi)存是固定的恍涂,一個應用程序用得多了,就意味意其它應用程序用得少了植榕。

二. 垃圾收集(GC)

Dalvik虛擬機可以自動回收那些不再使用了的Java Object再沧,也就是那些不再被引用了的Java Object。垃圾自動收集機制將開發(fā)者從內(nèi)存問題中解放出來尊残,極大地提高了開發(fā)效率炒瘸,以及提高了程序的可維護性。

我們知道寝衫,在C或者C++中什燕,開發(fā)者需要手動地管理在堆中分配的內(nèi)存,但是這往往導致很多問題竞端。例如屎即,內(nèi)存分配之后忘記釋放,造成內(nèi)存泄漏事富。又如技俐,非法訪問那些已經(jīng)釋放了的內(nèi)存,引發(fā)程序崩潰统台。如果沒有一個好的C或者C++應用程序開發(fā)框架雕擂,一般的開發(fā)者根本無法駕馭內(nèi)存問題,因為程序大了之后贱勃,很容易造成失控井赌。最要命的是,內(nèi)存被破壞的時候贵扰,并不一定就是程序崩潰的時候仇穗,它就是一顆不定時炸彈,說不準什么時候會被引爆戚绕,因此纹坐,查找原因是非常困難的。

從這里我們也可以推斷出舞丛,Android為什么會選擇Java而不是C/C++來作為應用程序開發(fā)語言耘子,就是為了能夠讓開發(fā)遠離內(nèi)存問題,而將精力集中在業(yè)務上球切,開發(fā)出更多更好的APP來谷誓,從而迎頭趕超iOS。當然吨凑,Android系統(tǒng)內(nèi)存也存在大量的C/C++代碼捍歪,這只要考慮性能問題,畢竟C/C++程序的運行性能整體上還是優(yōu)于運行在虛擬機之上的Java程序的。不過费封,為了避免出現(xiàn)內(nèi)存問題,在Android系統(tǒng)內(nèi)部的C++代碼蒋伦,大量地使用了智能指針來自動管理對象的生命周期弓摘。選擇Java來作為Android應用程序的開發(fā)語言,可以說是技術與商業(yè)之間一個折衷痕届,事實證明韧献,這種折衷是成功的。

回到正題研叫,在GingerBread之前锤窑,Dalvik虛擬使用的垃圾收集機制有以下特點:

1. Stop-the-world,也就是垃圾收集線程在執(zhí)行的時候嚷炉,其它的線程都停止渊啰;

2. Full heap collection,也就是一次收集完全部的垃圾申屹;

3. 一次垃圾收集造成的程序中止時間通常都大于100ms绘证。

在GingerBread以及更高的版本中,Dalvik虛擬使用的垃圾收集機制得到了改進哗讥,如下所示:

1. ?Cocurrent嚷那,也就是大多數(shù)情況下,垃圾收集線程與其它線程是并發(fā)執(zhí)行的杆煞;

2. ?Partial collection魏宽,也就是一次可能只收集一部分垃圾;

3. ?一次垃圾收集造成的程序中止時間通常都小于5ms决乎。

Dalvik虛擬機執(zhí)行完成一次垃圾收集之后队询,我們通常可以看到類似以下的日志輸出:

D/dalvikvm(9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms

在這一行日志中构诚,GC_CONCURRENT表示GC原因娘摔,2049K表示總共回收的內(nèi)存,3571K/9991K表示Java Object Heap統(tǒng)計唤反,即在9991K的Java Object Heap中凳寺,有3571K是正在使用的,4703K/5261K表示External Memory統(tǒng)計彤侍,即在5261K的External Memory中肠缨,有4703K是正在使用的,2ms+2ms表示垃圾收集造成的程序中止時間盏阶。

三. 即時編譯(JIT)

前面提到晒奕,JIT是相對AOT而言的,即JIT是在程序運行的過程中進行編譯的,而AOT是在程序運行前進行編譯的脑慧。在程序運行的過程中進行編譯既有好處魄眉,也有壞處。好處在于可以利用程序的運行時信息來對編譯出來的代碼進行優(yōu)化闷袒,而壞處在于占用程序的運行時間坑律,也就是說不能花太多時間在代碼編譯和優(yōu)化之上。

為了解決時間問題囊骤,JIT可能只會選擇那些熱點代碼進行編譯或者優(yōu)化晃择。根據(jù)2-8原則,一個程序80%的時間可能都是在重復執(zhí)行20%的代碼也物。因此宫屠,JIT就可以選擇這20%經(jīng)常執(zhí)行的代碼來進行編譯和優(yōu)化。

為了充分地利用好運行時信息來優(yōu)化代碼滑蚯,JIT采用一種激進的方法浪蹂。JIT在編譯代碼的時候,會對程序的運行情況進行假設告材,并且按照這種假設來對代碼進行優(yōu)化乌逐。隨著程序的代碼,如果前面的假設一直保持成立创葡,那么JIT就什么也不用做浙踢,因此就可以提高程序的運行性能。一旦前面的假設不再成立了灿渴,那么JIT就需要對前面編譯優(yōu)化的代碼進行調(diào)整洛波,以便適應新的情況。這種調(diào)整成本可能是很昂貴的骚露,但是只要假設不成立的情況很少或者幾乎不會發(fā)生蹬挤,那么獲得的好處還是大于壞處的。由于JIT在編譯和優(yōu)化代碼的時候棘幸,對程序的運行情況進行了假設焰扳,因此,它所采取的激進優(yōu)化措施又稱為賭博误续,即Gambling吨悍。

我們以一個例子來說明這種Gambling。我們知道蹋嵌,Java的同步原語涉及到Lock和Unlock操作育瓜。Lock和Unlock操作是非常耗時的,而且它們只有在多線程環(huán)境中才真的需要栽烂。但是一些同步函數(shù)或者同步代碼躏仇,有程序運行的時候恋脚,有可能始終都是被單線程執(zhí)行,也就是說焰手,這些同步函數(shù)或者同步代碼不會被多線程同時執(zhí)行糟描。這時候JIT就可以采取一種Lazy Unlocking機制。

當一個線程T1進入到一個同步代碼C時书妻,它還是按照正常的流程來獲取一個輕量級鎖L1船响,并且線程T1的ID會記錄在輕量鎖L1上。當經(jīng)程T1離開同步函數(shù)或者同步代碼時驻子,它并不會釋放前面獲得的輕量級鎖L1。當線程T1再次進入同步代碼C時估灿,它就會發(fā)現(xiàn)輕量級鎖L的所有者正是自己崇呵,因此,它就可以直接執(zhí)行同步代碼C馅袁。這時候如果另外一個線程T2也要進入同步代碼C域慷,它就會發(fā)現(xiàn)輕量級鎖L已經(jīng)被線程T1獲取。在這種情況下汗销,JIT就需要檢查線程T1的調(diào)用堆棧犹褒,看看它是否還在執(zhí)行同步代碼C。如果是的話弛针,那么就需要將輕量級鎖L1轉(zhuǎn)換成一個重量級鎖L2叠骑,并且將重量級鎖L2的狀態(tài)設置為鎖定,然后再讓線程T2在重量級鎖L2上睡眠削茁。等線程T1執(zhí)行完成同步代碼C之后宙枷,它就會按照正常的流程來釋放重量級鎖L2,從而喚醒線程T2來執(zhí)行同步代碼C茧跋。另一方面慰丛,如果線程T2在進入同步代碼C的時候,JIT通過檢查線程T1的調(diào)用堆棧瘾杭,發(fā)現(xiàn)它已經(jīng)離開同步代碼C了诅病,那么它就直接將輕量級鎖L1的所有者記錄為線程T2,并且讓線程T2執(zhí)行同步代碼C粥烁。

通過上述的Lazy Unlocking機制杨刨,我們就可以充分地利用程序的運行時信息來提高程序的執(zhí)行性能,這種優(yōu)化對于靜態(tài)編譯的語言來說俐末,是無法做到的存谎。從這個角度來看,我們就可以說变勇,靜態(tài)編譯語言(如C++)并不一定比在虛擬機上執(zhí)行的語言(如Java)快恤左,這是因為后者可以有一種強大的武器叫做JIT贴唇。

Dalvik虛擬機從Android 2.2版本開始,才支持JIT飞袋,而且是可選的戳气。在編譯Dalvik虛擬機的時候,可以通過WITH_JIT宏來將JIT也編譯進去巧鸭,而在啟動Dalvik虛擬機的時候瓶您,可以通過-Xint:jit選項來開啟JIT功能。

關于虛擬機JIT的實現(xiàn)原理的簡要介紹纲仍,可以進一步參考這篇文章:http://blog.reverberate.org/2012/12/hello-jit-world-joy-of-simple-jits.html呀袱。

四. Java本地調(diào)用(JNI)

無論如何,虛擬機最終都是運行在目標機器之上的郑叠,也就是說夜赵,它需要將自己的指令翻譯成目標機器指令來執(zhí)行,并且有些功能乡革,需要通過調(diào)用目標機器運行的操作系統(tǒng)接口來完成寇僧。這樣就需要有一個機制,使得函數(shù)調(diào)用可以從Java層穿越到Native層沸版,也就是C/C++層嘁傀。這種機制就稱為Java本地調(diào)用,即JNI视粮。當然细办,我們在執(zhí)行Native代碼的時候,有時候也是需要調(diào)用到Java函數(shù)的蕾殴,這同樣是可以通過JNI機制來實現(xiàn)蟹腾。也就是說,JNI機制既支持在Java函數(shù)中調(diào)用C/C++函數(shù)区宇,也支持在C/C++函數(shù)中調(diào)用Java函數(shù)娃殖。

事實上,Dalvik虛擬機提供的Java運行時庫议谷,大部分都是通過調(diào)用目標機器操作系統(tǒng)接口來實現(xiàn)的炉爆,也就是通過調(diào)用Linux系統(tǒng)接口來實現(xiàn)的。例如卧晓,當我們調(diào)用android.os.Process類的成員函數(shù)start來創(chuàng)建一個進程的時候芬首,最終會調(diào)用到Linux系統(tǒng)提供的fork系統(tǒng)調(diào)用來創(chuàng)建一個進程。

同時逼裆,為了方便開發(fā)者使用C/C++語言來開發(fā)應用程序郁稍,Android官方提供了NDK。通過NDK胜宇,我們就可以使用JNI機制來在Java函數(shù)中調(diào)用到C/C++函數(shù)耀怜。不過Android官方是不提倡使用NDK來開發(fā)應用程序的恢着,這從它對NDK的支持遠遠不如SDK的支持就可以看得出來。

五. 進程和線程管理

一般來說财破,虛擬機的進程和線程都是與目標機器本地操作系統(tǒng)的進程和線程一一對應的掰派,這樣做的好處是可以使本地操作系統(tǒng)來調(diào)度進程和線程。進程和線程調(diào)度是操作系統(tǒng)的核心模塊左痢,它的實現(xiàn)是非常復雜的靡羡,特別是考慮到多核的情況,因此俊性,就完全沒有必要在虛擬機中提供一個進程和線程庫略步。

Dalvik虛擬機運行在Linux操作系統(tǒng)之上。我們知道定页,Linux操作系統(tǒng)并沒有純粹的線程概念趟薄,只要兩個進程共享同一個地址空間,那么就可以認為它們同一個進程的兩個線程拯勉。Linux操作系統(tǒng)提供了兩個fork和clone兩個調(diào)用竟趾,其中憔购,前者就是用來創(chuàng)建進程的宫峦,而后者就是用來創(chuàng)建線程的。關于Linux操作系統(tǒng)的進程和線程的實現(xiàn)玫鸟,可以參考在前面Android學習啟動篇一文中提到的經(jīng)典Linux內(nèi)核書籍导绷。

關于Android應用程序進程,它有兩個很大的特點屎飘,下面我們就簡要介紹一下妥曲。

第一個特點是每一個Android應用程序進程都有一個Dalvik虛擬機實例。這樣做的好處是Android應用程序進程之間不會相互影響钦购,也就是說檐盟,一個Android應用程序進程的意外中止,不會影響到其它的Android應用程序進程的正常運行押桃。

第二個特點是每一個Android應用程序進程都是由一種稱為Zygote的進程fork出來的葵萎。Zygote進程是由init進程啟動起來的,也就是在系統(tǒng)啟動的時候啟動的唱凯。Zygote進程在啟動的時候羡忘,會創(chuàng)建一個虛擬機實例,并且在這個虛擬機實例將所有的Java核心庫都加載起來磕昼。每當Zygote進程需要創(chuàng)建一個Android應用程序進程的時候卷雕,它就通過復制自身來實現(xiàn),也就是通過fork系統(tǒng)調(diào)用來實現(xiàn)票从。這些被fork出來的Android應用程序進程漫雕,一方面是復制了Zygote進程中的虛擬機實例滨嘱,另一方面是與Zygote進程共享了同一套Java核心庫。這樣不僅Android應用程序進程的創(chuàng)建過程很快蝎亚,而且由于所有的Android應用程序進程都共享同一套Java核心庫而節(jié)省了內(nèi)存空間九孩。

關于Dalvik虛擬機的特性,我們就簡要介紹到這里发框。事實上躺彬,Dalvik虛擬機和Java虛擬機的實現(xiàn)是類似的,例如梅惯,Dalvik虛擬機也支持JDWP(Java Debug Wire Protocol)協(xié)議宪拥,這樣我們就可以使用DDMS來調(diào)試運行在Dalvik虛擬機中的進程。對Dalvik虛擬機的其它特性或者實現(xiàn)原理有興趣的铣减,建議都可以參考Java虛擬機的實現(xiàn)她君,這里提供三本參考書:

1.?Java Virtual Machine Specification (Java SE 7)

2. Inside the Java?Virtual Machine, Second Edition

3.?Oracle JRockit: The Definitive Guide

另外,關于Dalvik虛擬機的指令集和dex文件格式的介紹葫哗,可以參考官方文檔:http://source.android.com/tech/dalvik/index.html缔刹。如果對虛擬機的實現(xiàn)原理有興趣的,還可以參考這個鏈接:http://www.weibo.com/1595248757/zvdusrg15劣针。

在這里校镐,我們學習Dalvik虛擬機的目標是打通Java層到C/C++層之間的函數(shù)調(diào)用,從而可以更好地理解Android應用程序是如何在Linux內(nèi)核上面運行的捺典。為了達到這個目的鸟廓,在接下來的文章中,我們將關注以下四個情景:

1.Dalvik虛擬機的啟動過程襟己;

2.Dalvik虛擬機的運行過程引谜;

3.JNI函數(shù)的注冊過程

4.Java進程和線程的創(chuàng)建過程擎浴。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末员咽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贮预,更是在濱河造成了極大的恐慌贝室,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萌狂,死亡現(xiàn)場離奇詭異档玻,居然都是意外死亡,警方通過查閱死者的電腦和手機茫藏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門误趴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人务傲,你說我怎么就攤上這事凉当≡嫔辏” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵看杭,是天一觀的道長忠藤。 經(jīng)常有香客問我,道長楼雹,這世上最難降的妖魔是什么模孩? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮贮缅,結(jié)果婚禮上榨咐,老公的妹妹穿的比我還像新娘。我一直安慰自己谴供,他們只是感情好块茁,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桂肌,像睡著了一般数焊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上崎场,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天佩耳,我揣著相機與錄音,去河邊找鬼照雁。 笑死蚕愤,一個胖子當著我的面吹牛答恶,可吹牛的內(nèi)容都是我干的饺蚊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼悬嗓,長吁一口氣:“原來是場噩夢啊……” “哼污呼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起包竹,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤燕酷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后周瞎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苗缩,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年声诸,在試婚紗的時候發(fā)現(xiàn)自己被綠了酱讶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡彼乌,死狀恐怖泻肯,靈堂內(nèi)的尸體忽然破棺而出渊迁,到底是詐尸還是另有隱情,我是刑警寧澤灶挟,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布琉朽,位于F島的核電站,受9級特大地震影響稚铣,放射性物質(zhì)發(fā)生泄漏箱叁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一惕医、第九天 我趴在偏房一處隱蔽的房頂上張望蝌蹂。 院中可真熱鬧,春花似錦曹锨、人聲如沸孤个。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽齐鲤。三九已至,卻和暖如春椒楣,著一層夾襖步出監(jiān)牢的瞬間给郊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工捧灰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留淆九,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓毛俏,卻偏偏與公主長得像炭庙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子煌寇,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

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

  • 1.Dalvik虛擬機和Java虛擬機的區(qū)別 Dalvik虛擬機使用的是dex(Dalvik Executable...
    wenxiaohua閱讀 2,467評論 0 6
  • 1 什么是Dalvik虛擬機 Dalvik是Google公司自己設計用于Android平臺的Java虛擬機焕蹄,它是A...
    Android看海閱讀 4,802評論 0 7
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,133評論 25 707
  • 三門峽為何要叫三門峽?這個問題可能難倒了大部分同學阀溶。那么就由我來告訴你們吧腻脏。這是因為在黃河上面,離三門...
    是這樣的嗎閱讀 12,753評論 2 1
  • 我們都知道银锻,UI布局也影響著咱們App的性能永品,嵌套層級過多的布局在渲染時候會需要更多時間。下面就提出一些優(yōu)化布局的...
    __帝華閱讀 227評論 0 0