Android--加载大分辨率图片到内存
原文:http://blog.csdn.net/binyao02123202/article/details/17170791
前言
在使用ImageView顯示圖片的時(shí)候,直接加載一個(gè)圖片資源到內(nèi)存中,經(jīng)常會(huì)出現(xiàn)內(nèi)存溢出的錯(cuò)誤,這是因?yàn)橛行﹫D片的分辨率比較高,把它直接加載到內(nèi)存中之后,會(huì)導(dǎo)致堆內(nèi)存溢出的問(wèn)題。這篇博客就來(lái)講解一下Android的堆內(nèi)存以及如何在Android應(yīng)用中加載一個(gè)高分辨率的圖片。關(guān)于ImageView不熟悉的朋友,可以看看之前的博客:Android--ImageView。
本篇博客的主要內(nèi)容:
1,還原堆內(nèi)存溢出的錯(cuò)誤
2,分析堆內(nèi)存溢出
3,如何加載大分辨率圖片
4,示例Demo
還原堆內(nèi)存溢出的錯(cuò)誤
首先來(lái)還原一下堆內(nèi)存溢出的錯(cuò)誤。首先在SD卡上放一張照片,分辨率為(3776 X 2520),大小為3.88MB,是我自己用相機(jī)拍的一張照片。應(yīng)用的布局很簡(jiǎn)單,一個(gè)Button一個(gè)ImageView,然后按照常規(guī)的方式,使用BitmapFactory加載一張照片并使用一個(gè)ImageView展示。
代碼如下:
btn_loadimage.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Bitmap bitmap=BitmapFactory.decodeFile("/sdcard/a.jpg");iv_bigimage.setImageBitmap(bitmap);}}當(dāng)點(diǎn)擊按鈕后,程序會(huì)報(bào)錯(cuò),查看日志為:
先來(lái)分析一下這個(gè)錯(cuò)誤,首先dalvikvm(Android虛擬機(jī))發(fā)現(xiàn)需要的內(nèi)存38MB大于應(yīng)用的堆內(nèi)存24MB,這個(gè)時(shí)候嘗試使用軟加載的方式加載數(shù)據(jù),我們知道當(dāng)內(nèi)存不足的時(shí)候dalvikvm會(huì)自動(dòng)進(jìn)行GC(Garbage Collection),大概清理了55k的空間出來(lái),耗時(shí)203毫秒,但是內(nèi)存還是不夠,所以最后發(fā)生堆內(nèi)存溢出的錯(cuò)誤。
分析堆內(nèi)存溢出
Android系統(tǒng)主要用于低能耗的移動(dòng)設(shè)備,所以對(duì)內(nèi)存的管理有很多限制,一個(gè)應(yīng)用程序,Android系統(tǒng)缺省會(huì)為其分配最大16MB(某些機(jī)型是24MB)的空間作為堆內(nèi)存空間,我這里使用的模擬器調(diào)試的,這個(gè)模擬器被設(shè)定為24MB,可以在Android Virtual Device Manager中查看到。
而這里的圖片明明只有3.88MB,遠(yuǎn)遠(yuǎn)小于Android為應(yīng)用分配的堆內(nèi)存,而加載到內(nèi)存中,為什么需要消耗大約38MB的內(nèi)存呢?
我們都知道,圖片是由一個(gè)一個(gè)點(diǎn)分布組成的(分辨率),通常加載這類數(shù)據(jù)都會(huì)在內(nèi)存中創(chuàng)建一個(gè)二維數(shù)組,數(shù)組中的每一項(xiàng)代表一個(gè)點(diǎn),而這個(gè)圖片的分辨率是3776 * 2520,每一點(diǎn)又是由ARGB色組成,每個(gè)色素占4個(gè)Byte,所以這張圖片加載到內(nèi)存中需要消耗的內(nèi)存為:
3776 * 2520 * 4byte = 38062080byte
大約需要38MB的內(nèi)存才能正確加載這張圖片,這就是上面錯(cuò)誤描述需要38MB的內(nèi)存空間,大小略有出入,因?yàn)閳D片還有一些Exif信息需要存儲(chǔ),會(huì)比僅靠分辨率計(jì)算要大一些。
如何加載大分辨率圖片
有時(shí)候我們確實(shí)會(huì)需要加載一些大分辨率的圖片,但是對(duì)于移動(dòng)設(shè)備而言,哪怕加載能成功那么大的內(nèi)存也是一種浪費(fèi)(屏幕分辨率限制),所以就需要想辦法把圖片按照一定比率壓縮,使分辨率降低,以至于又不需要耗費(fèi)很大的堆內(nèi)存空間,又可以最大的利用設(shè)備屏幕的分辨率來(lái)顯示圖片。這里就用到一個(gè)BitmapFactory.Options對(duì)象,下面來(lái)介紹它。
BitmapFactory.Options為BitmapFactory的一個(gè)內(nèi)部類,它主要用于設(shè)定與存儲(chǔ)BitmapFactory加載圖片的一些信息。下面是Options中需要用到的屬性:
1,inJustDecodeBounds:如果設(shè)置為true,將不把圖片的像素?cái)?shù)組加載到內(nèi)存中,僅加載一些額外的數(shù)據(jù)到Options中。,
2,outHeight:圖片的高度。
3,outWidth:圖片的寬度。
4,inSampleSize:如果設(shè)置,圖片將依據(jù)此采樣率進(jìn)行加載,不能設(shè)置為小于1的數(shù)。例如設(shè)置為4,分辨率寬和高將為原來(lái)的1/4,這個(gè)時(shí)候整體所占內(nèi)存將是原來(lái)的1/16。
示例Demo
下面通過(guò)一個(gè)簡(jiǎn)單的Demo來(lái)演示上面提到的內(nèi)容,代碼中注釋比較清晰,這里就不再累述了。
package cn.bgxt.loadbigimg;import android.os.Bundle;import android.os.Environment;import android.app.Activity;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.BitmapFactory.Options;import android.view.Menu;import android.view.View;import android.view.WindowManager;import android.widget.Button;import android.widget.ImageView;public class MainActivity extends Activity {private Button btn_loadimage;private ImageView iv_bigimage;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn_loadimage = (Button) findViewById(R.id.btn_loadimage);iv_bigimage = (ImageView) findViewById(R.id.iv_bigimage);btn_loadimage.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// Bitmap bitmap=BitmapFactory.decodeFile("/sdcard/a.jpg");// iv_bigimage.setImageBitmap(bitmap);BitmapFactory.Options opts = new Options();// 不讀取像素?cái)?shù)組到內(nèi)存中,僅讀取圖片的信息如高寬opts.inJustDecodeBounds = true;BitmapFactory.decodeFile("/sdcard/a.jpg", opts);// 從Options中獲取圖片的分辨率int imageHeight = opts.outHeight;int imageWidth = opts.outWidth;// 獲取Android屏幕的服務(wù)WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);// 獲取屏幕的分辨率,getHeight()、getWidth已經(jīng)被廢棄掉了// 應(yīng)該使用getSize(),但是這里為了向下兼容所以依然使用它們int windowHeight = wm.getDefaultDisplay().getHeight();int windowWidth = wm.getDefaultDisplay().getWidth();// 計(jì)算采樣率int scaleX = imageWidth / windowWidth;int scaleY = imageHeight / windowHeight;int scale = 1;// 采樣率依照最大的方向?yàn)闇?zhǔn)if (scaleX > scaleY && scaleY >= 1) {scale = scaleX;}if (scaleX < scaleY && scaleX >= 1) {scale = scaleY;}// false表示讀取圖片像素?cái)?shù)組到內(nèi)存中,依照設(shè)定的采樣率opts.inJustDecodeBounds = false;// 采樣率opts.inSampleSize = scale;Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/a.jpg", opts);iv_bigimage.setImageBitmap(bitmap);}});} }
總結(jié)
本篇博客到這里就講解了如何加載一個(gè)大分辨率的圖片到內(nèi)存中并使用它。不過(guò)一般好一點(diǎn)的圖片處理軟件,都會(huì)有圖片放大功能,如果僅做此處理,單純的把處理后的圖片放大,會(huì)影響顯示效果,圖片還原度不高。一般會(huì)重新獲取放大區(qū)域的圖片的分辨率像素?cái)?shù)組,然后重新處理加載到內(nèi)存中進(jìn)行顯示
優(yōu)化Dalvik虛擬機(jī)的堆內(nèi)存分配
對(duì) 于Android平臺(tái)來(lái)說(shuō),其托管層使用的Dalvik Java VM從目前的表現(xiàn)來(lái)看還有很多地方可以優(yōu)化處理,比如我們?cè)陂_(kāi)發(fā)一些大型游戲或耗資源的應(yīng)用中可能考慮手動(dòng)干涉GC處理,使用 dalvik.system.VMRuntime類提供的setTargetHeapUtilization方法可以增強(qiáng)程序堆內(nèi)存的處理效率。當(dāng)然具體 原理我們可以參考開(kāi)源工程,這里我們僅說(shuō)下使用方法: private final static float TARGET_HEAP_UTILIZATION = 0.75f; 在程序onCreate時(shí)就可以調(diào)用 VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 即可。
Android堆內(nèi)存也可自己定義大小
對(duì)于一些Android項(xiàng)目,影響性能瓶頸的主要是Android自己內(nèi)存管理機(jī)制問(wèn)題,目前手機(jī)廠商對(duì)RAM都比較吝嗇,對(duì)于軟件的流暢性來(lái)說(shuō)RAM對(duì) 性能的影響十分敏感,除了 優(yōu)化Dalvik虛擬機(jī)的堆內(nèi)存分配外,我們還可以強(qiáng)制定義自己軟件的對(duì)內(nèi)存大小,我們使用Dalvik提供的 dalvik.system.VMRuntime類來(lái)設(shè)置最小堆內(nèi)存為例:
private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); //設(shè)置最小heap內(nèi)存為6MB大小。當(dāng)然對(duì)于內(nèi)存吃緊來(lái)說(shuō)還可以通過(guò)手動(dòng)干涉GC去處理
bitmap 設(shè)置圖片尺寸,避免 內(nèi)存溢出 OutOfMemoryError的優(yōu)化方法
★android 中用bitmap 時(shí)很容易內(nèi)存溢出,報(bào)如下錯(cuò)誤:Java.lang.OutOfMemoryError : bitmap size exceeds VM budget
● 主要是加上這段:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
● eg1:(通過(guò)Uri取圖片)
以上代碼可以優(yōu)化內(nèi)存溢出,但它只是改變圖片大小,并不能徹底解決內(nèi)存溢出。
● eg2:(通過(guò)路徑去圖片)
private ImageView preview; private String fileName= "/sdcard/DCIM/Camera/2010-05-14 16.01.44.jpg"; BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2;//圖片寬高都為原來(lái)的二分之一,即圖片為原來(lái)的四分之一 Bitmap b = BitmapFactory.decodeFile(fileName, options); preview.setImageBitmap(b); filePath.setText(fileName);
★Android 還有一些性能優(yōu)化的方法:
● 首先內(nèi)存方面,可以參考 Android堆內(nèi)存也可自己定義大小 和 優(yōu)化Dalvik虛擬機(jī)的堆內(nèi)存分配
● 基礎(chǔ)類型上,因?yàn)镴ava沒(méi)有實(shí)際的指針,在敏感運(yùn)算方面還是要借助NDK來(lái)完成。Android123提示游戲開(kāi)發(fā)者,這點(diǎn)比較有意思的是Google 推出NDK可能是幫助游戲開(kāi)發(fā)人員,比如OpenGL ES的支持有明顯的改觀,本地代碼操作圖形界面是很必要的。
● 圖形對(duì)象優(yōu)化,這里要說(shuō)的是Android上的Bitmap對(duì)象銷毀,可以借助recycle()方法顯示讓GC回收一個(gè)Bitmap對(duì)象,通常對(duì)一個(gè)不用的Bitmap可以使用下面的方式,如
if(bitmapObject.isRecycled()==false) //如果沒(méi)有回收
bitmapObject.recycle();
總結(jié)
以上是生活随笔為你收集整理的Android--加载大分辨率图片到内存的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android PullToRefres
- 下一篇: Android的内存优化的几种方案