原文鏈接http://blog.csdn.net/guolin_blog/article/details/9316683
一.高效加載大圖
1.查看程序可用內(nèi)存大小
int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
Log.d("TAG","Max memory is "+maxmoory+"KB");
因此在展示高分辨率圖片的時(shí)候,最好先將圖片進(jìn)行壓縮。壓縮后的圖片大小應(yīng)該和用來(lái)展示它的控件大小相近,在一個(gè)很小的ImageView上顯示一張超大的圖片不會(huì)帶來(lái)任何視覺(jué)上的好處,但卻會(huì)占用我們相當(dāng)多寶貴的內(nèi)存,而且在性能上還可能會(huì)帶來(lái)負(fù)面影響。
每一種解析方法都提供了一個(gè)可選的BitmapFactory.Options參數(shù),將這個(gè)參數(shù)的inJustDecodeBounds屬性設(shè)置為true就可以讓解析方法禁止為bitmap分配內(nèi)存,返回值也不再是一個(gè)Bitmap對(duì)象,而是null。雖然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType屬性都會(huì)被賦值。這個(gè)技巧讓我們可以在加載圖片之前就獲取到圖片的長(zhǎng)寬值和MIME類型,從而根據(jù)情況對(duì)圖片進(jìn)行壓縮。如下代碼所示:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//禁止為Bitmap分配內(nèi)存
BitmapFactory.decodeResource(getResources(),R.id.myimage,options);
int imageHeihht = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
加載圖片前考慮是完整顯示圖片還是要壓縮后再顯示,就需要考慮以下因素:
- 預(yù)估整張圖片占用的內(nèi)存
- 為了加在一張圖片,你愿意提供多少內(nèi)存
- 用于展示的圖片控件的實(shí)際大小
- 當(dāng)前設(shè)備的屏幕尺寸和分辨率
對(duì)圖片壓縮需要使用BitmapFactory.Options中的inSampleSize(例如:2048 * 1536像素的圖片,inSampleSize= 4,圖片被壓縮成 512 * 384 所占的內(nèi)存大小為512 * 384 *4 = 0.75M (假設(shè)圖片是ARGB_8888類型,即每個(gè)像素點(diǎn)占用4個(gè)字節(jié))
下面的方法可以根據(jù)寬高計(jì)算出合適的inSampleSize值:
public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){
//源圖片的高度和寬度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSaze= 1;
if(height>reqHeight||width>reqWidth){
//計(jì)算實(shí)際寬高和目標(biāo)寬高的比率
final int heightRatio = Math.round((float)height/(float)reqHeight);
final int widthRatio = Math.round((float)height/(float)reqHeight);
//選擇寬高比例較小的作為InSampleSize的值,這樣保證最終圖片的寬高是大于目標(biāo)的寬高
inSampleSize = heightRatio>widthRatio?widthRatio:heightRatio;
}
return inSampleSize;
}
獲取到inSampleSize值以后再把inJustDecodeBounds設(shè)置為false,就可以使用壓縮后的圖片了
public static Bitmap decodeSampleBitmapFromResource(Resources res,int resId,int reqWidth,int reqHeight){
//第一次設(shè)置inJustDecodeBounds為true,來(lái)獲取圖片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res,resId,options);
//調(diào)用方法計(jì)算inSampleSize
options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
//使用insamplesize在此解析圖片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res,resId,options);
}
下面的代碼非常簡(jiǎn)單的將任意一張圖片設(shè)置壓縮成100*100的縮略圖,并顯示在ImageView上
mImageView.setImageBitmap(decodeSampleBitmapFromResource(getResource(),R.id.myimage,100,100));
二.使用圖片緩存技術(shù)
防止頻繁的顯示多張圖片 以及回收過(guò)的圖片再次顯示導(dǎo)致大量的加載而引起OOM
內(nèi)存緩存技術(shù)對(duì)那些大量占用應(yīng)用程序?qū)氋F內(nèi)存的圖片提供了快速訪問(wèn)的方法。其中最核心的類是LruCache (此類在android-support-v4的包中提供) 。這個(gè)類非常適合用來(lái)緩存圖片,它的主要算法原理是==把最近使用的對(duì)象用強(qiáng)引用存儲(chǔ)在 LinkedHashMap 中,并且把最近最少使用的對(duì)象在緩存值達(dá)到預(yù)設(shè)定值之前從內(nèi)存中移除。==
為了能夠選擇一個(gè)合適的緩存大小給LruCache, 有以下多個(gè)因素應(yīng)該放入考慮范圍內(nèi),例如:
- 你的設(shè)備可以為每個(gè)應(yīng)用程序分配多大的內(nèi)存?
- 設(shè)備屏幕上一次最多能顯示多少?gòu)垐D片?有多少圖片需要進(jìn)行預(yù)加載,因?yàn)橛锌赡芎芸煲矔?huì)顯示在屏幕上?
- 你的設(shè)備的屏幕大小和分辨率分別是多少?一個(gè)超高分辨率的設(shè)備(例如 Galaxy Nexus) 比起一個(gè)較低分辨率的設(shè)備(例如 Nexus S),在持有相同數(shù)量圖片的時(shí)候,需要更大的緩存空間。
- 圖片的尺寸和大小,還有每張圖片會(huì)占據(jù)多少內(nèi)存空間。
- 圖片被訪問(wèn)的頻率有多高?會(huì)不會(huì)有一些圖片的訪問(wèn)頻率比其它圖片要高?如果有的話,你也許應(yīng)該讓一些圖片常駐在內(nèi)存當(dāng)中,或者使用多個(gè)LruCache 對(duì)象來(lái)區(qū)分不同組的圖片。
- 你能維持好數(shù)量和質(zhì)量之間的平衡嗎?有些時(shí)候,存儲(chǔ)多個(gè)低像素的圖片,而在后臺(tái)去開(kāi)線程加載高像素的圖片會(huì)更加的有效
下面是一個(gè)使用 LruCache 來(lái)緩存圖片的例子:
private LruCache<String,Bitmap>mMemoryChahe;
@Override
protected void onCreate(Bundle savedInstanceState){
/ 獲取到可用內(nèi)存的最大值,使用內(nèi)存超出這個(gè)值會(huì)引起OutOfMemory異常。
// LruCache通過(guò)構(gòu)造函數(shù)傳入緩存值,以KB為單位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用內(nèi)存值的1/8作為緩存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protect int sizeOf(String key,Bitmap bitmap){
//重寫此方法來(lái)衡量每張圖片的大小,默認(rèn)返回圖片數(shù)量.
return bitmap.getByteCount()/1024;
}
};
}
public void addBitmapToMemoryCache(String key,Bitmap bitmap){
if(getBitmapFromMemCache(key)==null){
mMemoryCache.put(key,bitmap);
}
}
public Bitmap getBitmapFromMemoryCache(String key){
return mMemoryCache.get(key);
}
在這個(gè)例子當(dāng)中,使用了系統(tǒng)分配給應(yīng)用程序的八分之一內(nèi)存來(lái)作為緩存大小。在中高配置的手機(jī)當(dāng)中,這大概會(huì)有4兆(32/8)的緩存空間。一個(gè)全屏幕的 GridView 使用4張 800x480分辨率的圖片來(lái)填充,則大概會(huì)占用1.5兆的空間(800 * 480 * 4)。因此,這個(gè)緩存大小可以存儲(chǔ)2.5頁(yè)的圖片。
當(dāng)向 ImageView 中加載一張圖片時(shí),首先會(huì)在 LruCache 的緩存中進(jìn)行檢查。如果找到了相應(yīng)的鍵值,則會(huì)立刻更新ImageView ,否則開(kāi)啟一個(gè)后臺(tái)線程來(lái)加載這張圖片。
public void loadBitmap(int resId,ImageView imageview){
final String imageKay = String valueOf(resId);
Bitmap bitmap = mMemoryCache.getBitmapFromMemoryCache(imageKey);
if(bitmap!=null){
imageview.setImageBitmap(bitmap);
}else{
imageview.setImageResource(R.drawable.image_placeholder);
//緩存
BitmapWorkerTask task = new BitmapWorkTask(imageview);
task.execute(resId);
}
}
class BitmapWorkerTask extends AsyncTask<Integer,Void,Bitmap>{
//異步加載圖片
@Override
protected Bitmap doInBackground(Integer...params){
final Bitmap bitmap = edcodeSampleBitmapFromResource(getResource(),params[0],100,100);
addBitmapToMemoryCache(String.valueOf(params[0],bitmap));
return bitmap;
}
}