自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Android性能優(yōu)化-OOM崩潰引起的內(nèi)存管理大爆炸詳解

移動開發(fā) Android
各種圖標(biāo)圖片資源,如果不能很好的處理圖片的利用。會導(dǎo)致app性能嚴(yán)重下降,影響用戶體驗,最直觀的感受就是卡頓,手機(jī)發(fā)熱,有時候還OOM,那么今天我們就來分析oom和內(nèi)存優(yōu)化總結(jié)。

[[414819]]

前言

app開發(fā)中,圖片是少不了的。各種圖標(biāo)圖片資源,如果不能很好的處理圖片的利用。會導(dǎo)致app性能嚴(yán)重下降,影響用戶體驗,最直觀的感受就是卡頓,手機(jī)發(fā)熱,有時候還OOM,

那么今天我們就來分析oom和內(nèi)存優(yōu)化總結(jié);

一、什么是OOM?

  • OOM,全稱“Out Of Memory”,翻譯成中文就是“內(nèi)存用完了”,來源于java.lang.OutOfMemoryError;
  • 當(dāng)JVM因為沒有足夠的內(nèi)存來為對象分配空間并且垃圾回收器也已經(jīng)沒有空間可回收時,就會拋出這個error(注:非exception,因為這個問題已經(jīng)嚴(yán)重到不足以被應(yīng)用處理);
  • 在客戶端App中這個現(xiàn)象通常出現(xiàn)在用到很多圖片或者很大圖片的APP開發(fā)中;通俗講就是當(dāng)我們的APP需要申請一塊內(nèi)存來裝圖片的時候,系統(tǒng)覺得我們的APP所使用的內(nèi)存已經(jīng)夠多了,即使它有1G的空余內(nèi)存,它不同意給我的APP更多的內(nèi)存里,然后即使系統(tǒng)馬上拋出OOM錯誤,而程序沒有捕捉該錯誤,故彈框崩潰了;

二、OOM的類型

1、JVM內(nèi)存模型:

圖片

按照J(rèn)VM規(guī)范,JAVA虛擬機(jī)在運(yùn)行時會管理以下的內(nèi)存區(qū)域:

  • 程序計數(shù)器:當(dāng)前線程執(zhí)行的字節(jié)碼的行號指示器,線程私有;
  • JAVA虛擬機(jī)棧:Java方法執(zhí)行的內(nèi)存模型,每個Java方法的執(zhí)行對應(yīng)著一個棧幀的進(jìn)棧和出棧的操作;
  • 本地方法棧:類似“ JAVA虛擬機(jī)棧 ”,但是為native方法的運(yùn)行提供內(nèi)存環(huán)境;
  • JAVA堆:對象內(nèi)存分配的地方,內(nèi)存垃圾回收的主要區(qū)域,所有線程共享??煞譃樾律?,老生代;
  • 方法區(qū):用于存儲已經(jīng)被JVM加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。Hotspot中的“永久代”;
  • 運(yùn)行時常量池:方法區(qū)的一部分,存儲常量信息,如各種字面量、符號引用等;
  • 直接內(nèi)存:并不是JVM運(yùn)行時數(shù)據(jù)區(qū)的一部分, 可直接訪問的內(nèi)存, 比如NIO會用到這部分;
  • 按照J(rèn)VM規(guī)范,除了程序計數(shù)器不會拋出OOM外,其他各個內(nèi)存區(qū)域都可能會拋出OOM;

2、最常見的OOM情況有以下三種:

  • java.lang.OutOfMemoryError: Java heap space ------>java堆內(nèi)存溢出,此種情況最常見,一般由于內(nèi)存泄露或者堆的大小設(shè)置不當(dāng)引起。對于內(nèi)存泄露,需要通過內(nèi)存監(jiān)控軟件查找程序中的泄露代碼,而堆大小可以通過虛擬機(jī)參數(shù)-Xms,-Xmx等修改;
  • java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法區(qū)溢出了,一般出現(xiàn)于大量Class或者jsp頁面,或者采用cglib等反射機(jī)制的情況,因為上述情況會產(chǎn)生大量的Class信息存儲于方法區(qū)。此種情況可以通過更改方法區(qū)的大小來解決,使用類似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,過多的常量尤其是字符串也會導(dǎo)致方法區(qū)溢出;
  • java.lang.StackOverflowError ------> 不會拋OOM error,但也是比較常見的Java內(nèi)存溢出。JAVA虛擬機(jī)棧溢出,一般是由于程序中存在死循環(huán)或者深度遞歸調(diào)用造成的,棧大小設(shè)置太小也會出現(xiàn)此種溢出??梢酝ㄟ^虛擬機(jī)參數(shù)-Xss來設(shè)置棧的大小;

