最近剛完成一個仿微信項目,剛閑下來,寫篇文章,友善大家了解Android的圖檔加載模式。
有人會問:為什麼要自定義,三方庫不是很完善嗎?
我說:是的,現在有Picasso,Glide等很多優秀的封裝,可以完成。
但重點是:
1、通過自定義了解原理和本質才是最主要的。
2、三方庫少則十多個類,多則幾十個甚至上百個類,方法更是不計其數,要是完全掌握恐怕至少也要一個月時間,這樣對我們的學習效率影響很大,試想我們是在最有限的時間掌握原理好呢,還是一個勁的鑽進無限的回調,參數拼接... ..好呢。
3、也是上班原因,由于本人所在機關對外部通路嚴格的稽核,是以開發中的很多技術點,很多都是自己封裝完成,在自己編寫的過程中不斷完善和探索,以達到能公用的目标為己任。
好了,閑話不啰嗦,我們切入正題。
一、原理:圖檔加載的緩存機制,相信大家都知道,但還是唠叨幾句,希望對初學的人也有個認知。
因為APP端使用者是流量有限的,是以能最大限度的減下使用者流量的消耗,是使用者很看重的一點,是以要把一些圖檔呀,音頻視訊,集合呀等一些過大的資料,緩存起來,等到使用者再次登入,就可以從本地擷取,極大節約了資源。
三級緩存:

