Android官方开发文档Training系列课程中文版:高效显示位图之加载大位图
原文地址:http://android.xsoftlab.net/training/displaying-bitmaps/index.html
引言
學(xué)習(xí)如何使用一種常規(guī)的手段來處理及加載Bitmap對(duì)象,這種方式除了使用戶界面是可響應(yīng)的之外,還會(huì)避免超出內(nèi)存的限制。如果你不小心點(diǎn)的話,位圖會(huì)迅速的將那些可憐的內(nèi)存消耗殆盡,并會(huì)導(dǎo)致程序崩潰,因?yàn)檫@會(huì)產(chǎn)生一種可怕的異常:
java.lang.OutofMemoryError: bitmap size exceeds VM budget.這里列舉出了一些原因來說明為什么加載位圖對(duì)于Android程序來說是非常棘手的:
- 移動(dòng)設(shè)備通常含有有限的資源。Android設(shè)備對(duì)于單個(gè)程序只有少量的16MB可用內(nèi)存。虛擬機(jī)兼容性(Virtual Machine Compatibility)針對(duì)于各種的屏幕尺寸和密度給出了最低限度的程序內(nèi)存要求。程序應(yīng)該在極小的內(nèi)存空間下充分利用內(nèi)存空間。無論如何要記住一點(diǎn),很多設(shè)備配備了更高的限制。
- 位圖通常會(huì)消耗掉不少內(nèi)存,尤其是豐富的圖片,就像照片這樣的。舉個(gè)例子,Galaxy Nexus上的相機(jī)拍的照片會(huì)達(dá)到2592x1936個(gè)像素(五百萬像素)。如果位圖配置使用的是ARGB_8888(這在Android 2.3以前是默認(rèn)的),那么加載這張照片到內(nèi)存中就需要花費(fèi)掉19MB的內(nèi)存(2592*1936*4個(gè)字節(jié)),這會(huì)立即耗盡某些設(shè)備上的所有內(nèi)存。
- Android的APP界面有時(shí)會(huì)很頻繁的請(qǐng)求一些圖片來加載。有些組件比如ListView, GridView及ViewPager,它們有個(gè)共同的特性就是需要同時(shí)在屏幕上加載多個(gè)位圖并會(huì)在屏幕之外的地方加載以便在手指滑動(dòng)的時(shí)候顯示出來。
有效加載大圖
圖片會(huì)有各種形狀和大小。在很多情況下它們會(huì)比用戶界面上所要求的尺寸要大。舉個(gè)例子,系統(tǒng)的相冊(cè)應(yīng)用所展示的用相機(jī)拍攝的照片的分辨率通常要比屏幕的密度要高。
鑒于在有限的內(nèi)存中工作,理想上只用加載低分辨率的版本就可以。低分辨率的版本應(yīng)該匹配到展示這張圖片的控件大小。圖片的更高分辨率不會(huì)在視覺上有更佳的效果,但是這仍然會(huì)消耗寶貴的內(nèi)存空間,由于額外的動(dòng)態(tài)擴(kuò)展,這會(huì)招致額外的性能開銷。
這節(jié)課會(huì)討論將大位圖進(jìn)行二次采樣并將采樣后的小版本加載到內(nèi)存中的過程。這個(gè)過程并不會(huì)超出應(yīng)用的內(nèi)存限制。
讀取位圖的尺寸及類型
類BitmapFactory提供了若干個(gè)解碼方法(decodeByteArray(), decodeFile(), decodeResource(), etc.)根據(jù)不同的資源來創(chuàng)建位圖Bitmap。選擇更加適合的解碼方法取決于圖片的數(shù)據(jù)資源。這些方法會(huì)在構(gòu)造位圖時(shí)嘗試向內(nèi)存申請(qǐng)空間,所以會(huì)輕易的造成OutOfMemory異常。每個(gè)解碼方法都有一個(gè)附屬特征,這個(gè)特征可以使你通過BitmapFactory.Options類來指定解碼選項(xiàng)。設(shè)置inJustDecodeBounds屬性為true可以避免在解碼時(shí)向內(nèi)存申請(qǐng)空間,這會(huì)返回一個(gè)空的位圖,但是outWidth、outHeight和outMimeType這些設(shè)置除外。這項(xiàng)技術(shù)可以使你在構(gòu)造位圖(申請(qǐng)內(nèi)存)之前提前讀取圖像數(shù)據(jù)的尺寸及類型。
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType;為了避免java.lang.OutOfMemory異常,需要在解碼圖片之前檢查圖片的尺寸,除非你對(duì)這些圖像數(shù)據(jù)的尺寸絕對(duì)的信任,并且該尺寸對(duì)可用內(nèi)存非常適用。
加載等比縮小的版本到內(nèi)存
那么現(xiàn)在圖片的尺寸是知道了,這尺寸可以被用來決定:是否全尺寸的圖像應(yīng)該被加載到內(nèi)存中還是應(yīng)該有個(gè)二次采樣的版本加載到內(nèi)存中。這里有一些因素需要考慮:
- 往內(nèi)存中加載全尺寸的圖像應(yīng)該估算要使用的內(nèi)存大小。
- 要加載的圖片所需要的內(nèi)存數(shù)量需要給應(yīng)用預(yù)留一定的內(nèi)存空間,不要消耗完全。
- ImageView或者UI組件的尺寸是圖像將要加載的尺寸。
- 當(dāng)前設(shè)備的屏幕尺寸與密度。
舉個(gè)例子,加載一個(gè)1024*768像素的圖片到內(nèi)存中是沒有價(jià)值的,如果這個(gè)圖片最終被顯示為一個(gè)128x96像素的縮略圖的話。
為了告訴解碼器需要進(jìn)行二次采樣,以便加載一個(gè)小版本的圖像到內(nèi)存中,需要設(shè)置BitmapFactory.Options對(duì)象的inSampleSize屬性為true。舉個(gè)例子,一張圖片的分辨率為2048x1536,需要通過inSampleSize解碼為4分之一大小的位圖,大概是512x384。加載這樣的圖像只需要花費(fèi)0.75MB內(nèi)存,而全尺寸的圖像則需要花費(fèi)12MB的內(nèi)存(假設(shè)位圖的配置為ARGB_8888)。這里有一個(gè)方法可以來計(jì)算一個(gè)樣本容量值,這個(gè)值是2的冪次方值并基于原圖像的高度值與寬度值進(jìn)行計(jì)算。
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {// Raw height and width of imagefinal int height = options.outHeight;final int width = options.outWidth;int inSampleSize = 1;if (height > reqHeight || width > reqWidth) {final int halfHeight = height / 2;final int halfWidth = width / 2;// Calculate the largest inSampleSize value that is a power of 2 and keeps both// height and width larger than the requested height and width.while ((halfHeight / inSampleSize) > reqHeight&& (halfWidth / inSampleSize) > reqWidth) {inSampleSize *= 2;}}return inSampleSize; }Note: 最終計(jì)算后的值是一個(gè)2的冪次方值是因?yàn)榻獯a器需要通過舍入來獲得一個(gè)最終值,這個(gè)值與2的冪次方最為接近,依據(jù)inSampleSize文檔。
為了使用這個(gè)方法,第一步需要將inJustDecodeBounds設(shè)置為true,然后將options交給BitmapFactory使用,然后再次使用一個(gè)新的inSampleSize和inJustDecodeBounds設(shè)置為false來再次使用:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {// First decode with inJustDecodeBounds=true to check dimensionsfinal BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeResource(res, resId, options);// Calculate inSampleSizeoptions.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);// Decode bitmap with inSampleSize setoptions.inJustDecodeBounds = false;return BitmapFactory.decodeResource(res, resId, options); }這個(gè)方法可以很輕易的加載任何大尺寸的位圖給ImageView,這個(gè)ImageView展示了一個(gè)100*100像素的縮略圖,就像下面的代碼所展示的這樣:
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));你可以遵循類似的過程來對(duì)其它資源進(jìn)行解碼,如果需要的話,可以替代使用合適的BitmapFactory.decode*方法。
總結(jié)
以上是生活随笔為你收集整理的Android官方开发文档Training系列课程中文版:高效显示位图之加载大位图的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WSDL基础知识
- 下一篇: Android官方开发文档Trainin