三、為什么會OOM?

android系統(tǒng)的app的每個進(jìn)程或者每個虛擬機(jī)有個最大內(nèi)存限制,如果申請的內(nèi)存資源超過這個限制,系統(tǒng)就會拋出OOM錯誤;

跟整個設(shè)備的剩余內(nèi)存沒太大關(guān)系。比如比較早的android系統(tǒng)的一個虛擬機(jī)最多16M內(nèi)存,當(dāng)一個app啟動后,虛擬機(jī)不停的申請內(nèi)存資源來裝載圖片,當(dāng)超過內(nèi)存上限時就出現(xiàn)OOM

圖片

為什么會沒有內(nèi)存了呢?原因不外乎有兩點(diǎn):

1、分配的少了:比如虛擬機(jī)本身可使用的內(nèi)存(一般通過啟動時的VM參數(shù)指定)太少;

2、應(yīng)用用的太多,并且用完沒釋放,浪費(fèi)了,此時就會造成內(nèi)存泄露或者內(nèi)存溢出;

  • 內(nèi)存泄露:申請使用完的內(nèi)存沒有釋放,導(dǎo)致虛擬機(jī)不能再次使用該內(nèi)存,此時這段內(nèi)存就泄露了,因為申請者不用了,而又不能被虛擬機(jī)分配給別人用;
  • 內(nèi)存溢出:申請的內(nèi)存超出了JVM能提供的內(nèi)存大小,此時稱之為溢出;

在之前沒有垃圾自動回收的日子里,比如C語言和C++語言,我們必須親自負(fù)責(zé)內(nèi)存的申請與釋放操作,如果申請了內(nèi)存,用完后又忘記了釋放,比如C++中的new了但是沒有delete,那么就可能造成內(nèi)存泄露。偶爾的內(nèi)存泄露可能不會造成問題,而大量的內(nèi)存泄露可能會導(dǎo)致內(nèi)存溢出;

而在Java語言中,由于存在了垃圾自動回收機(jī)制,所以,我們一般不用去主動釋放不用的對象所占的內(nèi)存,也就是理論上來說,是不會存在“內(nèi)存泄露”的。但是,如果編碼不當(dāng),比如,將某個對象的引用放到了全局的Map中,雖然方法結(jié)束了,但是由于垃圾回收器會根據(jù)對象的引用情況來回收內(nèi)存,導(dǎo)致該對象不能被及時的回收。如果該種情況出現(xiàn)次數(shù)多了,就會導(dǎo)致內(nèi)存溢出,比如系統(tǒng)中經(jīng)常使用的緩存機(jī)制。Java中的內(nèi)存泄露,不同于C++中的忘了delete,往往是邏輯上的原因泄露。

四、如何規(guī)避OOM和進(jìn)行內(nèi)存優(yōu)化

1、減小對象的內(nèi)存占用

避免OOM的第一步就是要盡量減少新分配出來的對象占用內(nèi)存的大小,盡量使用更加輕量的對象。

1)使用更加輕量的數(shù)據(jù)結(jié)構(gòu)

我們可以考慮使用ArrayMap/SparseArray而不是HashMap等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu)。

HashMap的簡要工作原理,相比起Android專門為移動操作系統(tǒng)編寫的ArrayMap容器,在大多數(shù)情況下,都顯示效率低下,更占內(nèi)存。

通常的HashMap的實現(xiàn)方式更加消耗內(nèi)存,因為它需要一個額外的實例對象來記錄Mapping操作。

另外,SparseArray更加高效,在于他們避免了對key與value的自動裝箱(autoboxing),并且避免了裝箱后的解箱。

2)避免在Android里面使用Enum

枚舉通常需要兩倍于靜態(tài)常量的內(nèi)存。您應(yīng)該嚴(yán)格避免在Android上使用枚舉。,所以請避免在Android里面使用到枚舉。

3)減小Bitmap對象的內(nèi)存占用

Bitmap是一個極容易消耗內(nèi)存的大胖子,減小創(chuàng)建出來的Bitmap的內(nèi)存占用可謂是重中之重,通常來說有以下2個措施

inSampleSize:縮放比例,在把圖片載入內(nèi)存之前,我們需要先計算出一個合適的縮放比例,避免不必要的大圖載入。

decode format:解碼格式,選擇ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差異。

4)使用更小的圖片

在涉及給到資源圖片時,我們需要特別留意這張圖片是否存在可以壓縮的空間,是否可以使用更小的圖片。盡量使用更小的圖片不僅可以減少內(nèi)存的使用,還能避免出現(xiàn)大量的InflationException。假設(shè)有一張很大的圖片被XML文件直接引用,很有可能在初始化視圖時會因為內(nèi)存不足而發(fā)生InflationException,這個問題的根本原因其實是發(fā)生了OOM。

2、內(nèi)存對象的重復(fù)利用

  • 大多數(shù)對象的復(fù)用,最終實施的方案都是利用對象池技術(shù),要么是在編寫代碼時顯式地在程序里創(chuàng)建對象池,然后處理好復(fù)用的實現(xiàn)邏輯。要么就是利用系統(tǒng)框架既有的某些復(fù)用特性,減少對象的重復(fù)創(chuàng)建,從而降低內(nèi)存的分配與回收;
  • 復(fù)用系統(tǒng)自帶的資源:Android系統(tǒng)本身內(nèi)置了很多的資源,例如字符串/顏色/圖片/動畫/樣式以及簡單布局等等,這些資源都可以在應(yīng)用程序中直接引用。這樣做不僅僅可以減少應(yīng)用程序的自身負(fù)重,減小APK的大小,另外還可以一定程度上減少內(nèi)存的開銷,復(fù)用性更好。但是也有必要留意Android系統(tǒng)的版本差異性,對那些不同系統(tǒng)版本上表現(xiàn)存在很大差異,不符合需求的情況,還是需要應(yīng)用程序自身內(nèi)置進(jìn)去;
  • 注意在ListView/GridView等出現(xiàn)大量重復(fù)子組件的視圖里面對ConvertView的復(fù)用;
  • Bitmap對象的復(fù)用;
  • 避免在onDraw方法里面執(zhí)行對象的創(chuàng)建;
  • 類似onDraw等頻繁調(diào)用的方法,一定需要注意避免在這里做創(chuàng)建對象的操作,因為他會迅速增加內(nèi)存的使用,而且很容易引起頻繁的gc,甚至是內(nèi)存抖動;
  • StringBuilder:在有些時候,代碼中會需要使用到大量的字符串拼接的操作,這種時候有必要考慮使用StringBuilder來替代頻繁的“+”;

3、避免對象的內(nèi)存泄露

內(nèi)存對象的泄漏,會導(dǎo)致一些不再使用的對象無法及時釋放,這樣一方面占用了寶貴的內(nèi)存空間,很容易導(dǎo)致后續(xù)需要分配內(nèi)存的時候,空閑空間不足而出現(xiàn)OOM。顯然,這還使得每級Generation的內(nèi)存區(qū)域可用空間變小,GC就會更容易被觸發(fā),容易出現(xiàn)內(nèi)存抖動,從而引起性能問題

1)注意Activity的泄漏

  • 通常來說,Activity的泄漏是內(nèi)存泄漏里面最嚴(yán)重的問題,它占用的內(nèi)存多,影響面廣,我們需要特別注意以下兩種情況導(dǎo)致的Activity泄漏:
  • 內(nèi)部類引用導(dǎo)致Activity的泄漏:最典型的場景是Handler導(dǎo)致的Activity泄漏,如果Handler中有延遲的任務(wù)或者是等待執(zhí)行的任務(wù)隊列過長,都有可能因為Handler繼續(xù)執(zhí)行而導(dǎo)致Activity發(fā)生泄漏。此時的引用關(guān)系鏈?zhǔn)荓ooper -> MessageQueue -> Message -> Handler -> Activity。為了解決這個問題,可以在UI退出之前,執(zhí)行remove Handler消息隊列中的消息與runnable對象?;蛘呤鞘褂肧tatic + WeakReference的方式來達(dá)到斷開Handler與Activity之間存在引用關(guān)系的目的。
  • Activity Context被傳遞到其他實例中,這可能導(dǎo)致自身被引用而發(fā)生泄漏;
  • 內(nèi)部類引起的泄漏不僅僅會發(fā)生在Activity上,其他任何內(nèi)部類出現(xiàn)的地方,都需要特別留意!我們可以考慮盡量使用static類型的內(nèi)部類,同時使用WeakReference的機(jī)制來避免因為互相引用而出現(xiàn)的泄露;

2)考慮使用Application Context而不是Activity Context

對于大部分非必須使用Activity Context的情況(Dialog的Context就必須是Activity Context),我們都可以考慮使用Application Context而不是Activity的Context,這樣可以避免不經(jīng)意的Activity泄露;

3)注意臨時Bitmap對象的及時回收

  • 雖然在大多數(shù)情況下,我們會對Bitmap增加緩存機(jī)制,但是在某些時候,部分Bitmap是需要及時回收的。例如臨時創(chuàng)建的某個相對比較大的bitmap對象,在經(jīng)過變換得到新的bitmap對象之后,應(yīng)該盡快回收原始的bitmap,這樣能夠更快釋放原始bitmap所占用的空間。
  • 需要特別留意的是Bitmap類里面提供的createBitmap()方法:這個函數(shù)返回的bitmap有可能和source bitmap是同一個,在回收的時候,需要特別檢查source bitmap與return bitmap的引用是否相同,只有在不等的情況下,才能夠執(zhí)行source bitmap的recycle方法。

4)注意監(jiān)聽器的注銷

在Android程序里面存在很多需要register與unregister的監(jiān)聽器,我們需要確保在合適的時候及時unregister那些監(jiān)聽器。自己手動add的listener,需要記得及時remove這個listener。

5)注意緩存容器中的對象泄漏

有時候,我們?yōu)榱颂岣邔ο蟮膹?fù)用性把某些對象放到緩存容器中,可是如果這些對象沒有及時從容器中清除,也是有可能導(dǎo)致內(nèi)存泄漏的。例如,針對2.3的系統(tǒng),如果把drawable添加到緩存容器,因為drawable與View的強(qiáng)應(yīng)用,很容易導(dǎo)致activity發(fā)生泄漏。而從4.0開始,就不存在這個問題。解決這個問題,需要對2.3系統(tǒng)上的緩存drawable做特殊封裝,處理引用解綁的問題,避免泄漏的情況。

6)注意WebView的泄漏

Android中的WebView存在很大的兼容性問題,不僅僅是Android系統(tǒng)版本的不同對WebView產(chǎn)生很大的差異,另外不同的廠商出貨的ROM里面WebView也存在著很大的差異。更嚴(yán)重的是標(biāo)準(zhǔn)的WebView存在內(nèi)存泄露的問題,請看 這里。所以通常根治這個問題的辦法是為WebView開啟另外一個進(jìn)程,通過AIDL與主進(jìn)程進(jìn)行通信,WebView所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時機(jī)進(jìn)行銷毀,從而達(dá)到內(nèi)存的完整釋放。

7)注意Cursor對象是否及時關(guān)閉

在程序中我們經(jīng)常會進(jìn)行查詢數(shù)據(jù)庫的操作,但時常會存在不小心使用Cursor之后沒有及時關(guān)閉的情況。這些Cursor的泄露,反復(fù)多次出現(xiàn)的話會對內(nèi)存管理產(chǎn)生很大的負(fù)面影響,我們需要謹(jǐn)記對Cursor對象的及時關(guān)閉。

4、內(nèi)存使用策略優(yōu)化

1)謹(jǐn)慎使用large heap

  • 正如前面提到的,Android設(shè)備根據(jù)硬件與軟件的設(shè)置差異而存在不同大小的內(nèi)存空間,他們?yōu)閼?yīng)用程序設(shè)置了不同大小的Heap限制閾值。你可以通過調(diào)用getMemoryClass()來獲取應(yīng)用的可用Heap大小。在一些特殊的情景下,你可以通過在manifest的application標(biāo)簽下添加largeHeap=true的屬性來為應(yīng)用聲明一個更大的heap空間。然后,你可以通過getLargeMemoryClass()來獲取到這個更大的heap size閾值。然而,聲明得到更大Heap閾值的本意是為了一小部分會消耗大量RAM的應(yīng)用(例如一個大圖片的編輯應(yīng)用)。
  • 不要輕易的因為你需要使用更多的內(nèi)存而去請求一個大的Heap Size。只有當(dāng)你清楚的知道哪里會使用大量的內(nèi)存并且知道為什么這些內(nèi)存必須被保留時才去使用large heap。因此請謹(jǐn)慎使用large heap屬性。使用額外的內(nèi)存空間會影響系統(tǒng)整體的用戶體驗,并且會使得每次gc的運(yùn)行時間更長。
  • 在任務(wù)切換時,系統(tǒng)的性能會大打折扣。另外, large heap并不一定能夠獲取到更大的heap。在某些有嚴(yán)格限制的機(jī)器上,large heap的大小和通常的heap size是一樣的。因此即使你申請了large heap,你還是應(yīng)該通過執(zhí)行g(shù)etMemoryClass()來檢查實際獲取到的heap大小。

2)綜合考慮設(shè)備內(nèi)存閾值與其他因素設(shè)計合適的緩存大小

在設(shè)計ListView或者GridView的Bitmap LRU緩存的時候,需要考慮的點(diǎn)有:

  • 應(yīng)用程序剩下了多少可用的內(nèi)存空間?
  • 有多少圖片會被一次呈現(xiàn)到屏幕上?有多少圖片需要事先緩存好以便快速滑動時能夠立即顯示到屏幕?
  • 設(shè)備的屏幕大小與密度是多少? 一個xhdpi的設(shè)備會比hdpi需要一個更大的Cache來hold住同樣數(shù)量的圖片。
  • 不同的頁面針對Bitmap的設(shè)計的尺寸與配置是什么,大概會花費(fèi)多少內(nèi)存?
  • 頁面圖片被訪問的頻率?是否存在其中的一部分比其他的圖片具有更高的訪問頻繁?如果是,也許你想要保存那些最常訪問的到內(nèi)存中,或者為不同組別的位圖(按訪問頻率分組)設(shè)置多個LruCache容器。

3)onLowMemory()與onTrimMemory()

Android用戶可以隨意在不同的應(yīng)用之間進(jìn)行快速切換。為了讓background的應(yīng)用能夠迅速的切換到forground,每一個background的應(yīng)用都會占用一定的內(nèi)存。Android系統(tǒng)會根據(jù)當(dāng)前的系統(tǒng)的內(nèi)存使用情況,決定回收部分background的應(yīng)用內(nèi)存。如果background的應(yīng)用從暫停狀態(tài)直接被恢復(fù)到forground,能夠獲得較快的恢復(fù)體驗,如果background應(yīng)用是從Kill的狀態(tài)進(jìn)行恢復(fù),相比之下就顯得稍微有點(diǎn)慢。

  • onLowMemory():Android系統(tǒng)提供了一些回調(diào)來通知當(dāng)前應(yīng)用的內(nèi)存使用情況,通常來說,當(dāng)所有的background應(yīng)用都被kill掉的時候,forground應(yīng)用會收到onLowMemory()的回調(diào)。在這種情況下,需要盡快釋放當(dāng)前應(yīng)用的非必須的內(nèi)存資源,從而確保系統(tǒng)能夠繼續(xù)穩(wěn)定運(yùn)行。
  • onTrimMemory(int):Android系統(tǒng)從4.0開始還提供了onTrimMemory()的回調(diào),當(dāng)系統(tǒng)內(nèi)存達(dá)到某些條件的時候,所有正在運(yùn)行的應(yīng)用都會收到這個回調(diào),同時在這個回調(diào)里面會傳遞以下的參數(shù),代表不同的內(nèi)存使用情況,收到onTrimMemory()回調(diào)的時候,需要根據(jù)傳遞的參數(shù)類型進(jìn)行判斷,合理的選擇釋放自身的一些內(nèi)存占用,一方面可以提高系統(tǒng)的整體運(yùn)行流暢度,另外也可以避免自己被系統(tǒng)判斷為優(yōu)先需要?dú)⒌舻膽?yīng)用。
  • TRIM_MEMORY_UI_HIDDEN:你的應(yīng)用程序的所有UI界面被隱藏了,即用戶點(diǎn)擊了Home鍵或者Back鍵退出應(yīng)用,導(dǎo)致應(yīng)用的UI界面完全不可見。這個時候應(yīng)該釋放一些不可見的時候非必須的資源

當(dāng)程序正在前臺運(yùn)行的時候,可能會接收到從onTrimMemory()中返回的下面的值之一:

  • TRIM_MEMORY_RUNNING_MODERATE:你的應(yīng)用正在運(yùn)行并且不會被列為可殺死的。但是設(shè)備此時正運(yùn)行于低內(nèi)存狀態(tài)下,系統(tǒng)開始觸發(fā)殺死LRU Cache中的Process的機(jī)制。
  • TRIM_MEMORY_RUNNING_LOW:你的應(yīng)用正在運(yùn)行且沒有被列為可殺死的。但是設(shè)備正運(yùn)行于更低內(nèi)存的狀態(tài)下,你應(yīng)該釋放不用的資源用來提升系統(tǒng)性能。
  • TRIM_MEMORY_RUNNING_CRITICAL:你的應(yīng)用仍在運(yùn)行,但是系統(tǒng)已經(jīng)把LRU Cache中的大多數(shù)進(jìn)程都已經(jīng)殺死,因此你應(yīng)該立即釋放所有非必須的資源。如果系統(tǒng)不能回收到足夠的RAM數(shù)量,系統(tǒng)將會清除所有的LRU緩存中的進(jìn)程,并且開始?xì)⑺滥切┲氨徽J(rèn)為不應(yīng)該殺死的進(jìn)程,例如那個包含了一個運(yùn)行態(tài)Service的進(jìn)程。

當(dāng)應(yīng)用進(jìn)程退到后臺正在被Cached的時候,可能會接收到從onTrimMemory()中返回的下面的值之一:

  • TRIM_MEMORY_BACKGROUND: 系統(tǒng)正運(yùn)行于低內(nèi)存狀態(tài)并且你的進(jìn)程正處于LRU緩存名單中最不容易殺掉的位置。盡管你的應(yīng)用進(jìn)程并不是處于被殺掉的高危險狀態(tài),系統(tǒng)可能已經(jīng)開始?xì)⒌鬖RU緩存中的其他進(jìn)程了。你應(yīng)該釋放那些容易恢復(fù)的資源,以便于你的進(jìn)程可以保留下來,這樣當(dāng)用戶回退到你的應(yīng)用的時候才能夠迅速恢復(fù)。
  • TRIM_MEMORY_MODERATE: 系統(tǒng)正運(yùn)行于低內(nèi)存狀態(tài)并且你的進(jìn)程已經(jīng)已經(jīng)接近LRU名單的中部位置。如果系統(tǒng)開始變得更加內(nèi)存緊張,你的進(jìn)程是有可能被殺死的。
  • TRIM_MEMORY_COMPLETE: 系統(tǒng)正運(yùn)行于低內(nèi)存的狀態(tài)并且你的進(jìn)程正處于LRU名單中最容易被殺掉的位置。你應(yīng)該釋放任何不影響你的應(yīng)用恢復(fù)狀態(tài)的資源。

因為onTrimMemory()的回調(diào)是在API 14才被加進(jìn)來的,對于老的版本,你可以使用onLowMemory)回調(diào)來進(jìn)行兼容。onLowMemory相當(dāng)與TRIM_MEMORY_COMPLETE。

請注意:當(dāng)系統(tǒng)開始清除LRU緩存中的進(jìn)程時,雖然它首先按照LRU的順序來執(zhí)行操作,但是它同樣會考慮進(jìn)程的內(nèi)存使用量以及其他因素。占用越少的進(jìn)程越容易被留下來。

4)資源文件需要選擇合適的文件夾進(jìn)行存放

我們知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夾下的圖片在不同的設(shè)備上會經(jīng)過scale的處理。例如我們只在hdpi的目錄下放置了一張100100的圖片,那么根據(jù)換算關(guān)系,xxhdpi的手機(jī)去引用那張圖片就會被拉伸到200200。需要注意到在這種情況下,內(nèi)存占用是會顯著提高的。對于不希望被拉伸的圖片,需要放到assets或者nodpi的目錄下。

5)Try catch某些大內(nèi)存分配的操作

在某些情況下,我們需要事先評估那些可能發(fā)生OOM的代碼,對于這些可能發(fā)生OOM的代碼,加入catch機(jī)制,可以考慮在catch里面嘗試一次降級的內(nèi)存分配操作。例如decode bitmap的時候,catch到OOM,可以嘗試把采樣比例再增加一倍之后,再次嘗試decode。

6)謹(jǐn)慎使用static對象

因為static的生命周期過長,和應(yīng)用的進(jìn)程保持一致,使用不當(dāng)很可能導(dǎo)致對象泄漏,在Android中應(yīng)該謹(jǐn)慎使用static對象。

7)特別留意單例對象中不合理的持有

雖然單例模式簡單實用,提供了很多便利性,但是因為單例的生命周期和應(yīng)用保持一致,使用不合理很容易出現(xiàn)持有對象的泄漏。

8)珍惜Services資源

如果你的應(yīng)用需要在后臺使用service,除非它被觸發(fā)并執(zhí)行一個任務(wù),否則其他時候Service都應(yīng)該是停止?fàn)顟B(tài)。另外需要注意當(dāng)這個service完成任務(wù)之后因為停止service失敗而引起的內(nèi)存泄漏。當(dāng)你啟動一個Service,系統(tǒng)會傾向為了保留這個Service而一直保留Service所在的進(jìn)程。這使得進(jìn)程的運(yùn)行代價很高,因為系統(tǒng)沒有辦法把Service所占用的RAM空間騰出來讓給其他組件,另外Service還不能被Paged out。這減少了系統(tǒng)能夠存放到LRU緩存當(dāng)中的進(jìn)程數(shù)量,它會影響應(yīng)用之間的切換效率,甚至?xí)?dǎo)致系統(tǒng)內(nèi)存使用不穩(wěn)定,從而無法繼續(xù)保持住所有目前正在運(yùn)行的service。建議使用IntentService,它會在處理完交代給它的任務(wù)之后盡快結(jié)束自己。更多信息,請閱讀 Running in a Background Service。

9)優(yōu)化布局層次,減少內(nèi)存消耗

越扁平化的視圖布局,占用的內(nèi)存就越少,效率越高。我們需要盡量保證布局足夠扁平化,當(dāng)使用系統(tǒng)提供的View無法實現(xiàn)足夠扁平的時候考慮使用自定義View來達(dá)到目的。

10)謹(jǐn)慎使用“抽象”編程

很多時候,開發(fā)者會使用抽象類作為”好的編程實踐”,因為抽象能夠提升代碼的靈活性與可維護(hù)性。然而,抽象會導(dǎo)致一個顯著的額外內(nèi)存開銷:他們需要同等量的代碼用于可執(zhí)行,那些代碼會被mapping到內(nèi)存中,因此如果你的抽象沒有顯著的提升效率,應(yīng)該盡量避免他們。

11)使用nano protobufs序列化數(shù)據(jù)

Protocol buffers是由Google為序列化結(jié)構(gòu)數(shù)據(jù)而設(shè)計的,一種語言無關(guān),平臺無關(guān),具有良好的擴(kuò)展性。類似XML,卻比XML更加輕量,快速,簡單。如果你需要為你的數(shù)據(jù)實現(xiàn)序列化與協(xié)議化,建議使用nano protobufs。關(guān)于更多細(xì)節(jié),請參考 protobuf readme的”Nano version”章節(jié)。

12)謹(jǐn)慎使用依賴注入框架

那些注入框架會通過掃描你的代碼執(zhí)行許多初始化的操作,這會導(dǎo)致你的代碼需要大量的內(nèi)存空間來mapping代碼,而且mapped pages會長時間的被保留在內(nèi)存中。除非真的很有必要,建議謹(jǐn)慎使用這種技術(shù);

