Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發解決加載圖片OOM問題(兼顧4.0以下系統)

Android開發解決加載圖片OOM問題(兼顧4.0以下系統)

編輯:關於Android編程

我們項目中經常會加載圖片,有時候如果加載圖片過多的話,小則導致程序很卡,重則OOM導致App掛了,今天翻譯https://developer.Android.com/training/displaying-bitmaps/index.html,學習Google高效加載大圖片的方法。

圖片有各種形狀和大小,但在大多數情況下,這些圖片都會大於我們程序所需要的大小。比如說系統圖片庫裡展示的圖片大都是用手機攝像頭拍出來的,這些圖片的分辨率會比我們手機屏幕的分辨率高得多。大家應該知道,我們編寫的應用程序都是有一定內存限制的,程序占用了過高的內存就容易出現OOM(OutOfMemory)異常。我們可以通過下面的代碼看出每個應用程序最高可用內存是多少。

 

[java]view plaincopy     intmaxMemory=(int)(Runtime.getRuntime().maxMemory()/1024/1024);

現在大部分手機都是32M,既然知道了每個app分配的內存,所以就要計算好加載多少圖片而不導致出現OOM,所以要計算每張圖片所占用的內存是多少。

Android中計算一張圖片所占內存大小方法:圖片長*寬*所占像素字節數,而像素字節數Android中也就四種,

1:ALPHA_8 占1個字節

2:ARGB_4444 占2個字節

3:ARGB_8888 占4個字節

4:RGB_565 占2個字節

 

:ARGB指的是一種色彩模式,裡面A代表Alpha,R表示red,G表示green,B表示blue,其實所有的可見色都是紅綠藍組成的,所以紅綠藍又稱為三原色。 A  R  G  B
透明度 紅色 綠色 藍色

而這些字節數是可以通過bitmap對象去設置的,bitmap.setConfig(Bitmap.Config.ARGB_4444);如果這是個定值,那麼要改變一個張圖片的大小,就只能改寬或者高了,如果只改高,寬不變的話,就會造成圖片變形,因此一般都是一起改動,所以圖片要縮放,而縮放時根據屏幕的寬和高來縮放的,因為Android設備很多,每個屏幕的寬和高也不一樣,這樣就能達到加載大圖片避免OOM。

[java]view plaincopy
  1. BitmapFactory這個類提供了多個解析方法(decodeByteArray,decodeFile,decodeResource等)用於創建Bitmap對象,我們應該根據圖片的來源選擇合適的方法。比如SD卡中的圖片可以使用decodeFile方法,網絡上的圖片可以使用decodeStream方法,資源文件中的圖片可以使用decodeResource方法。這些方法會嘗試為已經構建的bitmap分配內存,這時就會很容易導致OOM出現。為此每一種解析方法都提供了一個可選的BitmapFactory.Options參數,將這個參數的inJustDecodeBounds屬性設置為true就可以讓解析方法禁止為bitmap分配內存,返回值也不再是一個Bitmap對象,而是null。雖然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType屬性都會被賦值。這個技巧讓我們可以在加載圖片之前就獲取到圖片的長寬值和MIME類型,從而根據情況對圖片進行壓縮。如下代碼所示:
[java]view plaincopy
  1. BitmapFactory.Optionsoptions=newBitmapFactory.Options();
  2. options.inJustDecodeBounds=true;
  3. BitmapFactory.decodeResource(getResources(),R.id.myimage,options);
  4. intimageHeight=options.outHeight;
  5. intimageWidth=options.outWidth;StringimageType=options.outMimeType;
比如一張480*800的圖片直接加載到內存中,如果圖片很多那就很容易造成OOM,那就必須壓縮,比如按1/8進行壓縮,壓縮後得到的60*100,如果從服務器獲取的圖片規格不一樣,那壓縮後現在在ImageView上肯定不一樣,因為ImageView寬和高肯定是設置成wrap_content。

Android系統的手機在系統底層指定了堆內存的上限值,大部分手機的缺省值是16MB,不過也有些高配置的機型是24MB的,所以我們的程序在申請內存空間時,為了確保能夠成功申請到內存空間,應該保證當前已分配的內存加上當前需要分配的內存值的總大小不能超過當前堆的最大內存值。由於內存管理上將外部內存完全當成了當前堆的一部分,也就是說Bitmap對象通過棧上的引用來指向堆上的Bitmap對象,而堆上的Bitmap對象又對應了一個使用了外部存儲的native圖像,也就是實際上使用的字節數組byte[]來存儲的位圖信息,因此解碼之後的Bitmap的總大小就不能超過8M了。

解決這類問題的最根本的,最有效的辦法就是,使用完bitmap之後,調用bitmap對象的recycle()方法釋放所占用的內存,以便於下一次使用。

下面是網上找到的一些常用的優化辦法,但是基本上都不能從本質上解決問題。

1.設置系統的最小堆大小:

 

[java]view plaincopy
  1. intnewSize=4*1024*1024;//設置最小堆內存大小為4MB
  2. VMRuntime.getRuntime().setMinimumHeapSize(newSize);
  3. VMRuntime.getRuntime().setTargetHeapUtilization(0.75);//設置堆內存的利用率為75%
補充說明:堆(HEAP)是VM中占用內存最多的部分,通常是動態分配的。堆的大小不是一成不變的,當堆內存實際的利用率偏離設定的值的時候,虛擬機會在GC的時候調整堆內存大小,讓實際占用率向個百分比靠攏。比如初始的HEAP是4M大小,當4M的空間被占用超過75%的時候,重新分配堆為8M大;當8M被占用超過75%,分配堆為16M大。倒過來,當16M的堆利用不足30%的時候,縮減它的大小為8M大。重新設置堆的大小,尤其是壓縮,一般會涉及到內存的拷貝,所以變更堆的大小對效率有不良影響。

