您好,登錄后才能下訂單哦!
RAM對(duì)于軟件開(kāi)發(fā)環(huán)境而言是有價(jià)值的資源,但它對(duì)受限于物理內(nèi)存限制的操作系統(tǒng)具有更大的價(jià)值。即使Android Runtime和Dalvik virtual machein執(zhí)行常規(guī)的垃圾回收,但這并不意味著你可以忽略app在何時(shí)何地指派和釋放內(nèi)存。你仍然需要去避免產(chǎn)生內(nèi)存泄露。比如長(zhǎng)期持有靜態(tài)成員變量常常引起內(nèi)存泄露。我們應(yīng)該在合適的時(shí)間,比如在生命周期回調(diào)函數(shù)處釋放一些引用對(duì)象,來(lái)避免內(nèi)存泄露的發(fā)生。
這篇文章將闡述怎樣在app中主動(dòng)的降低內(nèi)存消耗。關(guān)于java編程中清理資源的一般實(shí)踐,請(qǐng)參照其他關(guān)于資源引用管理的書(shū)籍或者在線文檔。如果你想分析一個(gè)運(yùn)行中app的內(nèi)存情況,請(qǐng)閱讀文章“Tools for analyzing RAM usage”。如果你想知道AndroidRuntime和Dalvik virtual machine如何管理內(nèi)存,請(qǐng)參閱文章“Overview of Android Memory Management”。
(一)監(jiān)控內(nèi)存的使用情況:
Androidframework和Android Studio能夠幫助你分析和調(diào)整app的內(nèi)存使用情況。Android framework 暴露了幾個(gè)在app運(yùn)行時(shí)動(dòng)態(tài)降低內(nèi)存消耗的API。Android Studio 提供了幾個(gè)觀察app內(nèi)存使用情況的工具。
分析內(nèi)存使用的工具:
想要解決app產(chǎn)生的內(nèi)存問(wèn)題,我們首先得知道內(nèi)存問(wèn)題由什么引起。Android Studio提供了如下幾個(gè)分析內(nèi)存的工具:
1.“MemoryMonitor”展示app 在一個(gè)單獨(dú)回話過(guò)程中如何指派內(nèi)存。這個(gè)工具會(huì)實(shí)時(shí)繪制包括垃圾回收事件等可用內(nèi)存和已占用內(nèi)存的模擬圖像。在程序運(yùn)行時(shí),你僅僅能通過(guò)初始化一個(gè)垃圾回收事件,獲取Java 堆快照。這個(gè)Memory Monitor 的輸出圖像能幫你定位到app在什么地方因產(chǎn)生過(guò)多的垃圾回收事件導(dǎo)致程序變慢。
關(guān)于Memory Monitor的具體使用方法,請(qǐng)參閱“ViewingHeap Updates”
2.“AllocationTracker”能夠讓我們?cè)敿?xì)的跟蹤到app如何分配內(nèi)存。這個(gè)工具能夠記錄app的內(nèi)存分配情況,而且能夠列出所有被分配的對(duì)象以及分析快照。用這個(gè)工具你能夠追蹤到分配太多對(duì)象的代碼。
關(guān)于AllocationTracker的具體使用方法,請(qǐng)參閱“Allocation Tracker Walkthrough”
(二)在合適的時(shí)間釋放內(nèi)存
Android設(shè)備在運(yùn)行時(shí)的可用內(nèi)存會(huì)根據(jù)物理內(nèi)存總量以及用戶(hù)操作方法不斷變化。當(dāng)系統(tǒng)內(nèi)存有壓力的情況下會(huì)發(fā)送信號(hào)來(lái)通知程序。app應(yīng)監(jiān)聽(tīng)這些通知,并依據(jù)監(jiān)聽(tīng)結(jié)果來(lái)調(diào)整內(nèi)存使之使用適度。
用APIComponentCallbacks2 來(lái)響應(yīng)app生命周期事件和 設(shè)備事件,根據(jù)監(jiān)聽(tīng)相關(guān)信號(hào)的結(jié)果調(diào)整內(nèi)存的使用。方法onTrimMemory()能夠監(jiān)聽(tīng)app運(yùn)行在前臺(tái)或后臺(tái)時(shí)候的相關(guān)內(nèi)存事件。
在Activity中,實(shí)現(xiàn)回調(diào)方法onTrimMemory()來(lái)監(jiān)聽(tīng)這些內(nèi)存變化的事件,如下代碼片段:
import android.content.ComponentCallbacks2;
// Other import statements ...
publicclassMainActivityextendsAppCompatActivity
implementsComponentCallbacks2{
// Other activity code ...
/**
* Release memory when the UI becomes hidden or whensystem resources become low.
* @param level the memory-related event that wasraised.
*/
publicvoid onTrimMemory(int level){
// Determine which lifecycle or systemevent was raised.
switch(level){
caseComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
/*
Release any UI objects that currently hold memory.
Theuser interface has moved to the background.
*/
break;
caseComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
caseComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
caseComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
/*
Release any memory that your app doesn't need to run.
Thedevice is running low on memory while the app is running.
Theevent raised indicates the severity of the memory-related event.
Ifthe event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
beginkilling background processes.
*/
break;
caseComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
caseComponentCallbacks2.TRIM_MEMORY_MODERATE:
caseComponentCallbacks2.TRIM_MEMORY_COMPLETE:
/*
Release as much memory as the process can.
Theapp is on the LRU list and the system is running low on memory.
Theevent raised indicates where the app sits within the LRU list.
Ifthe event is TRIM_MEMORY_COMPLETE, the process will be one of
thefirst to be terminated.
*/
break;
default:
/*
Release anynon-critical data structures.
The appreceived an unrecognized memory level value
from thesystem. Treat this as a generic low-memory message.
*/
break;
}
}
}
備注:回調(diào)方法onTrimMemory()在Android4.0中添加。更早的版本,能用回調(diào)方法onLowMemory()作為替代,大約和 TRIM_MEMORY_COMPLETE 事件相同。
(三)檢查你需要用到的內(nèi)存:
為了支持多進(jìn)程,android為每一個(gè)app設(shè)置了一個(gè)堆內(nèi)存上限。不同設(shè)備為app分配的具體上限值,取決于RAM的總大小。如果程序到達(dá)了這個(gè)上限值還想占用更多的內(nèi)存,系統(tǒng)將會(huì)拋出異常 OutOfMemoryError。
為了避免內(nèi)存溢出,我們可以查詢(xún)當(dāng)前設(shè)備為app指定的內(nèi)存上限值。我們可以通過(guò)調(diào)用方法getMemoryInfo()來(lái)查詢(xún)當(dāng)前系統(tǒng)的這個(gè)屬性值。這個(gè)方法將會(huì)返回一個(gè)包含可用內(nèi)存、總內(nèi)存、內(nèi)存臨界值(如果系統(tǒng)可用內(nèi)存低于這個(gè)臨界值將會(huì)殺掉部分進(jìn)程)等當(dāng)前設(shè)備狀態(tài)的ActivityManager.MemoryInfo對(duì)象。ActivityManager.MemoryInfo也提供了一個(gè)boolean類(lèi)型的字段lowMemory,記錄當(dāng)前設(shè)備是否運(yùn)行在低內(nèi)存情況下。
下面的代碼片段展示了一個(gè)使用geMemoryInfo()方法的例子:
public void doSomethingMemoryIntensive(){
// Before doing something that requires a lot of memory,
// check to see whether the device is in a low memory state.
ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();
if(!memoryInfo.lowMemory){
// Do memory intensive work ...
}
}
// Get a MemoryInfo object for the device's current memory status.
privateActivityManager.MemoryInfo getAvailableMemory(){
ActivityManager activityManager =(ActivityManager)this.getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo =newActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
return memoryInfo;
}
(四)使用高效內(nèi)存的代碼結(jié)構(gòu)
一些android特性、java類(lèi)、代碼結(jié)構(gòu)趨向于用更多的內(nèi)存。我們可以通過(guò)選擇更高效的替代方案來(lái)使我們app用最少的內(nèi)存。
(1)少用Services:
在不必要的時(shí)候運(yùn)行一個(gè)服務(wù),是app最糟糕的內(nèi)存管理錯(cuò)誤之一。如果app需要運(yùn)行一個(gè)服務(wù)在后臺(tái)執(zhí)行工作,除非有必要的任務(wù),否則不要一直運(yùn)行。當(dāng)Service完成它的任務(wù)后一定要記得停這個(gè)Service。否則,可能一不留神就產(chǎn)生一個(gè)內(nèi)存泄露。
開(kāi)啟一個(gè)Service后,系統(tǒng)喜歡為這個(gè)service開(kāi)啟一個(gè)進(jìn)程使其持續(xù)運(yùn)行。這種情況使Service非常耗費(fèi)資源,因?yàn)槠渌M(jìn)程無(wú)法使用被service所在進(jìn)程占用的RAM資源。系統(tǒng)緩存在LRU catche的進(jìn)程數(shù)量減少后,使app運(yùn)行效率降低。更有甚者,當(dāng)內(nèi)存緊張,系統(tǒng)無(wú)法維持足夠的進(jìn)程支持所有service的時(shí)候,service可能被銷(xiāo)毀。
通常情況下我們應(yīng)該避免長(zhǎng)期使用service,因?yàn)樗鼈儠?huì)一直占用內(nèi)存。我們推薦使用可供選擇的類(lèi)比如JobScheduler 去調(diào)度后臺(tái)進(jìn)程。
關(guān)于如何使用JobScheduler調(diào)度后臺(tái)進(jìn)程,參閱“BackgroundOptimizations”
(2)使用最優(yōu)化的數(shù)據(jù)容器:
編程語(yǔ)言提供的部分類(lèi)對(duì)于移動(dòng)設(shè)備不是最優(yōu)化的。例如一般的HashMap實(shí)現(xiàn)能使很多內(nèi)存低效率工作,因?yàn)樗枰獮槊恳粋€(gè)映射生成一個(gè)單獨(dú)的條目對(duì)象。
Androidframework包含幾個(gè)最優(yōu)化的數(shù)據(jù)容器,包括SparseArray,SparseBooleanArray,LongSparseArray。例如 AparseArray之所以更高效,是由于它們避免系統(tǒng)的需要,對(duì)key(有時(shí)候也對(duì)value)進(jìn)行自動(dòng)裝箱。
如果有必要,我們可以總是選擇原始的數(shù)組作為最精煉的數(shù)據(jù)結(jié)構(gòu)。
(3)謹(jǐn)慎使用抽象代碼:
開(kāi)發(fā)者通常僅僅把抽象當(dāng)成一種好的編程實(shí)踐,因?yàn)槌橄罂梢蕴嵘a的靈活性和可維護(hù)性。然而,抽象伴隨著巨大的資源耗費(fèi)而來(lái):通常它們會(huì)需要更多的時(shí)間和更多的內(nèi)存,需要執(zhí)行相當(dāng)數(shù)量的代碼,花費(fèi)更多的時(shí)間和內(nèi)存。所以如果抽象不能帶來(lái)巨大好處,我們應(yīng)該盡可能去避免。
例如:枚舉需要消耗的內(nèi)存通常是靜態(tài)變量的兩倍多。我們應(yīng)該嚴(yán)格避免在android上使用枚舉。
(4)使用nanoprotobufs序列化數(shù)據(jù)
Protocol buffers 是一個(gè)谷歌設(shè)計(jì)的具有語(yǔ)言中立、平臺(tái)中立、可擴(kuò)展、用來(lái)序列化結(jié)構(gòu)數(shù)據(jù)的類(lèi)似于XML的機(jī)制,但其比XML快,比XML小,比XML簡(jiǎn)單。如果你決定使用protobufs處理數(shù)據(jù),那么我們應(yīng)該在客戶(hù)端代碼中一直使用nano protobufs。普通的protobufs通常生成非常多的代碼,這樣會(huì)引起app產(chǎn)生很多問(wèn)題,比如占用更多內(nèi)存、APK尺寸大幅度增加、運(yùn)行緩慢。
更多運(yùn)行,請(qǐng)參閱文章"protobuf readme"中"Nanoversion"的段落。
(5)避免內(nèi)存抖動(dòng)
就像上文提醒到的,GC事件通常不會(huì)影響app的性能。然而一些發(fā)生在短時(shí)間內(nèi)的GC事件能迅速耗盡你的幀像時(shí)間。系統(tǒng)花費(fèi)在GC上的時(shí)間越多,那么它處理其他諸如渲染或者音頻流的事件。
通常情況下下,內(nèi)存抖動(dòng)能引起大量的的GC事件。在實(shí)踐中,內(nèi)存抖動(dòng)描繪的是,在一個(gè)給定時(shí)間內(nèi),分配臨時(shí)對(duì)象的數(shù)目。
例如,你可能通過(guò)for循環(huán)分配多個(gè)臨時(shí)對(duì)象?;蛘咴谝粋€(gè)View的onDraw方法中創(chuàng)建一個(gè)Paint或者Bitmap對(duì)象。如上兩個(gè)例子,app迅速地創(chuàng)建了大量的大體積對(duì)象。年青一代的這些行為將會(huì)迅速的消耗所有的可用內(nèi)存,強(qiáng)制一個(gè)垃圾回收事件產(chǎn)生。
當(dāng)然,在你修復(fù)內(nèi)存抖動(dòng)之前,需要先發(fā)現(xiàn)導(dǎo)致內(nèi)存抖動(dòng)發(fā)生的代碼所在地方。請(qǐng)用在文章"Analyze your RAM usage"中討論的工具。
一旦你識(shí)別代碼出現(xiàn)問(wèn)題的區(qū)域,試著降低處于性能臨界區(qū)域的配置數(shù)目??紤]移除內(nèi)循環(huán)的東西,或者將它們移到基于分配結(jié)構(gòu)的 Factory(參閱文章:)。
(五)降低內(nèi)存占有量——累贅的資源和庫(kù)
一些資源和庫(kù)在你的代碼能在你不知道的情況下貪婪的消耗內(nèi)存。apk的大小、包括第三方庫(kù)以及嵌入的資源、能夠影響app消耗的內(nèi)存量。我們能通過(guò)移除代碼中多余的、不必要的、臃腫的組件、資源或者庫(kù)的方式來(lái)改善app的內(nèi)存消耗。
(1)降低apk大小
我們能通過(guò)降低apk大小的方式顯著的降低app的內(nèi)存使用情況。bitmap尺寸、資源、動(dòng)畫(huà)的幀、第三方庫(kù)都能導(dǎo)致apk變大。android sdk 和 android studio 提供了多種工具來(lái)幫助我們降低資源和外部依賴(lài)的尺寸。
關(guān)于降低apk大小的更多信息,參閱"ReduceAPK Size"。
(2)如果需要依賴(lài)注入,建議使用Dagger
依賴(lài)注入框架能夠簡(jiǎn)化你寫(xiě)的代碼以及為測(cè)試和測(cè)試改變提供一個(gè)適配的環(huán)境。如果你打算在app中用一個(gè)依賴(lài)注入框架,考慮使用Dagger2(參閱https://google.github.io/dagger/).
Dagger不會(huì)用反射掃描app代碼。它是靜態(tài)的、編譯時(shí)實(shí)現(xiàn)的框架,這意味著在運(yùn)行時(shí)沒(méi)有不必要的運(yùn)行時(shí)花費(fèi)或者內(nèi)存使用。
其他的那些依賴(lài)注入框架通過(guò)掃描代碼和注解,運(yùn)用反射去初始化進(jìn)程。這個(gè)產(chǎn)生的進(jìn)程會(huì)需要大量的CPU周期以及內(nèi)存,能在app啟動(dòng)時(shí)引起一個(gè)顯而易見(jiàn)的延遲。
(3)謹(jǐn)慎使用依賴(lài)庫(kù):
外部庫(kù)的代碼通常不是為手機(jī)環(huán)境所寫(xiě),使其在手機(jī)客戶(hù)端上工作時(shí)可能是很低效的。當(dāng)我們決定用一個(gè)依賴(lài)庫(kù),可能需要針對(duì)移動(dòng)設(shè)備進(jìn)行優(yōu)化。在決定真正使用這個(gè)依賴(lài)庫(kù)之前,應(yīng)預(yù)先做好針對(duì)移動(dòng)設(shè)備優(yōu)化的計(jì)劃,依據(jù)代碼尺寸和內(nèi)存大小進(jìn)行分析。
即使一些移動(dòng)優(yōu)化的庫(kù)也能由于不同的實(shí)現(xiàn)引起問(wèn)題。例如,當(dāng)一個(gè)庫(kù)使用了micro protobufs而一個(gè)庫(kù)使用了nano protobufs,這樣app中有兩個(gè)不同的 protobuf實(shí)現(xiàn)。當(dāng)問(wèn)題發(fā)生時(shí),原因可能是logging,analytics,p_w_picpath loading frameworks,caching或者一些其他我們無(wú)法預(yù)料的事情的不同實(shí)現(xiàn)導(dǎo)致的。
盡管ProGuard(參閱:https://developer.android.com/studio/build/shrink-code.html)能根據(jù)正確的標(biāo)記移除APIs和資源,但是它不能移除一個(gè)庫(kù)大型的依賴(lài)。你在庫(kù)中想要的特性可能需要低版本的依賴(lài)....
翻譯整理,更多內(nèi)容,敬請(qǐng)期待....
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。