1. 圖片三級緩存流程圖(盜用的網(wǎng)上的)
2. 內(nèi)存緩存
2.1 java中對象的四種引用類型
-
強引用
Object obj = new Object();
java中所有new出來的對象都是強引用類型空另,gc寧愿拋出OOM異常吆寨,也不會回收它
-
軟引用管削,SoftReference
Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj);
當(dāng)一個對象只有軟引用存在時泛烙,系統(tǒng)內(nèi)存不足時此對象會被gc回收
-
弱引用巧还,WeakReference
Object obj = new Object(); WeakReference<Object> wf = new WeakReference<Object>(obj);
當(dāng)一個對象只有弱引用存在時,此對象隨時會被gc回收
-
虛引用
Object obj = new Object(); PhantomReference<Object> pf = new PhantomReference<Object>(obj);
每次垃圾回收的時候都會被回收
2.2 使用LruCache類來做內(nèi)存緩存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mLruCache = new LruCache<String, SoftReference<Bitmap>>(cacheSize) {
//必須重寫此方法,來測量Bitmap的大小
@Override
protected int sizeOf(String key, SoftReference<Bitmap> value) {
return value.get() == null ? 0 : value.get().getByteCount();
}
};
2.3 從內(nèi)存中獲取圖片
//從內(nèi)存中查找圖片,有就返回丑瞧,無則去文件中查找
SoftReference<Bitmap> reference = mLruCache.get(url);
Bitmap cacheBitmap;
if (reference != null) {
//有則顯示圖片
cacheBitmap = reference.get();
if (cacheBitmap != null) {
imageView.setImageBitmap(cacheBitmap);
Log.d(TAG, "內(nèi)存中有圖片顯示");
//不往下走了
return;
}
}
3. 磁盤緩存
3.1 設(shè)定文件路徑
private File getCacheDir() {
File file;
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
// 有SD卡就保存到sd卡
file = mContext.getExternalCacheDir();
} else {
// 沒有就保存到內(nèi)部儲存
file = mContext.getCacheDir();
}
return file;
}
3.2 解析文件生成圖片
private Bitmap getBitmapFromFile() {
// 從url中獲取文件名字
String fileName = url.substring(url.lastIndexOf("/") + 1);
File file = new File(getCacheDir(), fileName);
// 確保路徑?jīng)]有問題
if (file.exists() && file.length() > 0) {
// 返回圖片
return BitmapFactory.decodeFile(file.getAbsolutePath());
} else {
return null;
}
}
3.2 從磁盤緩存中獲取圖片
Bitmap diskBitmap = getBitmapFromFile();
if (diskBitmap != null) {
imageView.setImageBitmap(diskBitmap);
Log.d(TAG, "磁盤中有圖片顯示");
//不往下走了
return;
}
4. 從網(wǎng)絡(luò)中請求圖片
4.1 創(chuàng)建一個線程池
private ExecutorService mExecutorService = Executors.newFixedThreadPool(5);
4.2 利用線程池執(zhí)行任務(wù)
mExecutorService.submit(this);
4.3 利用HttpURLConnection請求圖片
URL loadUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) loadUrl.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(2000);
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
final Bitmap bitmap = BitmapFactory.decodeStream(is);
mHandler.post(new Runnable() {
@Override
public void run() {
//主線程顯示圖片
imageView.setImageBitmap(bitmap);
Log.d(TAG, "網(wǎng)絡(luò)請求圖片");
}
});
is.close();
4.3 將圖片存入內(nèi)存中
mLruCache.put(url, new SoftReference<Bitmap>(bitmap));
4.4 將圖片存入磁盤
private void saveBitmapToFile(Bitmap bitmap) throws FileNotFoundException {
String fileName = url.substring(url.lastIndexOf("/") + 1);
File file = new File(getCacheDir(), fileName);
FileOutputStream os = new FileOutputStream(file);
// 將圖片轉(zhuǎn)換為文件進行存儲
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
}
5. 通過listview異步加載圖片優(yōu)化
5.1 listview列表錯亂
現(xiàn)象
快速滑動列表,圖片的位置可能會發(fā)生錯位蜀肘,即圖片和對應(yīng)的列表位置對應(yīng)不上绊汹。-
原因
重用了 convertView 且有異步操作,兩者缺一不可扮宠。
listview布局復(fù)用圖.jpg
我來簡單分析一下
當(dāng)重用convertView時西乖,最初一屏幕顯示7個記錄,getView被調(diào)用7次,也就創(chuàng)建了7個convertView浴栽。當(dāng)Item1被滑出屏幕外,Item8進入屏幕時轿偎,這時沒有為Item8創(chuàng)建新的view實例典鸡,Item8復(fù)用的是Item1的view,如果沒有異步就沒有任何問題坏晦,雖然Item8和Item1用的是一個view萝玷,但滑動到Item8時已經(jīng)刷上了Item8的數(shù)據(jù),這時Item1的數(shù)據(jù)和Item8的數(shù)據(jù)是一樣的昆婿,因為它們指向的是同一塊內(nèi)存球碉,但此時Item1已經(jīng)在屏幕外了,你是看不見的仓蛆。當(dāng)Item1可見時睁冬,又刷上了Item1的數(shù)據(jù)。
但是當(dāng)異步就會有問題了看疙。如果Item1下載圖片的速度比較慢豆拨,Item8下載圖片的速度比較快,當(dāng)Item8可見時能庆,Item8會先顯示自己下載的圖片施禾,但等到Item1下載完成時你會發(fā)現(xiàn)Item8的圖片變成了Item1的圖片,因為它們復(fù)用的是同一個view搁胆。如果Item8下載圖片速度較慢弥搞,Item1下載圖片速度較快,當(dāng)Item8可見時渠旁,Item8會顯示Item1的圖片攀例,之后再顯示Item8的圖片,就會造成閃爍的狀況一死。
-
解決辦法
給ImageView設(shè)置一個tag肛度,只有滿足條件才設(shè)置圖片。holder.icon_iv.setTag(mNewsBeans.get(position).getUrl()); if (imageView.getTag() != null && imageView.getTag().equals(url)) { imageView.setImageBitmap(bitmap); Log.d(TAG, "網(wǎng)絡(luò)請求圖片"); }
分析
如果Item1下載圖片的速度比較慢投慈,Item8下載圖片的速度比較快承耿,當(dāng)Item8可見時,Item8滿足if條件伪煤,并顯示自己下載的圖片加袋,當(dāng)item1下載完成時,由于當(dāng)前的tag是Item8得url抱既,而下載圖片的url是Item1得url职烧,當(dāng)然不滿足if條件,也就不會再設(shè)置圖片了。
5.2 listview卡頓
現(xiàn)象以及原因
如果用戶刻意地頻繁上下滑動蚀之,這就會在一瞬間產(chǎn)生上百個異步任務(wù)蝗敢,這些異步任務(wù)會造成線程池的堵塞,并且會帶來大量的UI更新操作足删,由于一瞬間存在大量的UI更細操作寿谴,這些UI操作是運行在主線程的,這樣機會造成一定程度的卡頓失受。-
解決辦法
可以在列表滑動的時候停止異步任務(wù)讶泰,而在停下來以后再加載圖片。if (mIsListViewIdle) { ImageLoader.with(mContext).load(mNewsBeans.get(position).getUrl()).placeholder(R.mipmap.ic_launcher).into(holder.icon_iv); } public void onScrollStateChanged(AbsListView view, int scrollState) { //停止?jié)L動 if (scrollState == SCROLL_STATE_IDLE) { mIsListViewIdle = true; notifyDataSetChanged(); } else { mIsListViewIdle = false; } }
題外話拂到,開啟硬件加速也可以解決一些卡頓問題
<activity android:hardwareAccelerated="true" ...>
6. 完整的ImageLoader代碼
public class ImageLoader {
private static final String TAG = "ImageLoader";
private Context mContext;
private static ImageLoader instance;
private LruCache<String, SoftReference<Bitmap>> mLruCache;
private ExecutorService mExecutorService = Executors.newFixedThreadPool(5);
private Handler mHandler;
private ImageLoader(Context context) {
mContext = context;
mHandler = new Handler(Looper.getMainLooper());
//計算程序分配的最大內(nèi)存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mLruCache = new LruCache<String, SoftReference<Bitmap>>(cacheSize) {
//必須重寫此方法痪署,來測量Bitmap的大小
@Override
protected int sizeOf(String key, SoftReference<Bitmap> value) {
return value.get() == null ? 0 : value.get().getByteCount();
}
};
}
private static ImageLoader getInstance(Context context) {
if (instance == null) {
synchronized (ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader(context);
}
}
}
return instance;
}
public static ImageLoader with(Context context) {
return getInstance(context);
}
public RequestCreator load(String url) {
return new RequestCreator(url);
}
public class RequestCreator implements Runnable {
String url;
int holderResId;
int errorResId;
ImageView imageView;
public RequestCreator(String url) {
this.url = url;
}
public RequestCreator placeholder(int holderResId) {
this.holderResId = holderResId;
return this;
}
public RequestCreator error(int errorResId) {
this.errorResId = errorResId;
return this;
}
public void into(ImageView imageView) {
this.imageView = imageView;
//先設(shè)置占位圖片
imageView.setImageResource(holderResId);
//從內(nèi)存中查找圖片,有就返回兄旬,無則去磁盤中查找
SoftReference<Bitmap> reference = mLruCache.get(url);
Bitmap cacheBitmap;
if (reference != null) {
//有則顯示圖片
cacheBitmap = reference.get();
if (cacheBitmap != null) {
imageView.setImageBitmap(cacheBitmap);
Log.d(TAG, "內(nèi)存中有圖片顯示");
//不往下走了
return;
}
}
//去磁盤中查找圖片狼犯,有就返回,無則去網(wǎng)絡(luò)中請求
Bitmap diskBitmap = getBitmapFromFile();
if (diskBitmap != null) {
imageView.setImageBitmap(diskBitmap);
Log.d(TAG, "磁盤中有圖片顯示");
//不往下走了
return;
}
//磁盤中沒有领铐,啟動線程池請求圖片
mExecutorService.submit(this);
}
@Override
public void run() {
try {
URL loadUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) loadUrl.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(2000);
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
final Bitmap bitmap = BitmapFactory.decodeStream(is);
mHandler.post(new Runnable() {
@Override
public void run() {
//主線程顯示圖片
if (imageView.getTag() != null && imageView.getTag().equals(url)) {
imageView.setImageBitmap(bitmap);
Log.d(TAG, "網(wǎng)絡(luò)請求圖片");
}
}
});
is.close();
//保存到內(nèi)存
mLruCache.put(url, new SoftReference<Bitmap>(bitmap));
//保存到磁盤中
saveBitmapToFile(bitmap);
} else {
Log.d(TAG, "網(wǎng)絡(luò)請求碼非200");
showError();
}
} catch (Exception e) {
Log.d(TAG, "網(wǎng)絡(luò)請求圖片發(fā)生異常");
showError();
}
}
/**
* 顯示錯誤圖片
*/
private void showError() {
mHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageResource(errorResId);
}
});
}
/**
* 將bitmap保存到磁盤中
*/
private void saveBitmapToFile(Bitmap bitmap) throws FileNotFoundException {
String fileName = url.substring(url.lastIndexOf("/") + 1);
File file = new File(getCacheDir(), fileName);
FileOutputStream os = new FileOutputStream(file);
// 將圖片轉(zhuǎn)換為文件進行存儲
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
}
/**
* 從文件中獲取bitmap
*/
private Bitmap getBitmapFromFile() {
// 從url中獲取文件名字
String fileName = url.substring(url.lastIndexOf("/") + 1);
File file = new File(getCacheDir(), fileName);
// 確保路徑?jīng)]有問題
if (file.exists() && file.length() > 0) {
// 返回圖片
return BitmapFactory.decodeFile(file.getAbsolutePath());
} else {
return null;
}
}
/**
* 獲取緩存路徑目錄
*/
private File getCacheDir() {
File file;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
// 有SD卡就保存到sd卡
file = mContext.getExternalCacheDir();
} else {
// 沒有就保存到內(nèi)部儲存
file = mContext.getCacheDir();
}
return file;
}
}
}
用法
ImageLoader.with(this).load(imgUrl).placeholder(resId).error(resId).into(imageView);
7. 擴展
現(xiàn)在流行的圖片加載框架辜王,在緩存處理這塊做了很多細節(jié)處理。后續(xù)可以考慮在緩存這塊擴展一下罐孝,比如設(shè)置是否緩存的開關(guān)呐馆,設(shè)置文件緩存的路徑和大小等等。