好了,以目了然,現在就開始具體的代碼實作了
二:代碼
ImageLoader.java(圖檔加載類)
ImageLoadListener.java(圖檔加載完成回調接口)
/**
* Created by jambestwick on 2018/1/10.
* <p>
* 三級緩存
*/
public class ImageLoader {
private static final String TAG = "ImageLoader";
// LRUCahce 池子
private static LruCache<String, Bitmap> mCache;
private static Handler mHandler;
private static Map<ImageView, Future<?>> mTaskTags = new LinkedHashMap<ImageView, Future<?>>();
private Context mContext;
private static ImageLoader imageLoader;
public ImageLoader(Context context) {
mContext = context;
if (mCache == null) {
// 防止OOM,mCache存放
int maxSize = (int) (Runtime.getRuntime().freeMemory() / 2);
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
}
if (mHandler == null) {
mHandler = new Handler();
}
}
public static ImageLoader getImageLoader() {
return null == imageLoader ? new ImageLoader(BaseApplication.getBaseApplication()) : imageLoader;
}
public LruCache<String, Bitmap> getmCache() {
return mCache;
}
public void display(ImageView iv, String url, ImageLoadListener loadListener) {
// 1.去記憶體中取
Bitmap bitmap;
bitmap = mCache.get(url);
if (bitmap != null) {
Log.e("1記憶體讀取:", url);
iv.setImageBitmap(bitmap);
if (null != loadListener) {//單個朋友圈圖檔重置layoutParams
invokeRelayoutBitmap(loadListener, bitmap, iv, url);
}
return;
}
// 2.去硬碟上取
bitmap = loadBitmapFromLocal(url);
if (bitmap != null) {
Log.e("2檔案讀取:", url);
iv.setImageBitmap(bitmap);
if (null != loadListener) {//單個朋友圈圖檔重置layoutParams
invokeRelayoutBitmap(loadListener, bitmap, iv, url);
}
return;
}
// 3. 去網絡擷取圖檔
loadBitmapFromNet(iv, url, loadListener);
}
private void invokeRelayoutBitmap(ImageLoadListener loadListener, Bitmap bitmap, ImageView iv, String url) {
mHandler.post(new RefreshBitmap(loadListener, bitmap, url, iv));
}
private void loadBitmapFromNet(ImageView iv, String url, ImageLoadListener loadListener) {
// 判斷是否有線程在為 imageView加載資料
Future<?> futrue = mTaskTags.get(iv);
if (futrue != null && !futrue.isCancelled() && !futrue.isDone()) {
Log.d(TAG, "取消任務");
// 線程正在執行
futrue.cancel(true);
}
futrue = ThreadPool.getCachedInstance().submit(new ImageLoadTask(iv, url, loadListener));
mTaskTags.put(iv, futrue);
}
class ImageLoadTask implements Runnable {
private String mUrl;
private ImageView iv;
private ImageLoadListener loadListener;
public ImageLoadTask(ImageView iv, String url, ImageLoadListener loadListener) {
this.mUrl = url;
this.iv = iv;
this.loadListener = loadListener;
}
@Override
public void run() {
try {
Log.e("網絡異步", "圖檔" + mUrl);
// 擷取連接配接
//HttpsURLConnection conn = HttpsFormUtil.getInstance().getHttpURLConnection(new URL(mUrl));
HttpURLConnection conn = (HttpURLConnection) new URL(mUrl).openConnection();
conn.setConnectTimeout(30 * 1000);// 設定連接配接伺服器逾時時間
conn.setReadTimeout(30 * 1000);// 設定讀取響應逾時時間
// 連接配接網絡
conn.connect();
// 擷取響應碼
int code = conn.getResponseCode();
if (200 == code) {
InputStream is = conn.getInputStream();
// 将流轉換為bitmap
final Bitmap bitmap = BitmapFactory.decodeStream(is);
// 存儲到本地
write2Local(mUrl, bitmap);
// 存儲到記憶體
mCache.put(mUrl, bitmap);
mHandler.post(new Runnable() {
@Override
public void run() {
if (null != loadListener) {
loadListener.onLoadingComplete(mUrl, iv, bitmap);
}
display(iv, mUrl, loadListener);
}
});
}
} catch (Exception e) {
Log.e(TAG, "download failed:" + e.toString());
}
}
}
/**
* 本地找圖檔
*
* @param url
*/
private Bitmap loadBitmapFromLocal(String url) {
// 去找檔案,将檔案轉換為bitmap
String name;
try {
name = MD5Encode.encode(url);
File file = new File(getCacheDir(), name);
if (file.exists()) {
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
// 存儲到記憶體
mCache.put(url, bitmap);
return bitmap;
}
} catch (Exception e) {
// TODO Auto-generated catch block
Log.e(TAG, "load LocalImage Failed:" + e.toString());
}
return null;
}
private void write2Local(String url, Bitmap bitmap) {
String name;
FileOutputStream fos = null;
try {
name = MD5Encode.encode(url);
File file = new File(getCacheDir(), name);
fos = new FileOutputStream(file);
// 将圖像寫到流中,放縮略,減下存儲
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, fos);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
Log.e(TAG, "io exception:" + e.toString());
}
}
}
}
private String getCacheDir() {
String state = Environment.getExternalStorageState();//外置sd卡路徑
File dir;
if (Environment.MEDIA_MOUNTED.equals(state)) {
// 有sd卡
dir = new File(Environment.getExternalStorageDirectory(), "/zhanglong/data/" + mContext.getPackageName()
+ "/icon");
} else {
// 沒有sd卡
dir = new File(mContext.getCacheDir(), "/icon");
}
if (!dir.exists()) {
dir.mkdirs();
}
return dir.getAbsolutePath();
}
static class RefreshBitmap implements Runnable {
private ImageLoadListener loadListener;
private Bitmap bitmap;
private String url;
private ImageView iv;
public RefreshBitmap(ImageLoadListener loadListener, Bitmap bitmap, String url, ImageView iv) {
this.loadListener = loadListener;
this.bitmap = bitmap;
this.url = url;
this.iv = iv;
}
@Override
public void run() {
loadListener.onLoadingComplete(url, iv, bitmap);
}
}
}
核心方法就是一個display,
/**
* Created by jambestwick on 2018/1/10.
* 下載下傳完成監聽
*/
public interface ImageLoadListener {
void onLoadingComplete(String imageUri, View view, Bitmap bitmap);
}
就這麼簡單