13)謹(jǐn)慎使用多進(jìn)程

  • 使用多進(jìn)程可以把應(yīng)用中的部分組件運(yùn)行在單獨(dú)的進(jìn)程當(dāng)中,這樣可以擴(kuò)大應(yīng)用的內(nèi)存占用范圍,但是這個技術(shù)必須謹(jǐn)慎使用,絕大多數(shù)應(yīng)用都不應(yīng)該貿(mào)然使用多進(jìn)程,一方面是因為使用多進(jìn)程會使得代碼邏輯更加復(fù)雜,另外如果使用不當(dāng),它可能反而會導(dǎo)致顯著增加內(nèi)存。當(dāng)你的應(yīng)用需要運(yùn)行一個常駐后臺的任務(wù),而且這個任務(wù)并不輕量,可以考慮使用這個技術(shù);
  • 一個典型的例子是創(chuàng)建一個可以長時間后臺播放的Music Player。如果整個應(yīng)用都運(yùn)行在一個進(jìn)程中,當(dāng)后臺播放的時候,前臺的那些UI資源也沒有辦法得到釋放。類似這樣的應(yīng)用可以切分成2個進(jìn)程:一個用來操作UI,另外一個給后臺的Service。

14)使用ProGuard來剔除不需要的代碼

ProGuard能夠通過移除不需要的代碼,重命名類,域與方法等等對代碼進(jìn)行壓縮,優(yōu)化與混淆。使用ProGuard可以使得你的代碼更加緊湊,這樣能夠減少mapping代碼所需要的內(nèi)存空間。

15)謹(jǐn)慎使用第三方libraries

很多開源的library代碼都不是為移動網(wǎng)絡(luò)環(huán)境而編寫的,如果運(yùn)用在移動設(shè)備上,并不一定適合。即使是針對Android而設(shè)計的library,也需要特別謹(jǐn)慎,特別是在你不知道引入的library具體做了什么事情的時候。例如,其中一個library使用的是nano protobufs, 而另外一個使用的是micro protobufs。這樣一來,在你的應(yīng)用里面就有2種protobuf的實現(xiàn)方式。這樣類似的沖突還可能發(fā)生在輸出日志,加載圖片,緩存等等模塊里面。另外不要為了1個或者2個功能而導(dǎo)入整個library,如果沒有一個合適的庫與你的需求相吻合,你應(yīng)該考慮自己去實現(xiàn),而不是導(dǎo)入一個大而全的解決方案。

總結(jié)

內(nèi)存優(yōu)化并不就是說程序占用的內(nèi)存越少就越好,如果因為想要保持更低的內(nèi)存占用,而頻繁觸發(fā)執(zhí)行g(shù)c操作,在某種程度上反而會導(dǎo)致應(yīng)用性能整體有所下降,這里需要綜合考慮做一定的權(quán)衡。

Android的內(nèi)存優(yōu)化涉及的知識面還有很多:內(nèi)存管理的細(xì)節(jié),垃圾回收的工作原理,如何查找內(nèi)存泄漏等等都可以展開講很多。OOM是內(nèi)存優(yōu)化當(dāng)中比較突出的一點(diǎn),盡量減少OOM的概率對內(nèi)存優(yōu)化有著很大的意義

 

責(zé)任編輯:姜華 來源: Android開發(fā)編程
相關(guān)推薦

2022-08-05 11:55:13

FlutteriOS

2015-05-18 15:01:31

全光智慧社區(qū)2015網(wǎng)絡(luò)大會華為

2023-06-08 12:43:21

性能優(yōu)化開發(fā)

2013-07-15 17:11:53

三星蘋果

2022-04-28 15:07:41

抖音內(nèi)存泄漏Android

2015-09-16 15:21:23

Android性能優(yōu)化內(nèi)存

2016-12-22 17:21:11

Android性能優(yōu)化內(nèi)存泄漏

2022-07-25 09:40:41

內(nèi)存00M

2013-07-03 14:27:05

2012-07-27 09:12:13

甲骨文云計算

2012-10-08 14:12:57

2017-03-14 18:48:06

Android性能優(yōu)化內(nèi)存優(yōu)化

2015-01-14 13:50:58

AndroidHandler內(nèi)存泄露

2021-08-02 13:08:56

高并發(fā)服務(wù)

2010-03-02 09:53:14

MySQL性能優(yōu)化

2024-03-07 11:03:21

ElasticseaES索引

2010-12-01 12:30:15

TechED 2010課程

2018-07-23 09:26:08

iOS內(nèi)存優(yōu)化

2013-08-07 10:07:07

Handler內(nèi)存泄露

2021-07-27 20:51:02

AndroidDNS網(wǎng)絡(luò)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號