Random Access Memory(RAM)在任何軟件開發(fā)環(huán)境中都是一個(gè)很寶貴的資源,。這一點(diǎn)在物理內(nèi)存通常很有限的移動(dòng)操作系統(tǒng)上,,顯得尤為突出。盡管Android的Dalvik虛擬機(jī)扮演了常規(guī)的垃圾回收的角色,,但這并不意味著你可以忽視app的內(nèi)存分配與釋放的時(shí)機(jī)與地點(diǎn),。 為了GC能夠從你的app中及時(shí)回收內(nèi)存,你需要避免Memory Leaks(這通常由引用的不能釋放而導(dǎo)致)并且在適當(dāng)?shù)臅r(shí)機(jī)(下面會(huì)講到的lifecycle callbacks)來釋放引用,。對(duì)于大多數(shù)apps來說,,Dalvik的GC會(huì)自動(dòng)把離開活動(dòng)線程的對(duì)象進(jìn)行回收。 這篇文章會(huì)解釋Android如何管理app的進(jìn)程與內(nèi)存分配,,并且你可以在開發(fā)Android應(yīng)用的時(shí)候主動(dòng)的減少內(nèi)存的使用,。關(guān)于Java的資源管理機(jī)制,請(qǐng)參加其它書籍或者線上材料,。如果你正在尋找如何分析你的內(nèi)存使用情況的文章,,請(qǐng)參考這里Investigating Your RAM Usage。 第1部分:Android是如何管理內(nèi)存的Android并沒有提供內(nèi)存的交換區(qū)(Swap space),,但是它有使用paging與memory-mapping(mmapping)的機(jī)制來管理內(nèi)存,。這意味著任何你修改的內(nèi)存(無論是通過分配新的對(duì)象還是訪問到mmaped pages的內(nèi)容)都會(huì)貯存在RAM中,,而且不能被paged out。因此唯一完整釋放內(nèi)存的方法是釋放那些你可能hold住的對(duì)象的引用,,這樣使得它能夠被GC回收,。只有一種例外是:如果系統(tǒng)想要在其他地方進(jìn)行reuse。 1)共享內(nèi)存Android通過下面幾個(gè)方式在不同的Process中來共享RAM:
關(guān)于如何查看app所使用的共享內(nèi)存,,請(qǐng)查看Investigating Your RAM Usage 2)分配與回收內(nèi)存這里有下面幾點(diǎn)關(guān)于Android如何分配與回收內(nèi)存的事實(shí):
3)限制應(yīng)用的內(nèi)存為了維持多任務(wù)的功能環(huán)境,Android為每一個(gè)app都設(shè)置了一個(gè)硬性的heap size限制,。準(zhǔn)確的heap size限制隨著不同設(shè)備的不同RAM大小而各有差異,。如果你的app已經(jīng)到了heap的限制大小并且再嘗試分配內(nèi)存的話,會(huì)引起OutOfMemoryError的錯(cuò)誤,。 在一些情況下,,你也許想要查詢當(dāng)前設(shè)備的heap size限制大小是多少,然后決定cache的大小,??梢酝ㄟ^getMemoryClass()來查詢。這個(gè)方法會(huì)返回一個(gè)整數(shù),,表明你的app heap size限制是多少megabates,。 4)切換應(yīng)用當(dāng)用戶在不同應(yīng)用之間進(jìn)行切換的時(shí)候,不是使用交換空間的辦法,。Android會(huì)把那些不包含foreground組件的進(jìn)程放到LRU cache中,。例如,,當(dāng)用戶剛開始啟動(dòng)了一個(gè)應(yīng)用,,這個(gè)時(shí)候?yàn)樗鼊?chuàng)建了一個(gè)進(jìn)程,但是當(dāng)用戶離開這個(gè)應(yīng)用,,這個(gè)進(jìn)程并沒有離開,。系統(tǒng)會(huì)把這個(gè)進(jìn)程放到cache中,,如果用戶后來回到這個(gè)應(yīng)用,這個(gè)進(jìn)程能夠被resued,,從而實(shí)現(xiàn)app的快速切換,。 如果你的應(yīng)用有一個(gè)被緩存的進(jìn)程,它被保留在內(nèi)存中,,并且當(dāng)前不再需要它了,,這會(huì)對(duì)系統(tǒng)的整個(gè)性能有影響。因此當(dāng)系統(tǒng)開始進(jìn)入低內(nèi)存狀態(tài)時(shí),,它會(huì)由系統(tǒng)根據(jù)LRU的規(guī)則與其他因素選擇殺掉某些進(jìn)程,,為了保持你的進(jìn)程能夠盡可能長久的被cached,請(qǐng)參考下面的章節(jié)學(xué)習(xí)何時(shí)釋放你的引用,。 更對(duì)關(guān)于不在foreground的進(jìn)程是Android是如何決定kill掉哪一類進(jìn)程的問題,,請(qǐng)參考Processes and Threads. 第2部分:你的應(yīng)用該如何管理內(nèi)存你應(yīng)該在開發(fā)過程的每一個(gè)階段都考慮到RAM的有限性,甚至包括在開發(fā)開始之前的設(shè)計(jì)階段。有許多種設(shè)計(jì)與實(shí)現(xiàn)方式,,他們有著不同的效率,,盡管是對(duì)同樣一種技術(shù)的不斷組合與演變。 為了使得你的應(yīng)用效率更高,,你應(yīng)該在設(shè)計(jì)與實(shí)現(xiàn)代碼時(shí),,遵循下面的技術(shù)要點(diǎn)。 1)珍惜Services資源如果你的app需要在后臺(tái)使用service,,除非它被觸發(fā)執(zhí)行一個(gè)任務(wù),,否則其他時(shí)候都應(yīng)該是非運(yùn)行狀態(tài)。同樣需要注意當(dāng)這個(gè)service已經(jīng)完成任務(wù)后停止service失敗引起的泄漏,。 當(dāng)你啟動(dòng)一個(gè)service,,系統(tǒng)會(huì)傾向?yàn)榱诉@個(gè)Service而一直保留它的Process。這使得process的運(yùn)行代價(jià)很高,,因?yàn)橄到y(tǒng)沒有辦法把Service所占用的RAM讓給其他組件或者被Paged out,。這減少了系統(tǒng)能夠存放到LRU緩存當(dāng)中的process數(shù)量,它會(huì)影響app之間的切換效率,。它甚至?xí)?dǎo)致系統(tǒng)內(nèi)存使用不穩(wěn)定,,從而無法繼續(xù)Hold住 所有目前正在運(yùn)行的Service。 限制你的service的最好辦法是使用IntentService, 它會(huì)在處理完扔給它的intent任務(wù)之后盡快結(jié)束自己,。更多信息,,請(qǐng)閱讀Running in a Background Service. 當(dāng)一個(gè)service已經(jīng)不需要的時(shí)候還繼續(xù)保留它,這對(duì)Android應(yīng)用的內(nèi)存管理來說是最糟糕的錯(cuò)誤之一,。因此千萬不要貪婪的使得一個(gè)Service持續(xù)保留,。不僅僅是因?yàn)樗鼤?huì)使得你的app因RAM的限制而性能糟糕,而且用戶會(huì)發(fā)現(xiàn)那些行為奇怪的app并且卸載它,。 2)當(dāng)你的UI隱藏時(shí)釋放內(nèi)存當(dāng)用戶切換到其它app并且你的app UI不再可見時(shí),,你應(yīng)該釋放你的UI上占用的任何資源。在這個(gè)時(shí)候釋放UI資源可以顯著的增加系統(tǒng)cached process的能力,,它會(huì)對(duì)用戶的質(zhì)量體驗(yàn)有著直接的影響,。 為了能夠接收到用戶離開你的UI時(shí)的通知,你需要實(shí)現(xiàn)Activtiy類里面的onTrimMemory())回調(diào)方法,。你應(yīng)該使用這個(gè)方法來監(jiān)聽到TRIM_MEMORY_UI_HIDDEN級(jí)別, 它意味著你的UI已經(jīng)隱藏,,你應(yīng)該釋放那些僅僅被你的UI使用的資源。 請(qǐng)注意:你的app僅僅會(huì)在所有UI組件的被隱藏的時(shí)候接收到onTrimMemory()的回調(diào)并帶有參數(shù)TRIM_MEMORY_UI_HIDDEN,。這與onStop()的回調(diào)是不同的,onStop會(huì)在activity的實(shí)例隱藏時(shí)會(huì)執(zhí)行,,例如當(dāng)用戶從你的app的某個(gè)activity跳轉(zhuǎn)到另外一個(gè)activity時(shí)onStop會(huì)被執(zhí)行,。因此你應(yīng)該實(shí)現(xiàn)onStop回調(diào),并且在此回調(diào)里面釋放activity的資源,例如網(wǎng)絡(luò)連接,,unregister廣播接收者,。除非接收到onTrimMemory(TRIM_MEMORY_UI_HIDDEN))的回調(diào),否者你不應(yīng)該釋放你的UI資源,。這確保了用戶從其他activity切回來時(shí),,你的UI資源仍然可用,并且可以迅速恢復(fù)activity,。 3)當(dāng)內(nèi)存緊張時(shí)釋放部分內(nèi)存在你的app生命周期的任何階段,,onTrimMemory回調(diào)方法同樣可以告訴你整個(gè)設(shè)備的內(nèi)存資源已經(jīng)開始緊張。你應(yīng)該根據(jù)onTrimMemory方法中的內(nèi)存級(jí)別來進(jìn)一步?jīng)Q定釋放哪些資源,。
同樣,,當(dāng)你的app進(jìn)程正在被cached時(shí),,你可能會(huì)接受到從onTrimMemory()中返回的下面的值之一:
因?yàn)閛nTrimMemory()的回調(diào)是在API 14才被加進(jìn)來的,,對(duì)于老的版本,,你可以使用onLowMemory)回調(diào)來進(jìn)行兼容。onLowMemory相當(dāng)與TRIM_MEMORY_COMPLETE,。 Note: 當(dāng)系統(tǒng)開始清除LRU緩存中的進(jìn)程時(shí),,盡管它首先按照LRU的順序來操作,但是它同樣會(huì)考慮進(jìn)程的內(nèi)存使用量,。因此消耗越少的進(jìn)程則越容易被留下來,。 4)檢查你應(yīng)該使用多少的內(nèi)存正如前面提到的,每一個(gè)Android設(shè)備都會(huì)有不同的RAM總大小與可用空間,,因此不同設(shè)備為app提供了不同大小的heap限制,。你可以通過調(diào)用getMemoryClass())來獲取你的app的可用heap大小。如果你的app嘗試申請(qǐng)更多的內(nèi)存,,會(huì)出現(xiàn)OutOfMemory的錯(cuò)誤,。 在一些特殊的情景下,你可以通過在manifest的application標(biāo)簽下添加largeHeap=true的屬性來聲明一個(gè)更大的heap空間,。如果你這樣做,,你可以通過getLargeMemoryClass())來獲取到一個(gè)更大的heap size。 然而,,能夠獲取更大heap的設(shè)計(jì)本意是為了一小部分會(huì)消耗大量RAM的應(yīng)用(例如一個(gè)大圖片的編輯應(yīng)用),。不要輕易的因?yàn)槟阈枰褂么罅康膬?nèi)存而去請(qǐng)求一個(gè)大的heap size。只有當(dāng)你清楚的知道哪里會(huì)使用大量的內(nèi)存并且為什么這些內(nèi)存必須被保留時(shí)才去使用large heap. 因此請(qǐng)盡量少使用large heap,。使用額外的內(nèi)存會(huì)影響系統(tǒng)整體的用戶體驗(yàn),,并且會(huì)使得GC的每次運(yùn)行時(shí)間更長。在任務(wù)切換時(shí),,系統(tǒng)的性能會(huì)變得大打折扣,。 另外, large heap并不一定能夠獲取到更大的heap。在某些有嚴(yán)格限制的機(jī)器上,,large heap的大小和通常的heap size是一樣的,。因此即使你申請(qǐng)了large heap,你還是應(yīng)該通過執(zhí)行g(shù)etMemoryClass()來檢查實(shí)際獲取到的heap大小,。 5)避免bitmaps的浪費(fèi)當(dāng)你加載一個(gè)bitmap時(shí),,僅僅需要保留適配當(dāng)前屏幕設(shè)備分辨率的數(shù)據(jù)即可,如果原圖高于你的設(shè)備分辨率,,需要做縮小的動(dòng)作,。請(qǐng)記住,增加bitmap的尺寸會(huì)對(duì)內(nèi)存呈現(xiàn)出2次方的增加,,因?yàn)閄與Y都在增加,。 Note:在Android 2.3.x (API level 10)及其以下, bitmap對(duì)象是的pixel data是存放在native內(nèi)存中的,它不便于調(diào)試,。然而,,從Android 3.0(API level 11)開始,,bitmap pixel data是分配在你的app的Dalvik heap中, 這提升了GC的工作并且更加容易Debug。因此如果你的app使用bitmap并在舊的機(jī)器上引發(fā)了一些內(nèi)存問題,,切換到3.0以上的機(jī)器上進(jìn)行Debug。 6)使用優(yōu)化的數(shù)據(jù)容器利用Android Framework里面優(yōu)化過的容器類,,例如SparseArray, SparseBooleanArray, 與 LongSparseArray. 通常的HashMap的實(shí)現(xiàn)方式更加消耗內(nèi)存,,因?yàn)樗枰粋€(gè)額外的實(shí)例對(duì)象來記錄Mapping操作。另外,,SparseArray更加高效在于他們避免了對(duì)key與value的autobox自動(dòng)裝箱,,并且避免了裝箱后的解箱。 7)請(qǐng)注意內(nèi)存開銷對(duì)你所使用的語言與庫的成本與開銷有所了解,,從開始到結(jié)束,,在設(shè)計(jì)你的app時(shí)謹(jǐn)記這些信息。通常,,表面上看起來無關(guān)痛癢(innocuous)的事情也許實(shí)際上會(huì)導(dǎo)致大量的開銷,。例如:
8)請(qǐng)注意代碼“抽象”通常, 開發(fā)者使用抽象簡單的作為”好的編程實(shí)踐”,因?yàn)槌橄竽軌蛱嵘a的靈活性與可維護(hù)性,。然而,抽象會(huì)導(dǎo)致一個(gè)顯著的開銷:通常他們需要同等量的代碼用于可執(zhí)行,。那些代碼會(huì)被map到內(nèi)存中,。因此如果你的抽象沒有顯著的提升效率,應(yīng)該盡量避免他們,。 9)為序列化的數(shù)據(jù)使用nano protobufsProtocol buffers是由Google為序列化結(jié)構(gòu)數(shù)據(jù)而設(shè)計(jì)的,,一種語言無關(guān),平臺(tái)無關(guān),,具有良好擴(kuò)展性的協(xié)議,。類似XML,卻比XML更加輕量,,快速,,簡單。如果你需要為你的數(shù)據(jù)實(shí)現(xiàn)協(xié)議化,,你應(yīng)該在客戶端的代碼中總是使用nano protobufs,。通常的協(xié)議化操作會(huì)生成大量繁瑣的代碼,這容易給你的app帶來許多問題:增加RAM的使用量,,顯著增加APK的大小,,更慢的執(zhí)行速度,,更容易達(dá)到DEX的字符限制。 關(guān)于更多細(xì)節(jié),,請(qǐng)參考protobuf readme的”Nano version”章節(jié),。 10)Avoid dependency injection frameworks使用類似Guice或者RoboGuice等framework injection包是很有效的,因?yàn)樗麄兡軌蚝喕愕拇a,。
然而,,那些框架會(huì)通過掃描你的代碼執(zhí)行許多初始化的操作,這會(huì)導(dǎo)致你的代碼需要大量的RAM來map代碼,。但是mapped pages會(huì)長時(shí)間的被保留在RAM中,。 11)謹(jǐn)慎使用external libraries很多External library的代碼都不是為移動(dòng)網(wǎng)絡(luò)環(huán)境而編寫的,在移動(dòng)客戶端則顯示的效率不高,。至少,,當(dāng)你決定使用一個(gè)external library的時(shí)候,你應(yīng)該針對(duì)移動(dòng)網(wǎng)絡(luò)做繁瑣的porting與maintenance的工作,。 即使是針對(duì)Android而設(shè)計(jì)的library,,也可能是很危險(xiǎn)的,因?yàn)槊恳粋€(gè)library所做的事情都是不一樣的,。例如,,其中一個(gè)lib使用的是nano protobufs, 而另外一個(gè)使用的是micro protobufs。那么這樣,,在你的app里面就有2種protobuf的實(shí)現(xiàn)方式,。這樣的沖突同樣可能發(fā)生在輸出日志,加載圖片,,緩存等等模塊里面,。 同樣不要陷入為了1個(gè)或者2個(gè)功能而導(dǎo)入整個(gè)library的陷阱。如果沒有一個(gè)合適的庫與你的需求相吻合,,你應(yīng)該考慮自己去實(shí)現(xiàn),,而不是導(dǎo)入一個(gè)大而全的解決方案。 12)優(yōu)化整體性能官方有列出許多優(yōu)化整個(gè)app性能的文章:Best Practices for Performance. 這篇文章就是其中之一,。有些文章是講解如何優(yōu)化app的CPU使用效率,,有些是如何優(yōu)化app的內(nèi)存使用效率。 你還應(yīng)該閱讀optimizing your UI來為layout進(jìn)行優(yōu)化,。同樣還應(yīng)該關(guān)注lint工具所提出的建議,,進(jìn)行優(yōu)化。 13)使用ProGuard來剔除不需要的代碼ProGuard能夠通過移除不需要的代碼,,重命名類,,域與方法等方對(duì)代碼進(jìn)行壓縮,優(yōu)化與混淆。使用ProGuard可以是的你的代碼更加緊湊,,這樣能夠使用更少mapped代碼所需要的RAM,。 14)對(duì)最終的APK使用zipalign在編寫完所有代碼,,并通過編譯系統(tǒng)生成APK之后,你需要使用zipalign對(duì)APK進(jìn)行重新校準(zhǔn),。如果你不做這個(gè)步驟,,會(huì)導(dǎo)致你的APK需要更多的RAM,因?yàn)橐恍╊愃茍D片資源的東西不能被mapped,。 Notes::Google Play不接受沒有經(jīng)過zipalign的APK,。 15)分析你的RAM使用情況一旦你獲取到一個(gè)相對(duì)穩(wěn)定的版本后,需要分析你的app整個(gè)生命周期內(nèi)使用的內(nèi)存情況,,并進(jìn)行優(yōu)化,更多細(xì)節(jié)請(qǐng)參考Investigating Your RAM Usage. 16)使用多進(jìn)程如果合適的話,,有一個(gè)更高級(jí)的技術(shù)可以幫助你的app管理內(nèi)存使用:通過把你的app組件切分成多個(gè)組件,,運(yùn)行在不同的進(jìn)程中。這個(gè)技術(shù)必須謹(jǐn)慎使用,,大多數(shù)app都不應(yīng)該運(yùn)行在多個(gè)進(jìn)程中,。因?yàn)槿绻褂貌划?dāng),它會(huì)顯著增加內(nèi)存的使用,,而不是減少,。當(dāng)你的app需要在后臺(tái)運(yùn)行與前臺(tái)一樣的大量的任務(wù)的時(shí)候,可以考慮使用這個(gè)技術(shù),。 一個(gè)典型的例子是創(chuàng)建一個(gè)可以長時(shí)間后臺(tái)播放的Music Player,。如果整個(gè)app運(yùn)行在一個(gè)進(jìn)程中,當(dāng)后臺(tái)播放的時(shí)候,,前臺(tái)的那些UI資源也沒有辦法得到釋放,。類似這樣的app可以切分成2個(gè)進(jìn)程:一個(gè)用來操作UI,另外一個(gè)用來后臺(tái)的Service. 你可以通過在manifest文件中聲明’android:process’屬性來實(shí)現(xiàn)某個(gè)組件運(yùn)行在另外一個(gè)進(jìn)程的操作,。
更多關(guān)于使用這個(gè)技術(shù)的細(xì)節(jié),,請(qǐng)參考原文,鏈接如下,。 文章學(xué)習(xí)自http://developer./training/articles/memory.html 轉(zhuǎn)載請(qǐng)注明出自http://kesenhoo.github.com,,謝謝 |
|