12.1 Bitmap的高效加載
- Bitmap是如何加載的? BitmapFactory類提供了四類方法:
- decodeFile
- decodeResource
- decodeStream
- decodeByteArray
- 如何高效加載Bitmap?
- 采用BitmapFactory.Options按照一定的采樣率來(lái)加載所需尺寸的圖片,因?yàn)閕mageview所需的圖片大小往往小于圖片的原始尺寸。
- BitmapFactory.Options的inSampleSize參數(shù),即采樣率
- 獲取采樣率和高效加載的例子
注意:將inJustDecodeBounds設(shè)置為true的時(shí)候,BitmapFactory只會(huì)解析圖片的原始寬高信息,并不會(huì)真正的加載圖片
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;
}
12.2 Android中的緩存策略
- 緩存的主要是為了避免流量消耗。主要策略包含緩存的添加、獲取和刪除
- 最常用的緩存算法是LRU,核心是當(dāng)緩存滿時(shí),會(huì)優(yōu)先淘汰那些近期最少使用的緩存對(duì)象
- LRU算法的緩存:LruCache(內(nèi)存緩存)和DiskLruCache(磁盤(pán)緩存)
LruCache
- LruCache是泛型類,內(nèi)部采用LinkedHashMap以強(qiáng)引用的方式存儲(chǔ)外界的緩存對(duì)象;
- 當(dāng)緩存滿時(shí),移除較早使用的緩存對(duì)象,然后再添加新的緩存對(duì)象
- LruCache是線程安全的
- LruCache的創(chuàng)建示例
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
- put和get的示例
mMemoryCache.get(key)
mMemoryCache.put(key,bitmap)
DiskLruCache
DiskLruCache的創(chuàng)建、緩存查找和緩存添加操作
ImageLoader的實(shí)現(xiàn)
圖片壓縮功能實(shí)現(xiàn)——使用BitmapFactory.Options的inSampleSize
-
內(nèi)存緩存和磁盤(pán)緩存的實(shí)現(xiàn)
- 在ImageLoader的構(gòu)造方法中,建立好LruCache和DiskLruCache
- 在loadBitmapFromHttp中,將http下載的數(shù)據(jù)存儲(chǔ)到DiskLruCache中,然后去調(diào)用loadBitmapFromDiskCache;
- 在loadBitmapFromDiskCache中,取出相應(yīng)的Cache,使用圖片壓縮獲得壓縮后的bitmap,將bitmap放入LruCache中——addBitmapToMemoryCache(key,bitmap)。
- 這樣經(jīng)過(guò)2、3兩步,即實(shí)現(xiàn)了獲取Bitmap,又實(shí)現(xiàn)了內(nèi)存、硬盤(pán)的雙緩存
-
同步加載和異步加載接口的設(shè)計(jì)
- 同步加載,不能在主線程使用,首先嘗試從LruCache中獲取沒(méi),接著嘗試從DiskLruCache中讀取,最后才從網(wǎng)絡(luò)中拉取。
public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) { Bitmap bitmap = loadBitmapFromMemCache(uri); if (bitmap != null) { Log.d(TAG, "loadBitmapFromMemCache,url:" + uri); return bitmap; } try { bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight); if (bitmap != null) { Log.d(TAG, "loadBitmapFromDisk,url:" + uri); return bitmap; } bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight); Log.d(TAG, "loadBitmapFromHttp,url:" + uri); } catch (IOException e) { e.printStackTrace(); } if (bitmap == null && !mIsDiskLruCacheCreated) { Log.w(TAG, "encounter error, DiskLruCache is not created."); bitmap = downloadBitmapFromUrl(uri); } return bitmap; }- 異步加載: 首先嘗試從LruCache中獲取,如果讀取成功則返回,否則會(huì)從線程池中去調(diào)用loadBitmap,加載成功后,封裝成一個(gè)LoaderResult通過(guò)Handler發(fā)給主線程去顯示。
public void bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight) { imageView.setTag(TAG_KEY_URI, uri); Bitmap bitmap = loadBitmapFromMemCache(uri); if (bitmap != null) { imageView.setImageBitmap(bitmap); return; } Runnable loadBitmapTask = new Runnable() { @Override public void run() { Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight); if (bitmap != null) { LoaderResult result = new LoaderResult(imageView, uri, bitmap); mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget(); } } }; THREAD_POOL_EXECUTOR.execute(loadBitmapTask); }- 異步加載導(dǎo)致的列表錯(cuò)位問(wèn)題:可以在給ImageView設(shè)置圖片之前檢查url有沒(méi)有改變,如果發(fā)生改變則不給設(shè)置圖片。
-
優(yōu)化列表的卡頓現(xiàn)象
- 不要在getView中執(zhí)行耗時(shí)操作,不要在getView中直接加載圖片,否則肯定會(huì)導(dǎo)致卡頓;
- 控制異步任務(wù)的執(zhí)行頻率:在列表滑動(dòng)的時(shí)候停止加載圖片,等列表停下來(lái)以后再加載圖片;
- 使用硬件加速來(lái)解決莫名的卡頓問(wèn)題,給Activity添加配置android:hardwareAccelerated="true"。
12.3 幾個(gè)開(kāi)源Imageloader
1. Glide 一般項(xiàng)目里用這個(gè)的多,還可以實(shí)現(xiàn)裁剪,模糊等效果
2. Fresco