Android Bitmap的加載優(yōu)化與Cache相關(guān)介紹
一 . 高效加載 Bitmap
BitMapFactory 提供了四類方法: decodeFile,decodeResource,decodeStream 和 decodeByteArray 分別用于從文件系統(tǒng),資源,輸入流以及字節(jié)數(shù)組中加載出一個(gè) Bitmap 對(duì)象。
高效加載 Bitmap 很簡(jiǎn)單,即采用 BitMapFactory.options 來(lái)加載所需要尺寸圖片。BitMapFactory.options 就可以按照一定的采樣率來(lái)加載縮小后的圖片,將縮小后的圖片置于 ImageView 中顯示。
通過(guò)采樣率即可高效的加載圖片,遵循如下方式獲取采樣率:
- 將
BitmapFactory.Options的 inJustDecodeBounds 參數(shù)設(shè)置為 true 并加載圖片 - 從
BitmapFactory.Options中取出圖片的原始寬高信息,即對(duì)應(yīng)于 outWidth 和 outHeight 參數(shù) - 根據(jù)采樣率的規(guī)則并結(jié)合目標(biāo) View 的所需大小計(jì)算出采樣率 inSampleSize
- 將
BitmapFactory.Options的 injustDecodeBounds 參數(shù)設(shè)置為 false,然后重新加載圖片
過(guò)上述四個(gè)步驟,加載出的圖片就是最終縮放后的圖片,當(dāng)然也有可能沒(méi)有縮放。
代碼實(shí)現(xiàn)如下:
public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) {
return 1;
}
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
Log.d(TAG, "origin, w= " + width + " h=" + height);
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;
}
}
Log.d(TAG, "sampleSize:" + inSampleSize);
return inSampleSize;
}
實(shí)際使用就可以像下面這樣了,如加載 100*100 的圖片大小,就可以像下面這樣高效的加載圖片了:
mImageView.setImageBitmap( decodeSampledBitmapFromResource(getResource(),R.id.myimage,100,100));
二 . Android 中的緩存策略
目前常用的算法是 LRU,即近期最少使用算法,當(dāng)緩存存滿時(shí),會(huì)優(yōu)先淘汰近期最少使用的緩存對(duì)象
2.1 LruCache
LruCache 是一個(gè)泛型類,其內(nèi)部實(shí)現(xiàn)機(jī)制是 LinkedHashMap 以強(qiáng)引用的方式存儲(chǔ)外部的緩存對(duì)象,提供了 get() 和 put() 來(lái)完成緩存對(duì)象的存取。當(dāng)緩存滿了,移除較早的緩存對(duì)象,再添加新的。LruCache 是線程安全的。
- 強(qiáng)引用:直接的對(duì)象引用
- 軟引用:當(dāng)一個(gè)對(duì)象只有軟引用時(shí),系統(tǒng)內(nèi)存不足時(shí),會(huì)被 gc 回收
- 弱引用:當(dāng)一個(gè)對(duì)象只有弱引用時(shí),隨時(shí)會(huì)被回收
2.2 DiskLriCache
DiskLruCache 用于實(shí)現(xiàn)存儲(chǔ)設(shè)備緩存,即磁盤緩存。
2.2.1 DiskLruCache 的創(chuàng)建
由于它不屬于 Android SDK的一部分,所以不能通過(guò)構(gòu)造方法來(lái)創(chuàng)建,提供了 open() 方法用于自身的創(chuàng)建
public static DiskLruCache open(File directory,int appversion,int valueCount,long maxSize);
典型的 DiskLruCache 的創(chuàng)建過(guò)程
private static final Disk_CACHE_SIZE = 1024*1024*50;//50M
File diskCaCheDir = getDiskCacheDir(mContext,"bitmap");
if(!diskCacheDir.exists()){
diskCacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(diskCaCheDir,1,1,Disk_CACHE_SIZE);
第三個(gè)參數(shù)表示單個(gè)節(jié)點(diǎn)所對(duì)應(yīng)的數(shù)據(jù),一般設(shè)置為1即可。
2.2.2 DiskLruCache 的緩存添加 緩存的添加操作是通過(guò) Editor 完成的, Editor 表示一個(gè)緩存對(duì)象的編輯對(duì)象。DiskLruCache 不允許同時(shí)編輯一個(gè)緩存對(duì)象。
2.2.3 DiskLruCache 的緩存查找
緩存查找過(guò)程也需要將 url 轉(zhuǎn)換為 key,通過(guò) DiskLruCache 的 get() 得到一個(gè) Snapshot 對(duì)象,然后通過(guò)該對(duì)象即可得到緩存的文件輸入流,得到文件輸入流即可得到 Bitmap 對(duì)象了。為了避免加載過(guò)程中 OOM,一般不會(huì)直接加載原始圖片。在前面介紹通過(guò) BitmapFactory.Options 來(lái)加載一張縮放后的圖片,但是那種方法對(duì) FileInputStream 的縮放存在問(wèn)題,原因是 FileInputStream 是一種有序的文件流,而兩次 decodeStream 調(diào)用影響了文件流的位置屬性,導(dǎo)致了第二次 decodeStream 時(shí)得到的是 null。為了解決這個(gè)問(wèn)題,可以通過(guò)文件流得到其對(duì)應(yīng)的文件描述符,然后通過(guò) BitmapFactory.decodeFileDescriptor 方法來(lái)加載一張縮放過(guò)后的圖片。
Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
// 獲取文件描述符
FileDescriptor fileDescriptor = fileInputStream.getFD();
// 通過(guò) BitmapFactory.decodeFileDescriptor 來(lái)加載一張縮放后的圖片
bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
reqWidth, reqHeight);
if (bitmap != null) {
addBitmapToMemoryCache(key, bitmap);
}
}
return bitmap;
}
三 . ImageLoader 的實(shí)現(xiàn)
具備的功能,即圖片的同步加載,異步加載,圖片的壓縮,內(nèi)存緩存,磁盤緩存以及網(wǎng)絡(luò)拉取。
3.1 圖片壓縮功能
如前面所述。
3.2 內(nèi)存緩存和磁盤緩存的實(shí)現(xiàn)
選擇 LruCache 和 DiskLruCache 來(lái)分別完成內(nèi)存緩存和磁盤緩存的工作
3.3 同步加載和異步加載的接口設(shè)計(jì)
關(guān)于同步加載:從 loadBitmap 的實(shí)現(xiàn)可以看出,其工作過(guò)程遵循如下幾個(gè)步驟:先試著從內(nèi)存緩存中讀取圖片,接著從磁盤緩存中讀取圖片,最后試著從網(wǎng)絡(luò)拉取圖片。另外該方法不能在主線程中調(diào)用,否則就會(huì)拋出異常。因?yàn)榧虞d圖片是一個(gè)耗時(shí)的操作。
關(guān)于異步加載:從 bindBitmap 中可以看出,binfBitmap 會(huì)先試著從內(nèi)存緩存中讀取結(jié)果,如果成功就直接返回,否則會(huì)從線程池中去調(diào)用 loadBitmap() ,當(dāng)加載成功后,再講圖片,圖片地址以及需要綁定的 ImageView 封裝成一個(gè) loaderResult 對(duì)象,通過(guò) mMainHandler 向主線程發(fā)送一個(gè)消息,這樣就可以在主線程中給 ImageView 設(shè)置圖片了。圖片的異步加載是一個(gè)很有用的功能,很多時(shí)候調(diào)用者不想在單獨(dú)的線程中以同步的方式來(lái)加載圖片,并將圖片設(shè)置給需要的 ImageVIew, 從而ImageLoader 內(nèi)部需要自己需要在內(nèi)部線程中加載圖片,并且將圖片設(shè)置給所需要的 ImageView。
ImageLoader源碼可以點(diǎn)擊這里:下載 查看ImageLoader的實(shí)現(xiàn)
四 . ImageLoader 的使用
核心是 ImageAdapter , 其中的 getView() 的核心方法如下:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.image_list_item,parent, false);
holder = new ViewHolder();
holder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
ImageView imageView = holder.imageView;
final String tag = (String)imageView.getTag();
final String uri = getItem(position);
if (!uri.equals(tag)) {
imageView.setImageDrawable(mDefaultBitmapDrawable);
}
if (mIsGridViewIdle && mCanGetBitmapFromNetWork) {
imageView.setTag(uri);
// 這句話將圖片的復(fù)雜加載過(guò)程交給 ImageLoader 了
mImageLoader.bindBitmap(uri, imageView, mImageWidth, mImageWidth);
}
return convertView;
}
對(duì)于上述代碼 ImageAdapter 來(lái)說(shuō), ImageLoader 的加載圖片的復(fù)雜過(guò)程,更不需要知道。
優(yōu)化列表卡頓現(xiàn)象:
- 不要在 getView() 中做加載圖片的操作,那樣肯定會(huì)耗時(shí),像這個(gè)例子中一樣,交給 ImageLoaer 來(lái)實(shí)現(xiàn)。
- 控制異步加載頻率, 如果用戶刻意的頻繁的上下滑動(dòng),可能在一瞬間加載幾百個(gè)異步任務(wù),這樣會(huì)給線程池造成擁堵。解決的辦法是考慮在用戶滑動(dòng)列表時(shí),停止加載圖片。等到列表停下來(lái)時(shí),在進(jìn)行異步加載任務(wù)。
- 開啟硬件加速:給Activity添加配置android:hardwareAccelerated=”true”
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)給我Android開發(fā)者們能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流。
相關(guān)文章
android開發(fā)中獲取手機(jī)分辨率大小的方法
不管是在我們的布局還是在實(shí)現(xiàn)代碼中進(jìn)行操控,我們的靈活性都不是局限于一個(gè)固定的數(shù)值,而是面對(duì)不同的手機(jī)對(duì)象都有一個(gè)適應(yīng)的數(shù)值。2013-04-04
android預(yù)置默認(rèn)的語(yǔ)音信箱號(hào)碼具體實(shí)現(xiàn)
在此介紹以xml的方式預(yù)置VM number的方法,以及如何允許用戶去修改并能夠記住用戶的選擇2013-06-06
Android開發(fā)高級(jí)組件之自動(dòng)完成文本框(AutoCompleteTextView)用法示例【附源碼下載】
這篇文章主要介紹了Android開發(fā)高級(jí)組件之自動(dòng)完成文本框(AutoCompleteTextView)用法,簡(jiǎn)單描述了自動(dòng)完成文本框的功能并結(jié)合實(shí)例形式分析了Android實(shí)現(xiàn)自動(dòng)完成文本框功能的具體步驟與相關(guān)操作技巧,并附帶源碼供讀者下載參考,需要的朋友可以參考下2018-01-01
Android中Service和Activity相互通信示例代碼
在android中Activity負(fù)責(zé)前臺(tái)界面展示,service負(fù)責(zé)后臺(tái)的需要長(zhǎng)期運(yùn)行的任務(wù)。下面這篇文章主要給大家介紹了關(guān)于Android中Service和Activity相互通信的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-09-09
Android10 App 啟動(dòng)分析進(jìn)程創(chuàng)建源碼解析
這篇文章主要為大家介紹了Android10 App啟動(dòng)分析進(jìn)程創(chuàng)建源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
android viewpager實(shí)現(xiàn)豎屏滑動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了android viewpager實(shí)現(xiàn)豎屏滑動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
android實(shí)現(xiàn)Splash閃屏效果示例
這篇文章主要介紹了android實(shí)現(xiàn)Splash閃屏效果的方法,涉及Android中postDelayed方法及AndroidManifest.xml權(quán)限控制的相關(guān)使用技巧,需要的朋友可以參考下2016-08-08
Flutter 日期時(shí)間DatePicker控件及國(guó)際化
這篇文章主要介紹了Flutter 日期時(shí)間DatePicker控件及國(guó)際化,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Android BroadcastReceiver廣播機(jī)制概述
這篇文章主要為大家詳細(xì)介紹了Android BroadcastReceiver廣播機(jī)制,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08