2.對圖片的大小進行壓縮控制


[java]view plaincopy
  1. BitmapFactory.Optionsoptions=newBitmapFactory.Options();
  2. options.inSampleSize=2;//圖片寬高都為原來的二分之一,即圖片為原來的四分之一
  3. Bitmapbitmap=BitmapFactory.decodeFile("/mnt/sdcard/a.jpg",options);

補充說明:這種方法只是對圖片做了一個縮放處理,降低了圖片的分辨率,在需要保證圖片質量的應用中不可取。

3.設置臨時存儲空間


[java]view plaincopy
  1. BitmapFactory.Optionsoptions=newBitmapFactory.Options();
  2. options.inTempStorage=newbyte[1024*1024*5];//5MB的臨時存儲空間
  3. Bitmapbm=BitmapFactory.decodeFile("/mnt/sdcard/a.jpg",options);

補充說明:從創建Bitmap的C++底層代碼BitmapFactory.cpp中的處理邏輯來看,如果option不為null的話,那麼會優先處理option中設置的各個參數,假設當前你設置option的inTempStorage為1024*1024*4(4M)大小的話,而且每次解碼圖像時均使用該option對象作為參數,那麼你的程序極有可能會提前失敗,經過測試,如果使用一張大小為1.03M的圖片來進行解碼,如果不使用option參數來解碼,可以正常解碼四次,也就是分配了四次內存,而如果使用option的話,就會出現內存溢出錯誤,只能正常解碼兩次。Options類有一個預處理參數,當你傳入options時,並且指定臨時使用內存大小的話,Android將默認先申請你所指定的內存大小,如果申請失敗,就會先拋出內存溢出錯誤。而如果不指定內存大小,系統將會自動計算,如果當前還剩3M空間大小,而解碼只需要2M大小,那麼在缺省情況下將能解碼成功,而在設置inTempStorage大小為4M的情況下就將出現內存溢出錯誤。所以,通過設置Options的inTempStorage大小也不能從根本上解決大圖像解碼的內存溢出問題。

總之再做android開發時,出現內存溢出是屬於系統底層限制,只要解碼需要的內存超過系統可分配的最大內存值,那麼內存溢出錯誤必然會出現。

 

對於Android4.0以下的手機我們還要這麼處理:

Android平台在圖片處理方面經常會出現OOM的問題,一直被這個問題所困擾,在這方面也搜集了許多的資料,今天僅僅針對Android平台的Bitmap說事兒,今後再對內存的問題做詳細的探討,android平台對圖片解碼這塊確實設置的有內存上限,在解碼Bitmap的時候android平台會對其需要占用的內存進行Check,一旦需要的內存超越上限,則直接報錯,下面援引鄧凡平老師的解釋:

createBitmap好像有一個參數,可以繞過虛擬機的堆棧檢查。內存報錯其實是先檢查是否超過限制,比如最大16M,你要分配32M,檢查的時候超標,則會報錯。除此之外,沒有別的辦法可以解決該問題。我們當時測試了30M的圖片分配,如果不加該參數,則必然報錯。加了就沒事了。該參數是hidden的。你必須用源碼編譯才可以。而只有那個函數可以用。其余都用不了。該參數是BitmapFactory.options類的public boolean inNativeAlloc。不過4.0已經去掉該參數了。所以這個辦法可能也不行。

你最好分析下你的圖片尺寸大小,實在不行的話,把buffer傳遞給native層,然後在native層做修改。記住我剛才說的,內存檢查是Bitmap自己去check的。native層其實malloc/new多大內存都無所謂,只要你不去check。   這個參數就是:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inNativeAlloc = true; 接下來我查證了SDK的文檔,其中BitmapFactory.Options中並沒有inNativeAlloc這個參數,為了查證這個參數,我繼續查看了Android系統源代碼的BitmapFactory部分,在其中找到了該參數,以下是對該參數的描述:

/***
* Normally bitmap allocations count against the dalvik heap, which
* means they help trigger GCs when a lot have been allocated. However,
* in rare cases, the caller may want to allocate the bitmap outside of
* that heap. To request that, set inNativeAlloc to true. In these
* rare instances, it is solely up to the caller to ensure that OOM is
* managed explicitly by calling bitmap.recycle() as soon as such a
* bitmap is no longer needed.
*
* @hide pending API council approval
*/


我在Android Developers 論壇上找到了一段話:
> On Wed, Jun 8, 2011 at 10:17 AM, Erik R wrote:
> > I'm working on a simple image manipulation app that requires opening
> > bitmaps at full resolution, which of course results in OutOfMemory
> > issues. I know that the short answer is to simply use less memory via
> > BitmapFactory's inSampleSize Option to downsample the bitmap, but for
> > this app I really would like to retain the full resolution for
> > editing. One solution I have been investigating is loading the bitmap
> > entirely on the native heap, after learning that the memory limitation
> > is only imposed within the Dalvik VM heap. A look into the Android
> > source code revealed that this is already the case... BitmapFactory
> > uses native methods to load a bitmap on the native heap, while
> > maintaining a reference to it on the VM heap. The issue then is that
> > it appears the native memory used by the bitmap is actually counted
> > against the available VM memory, when it really isn't there. A look
> > into the stock Camera and Gallery apps' source revealed that they get
> > around this by using an additional BitmapFactory Option,
> > inNativeAlloc. I was able to find this field in the Java code for
> > BitmapFactory:
  但是這個參數是hidden的,在使用中需要帶源碼編譯才可以。   總之,使用這個參數確實能避開內存檢查,並且系統自帶的圖片浏覽器中也使用了這個參數,但是注意,這個參數僅僅在4.0以下平台適用。   解決方式:   \  
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved