12.1 Bitmap的高效加載
- Bitmap是如何加載的葱蝗? BitmapFactory類提供了四類方法:
- decodeFile
- decodeResource
- decodeStream
- decodeByteArray
- 如何高效加載Bitmap冈爹?
- 采用BitmapFactory.Options按照一定的采樣率來加載所需尺寸的圖片,因?yàn)閕mageview所需的圖片大小往往小于圖片的原始尺寸。
- BitmapFactory.Options的inSampleSize參數(shù),即采樣率
- 獲取采樣率和高效加載的例子
注意:將inJustDecodeBounds設(shè)置為true的時候,BitmapFactory只會解析圖片的原始寬高信息铺纽,并不會真正的加載圖片
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)緩存滿時,會優(yōu)先淘汰那些近期最少使用的緩存對象
- LRU算法的緩存:LruCache(內(nèi)存緩存)和DiskLruCache(磁盤緩存)
LruCache
- LruCache是泛型類锅很,內(nèi)部采用LinkedHashMap以強(qiáng)引用的方式存儲外界的緩存對象;
- 當(dāng)緩存滿時其馏,移除較早使用的緩存對象,然后再添加新的緩存對象
- 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)存緩存和磁盤緩存的實(shí)現(xiàn)
- 在ImageLoader的構(gòu)造方法中叛复,建立好LruCache和DiskLruCache
- 在loadBitmapFromHttp中,將http下載的數(shù)據(jù)存儲到DiskLruCache中扔仓,然后去調(diào)用loadBitmapFromDiskCache;
- 在loadBitmapFromDiskCache中褐奥,取出相應(yīng)的Cache,使用圖片壓縮獲得壓縮后的bitmap翘簇,將bitmap放入LruCache中——addBitmapToMemoryCache(key,bitmap)撬码。
- 這樣經(jīng)過2、3兩步版保,即實(shí)現(xiàn)了獲取Bitmap呜笑,又實(shí)現(xiàn)了內(nèi)存夫否、硬盤的雙緩存
-
同步加載和異步加載接口的設(shè)計(jì)
- 同步加載,不能在主線程使用叫胁,首先嘗試從LruCache中獲取沒慷吊,接著嘗試從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中獲取溉瓶,如果讀取成功則返回,否則會從線程池中去調(diào)用loadBitmap谤民,加載成功后堰酿,封裝成一個LoaderResult通過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)致的列表錯位問題:可以在給ImageView設(shè)置圖片之前檢查url有沒有改變张足,如果發(fā)生改變則不給設(shè)置圖片触创。
-
優(yōu)化列表的卡頓現(xiàn)象
- 不要在getView中執(zhí)行耗時操作,不要在getView中直接加載圖片为牍,否則肯定會導(dǎo)致卡頓哼绑;
- 控制異步任務(wù)的執(zhí)行頻率:在列表滑動的時候停止加載圖片,等列表停下來以后再加載圖片碉咆;
- 使用硬件加速來解決莫名的卡頓問題抖韩,給Activity添加配置android:hardwareAccelerated="true"。
12.3 幾個開源Imageloader
1. Glide 一般項(xiàng)目里用這個的多疫铜,還可以實(shí)現(xiàn)裁剪茂浮,模糊等效果
2. Fresco