1、概述
Glide作爲Google推薦的一套快速高效的圖片加載框架,有很多人都在使用,我也不例外。不過在項目的需求中,難免會遇到一個這樣的需求:在非WiFi環境下,需要手動點擊才能下載圖片。
這初步實現起來是很簡單的,但一些細節卻不好解決。比如,在使用移動數據的情況下,我不能去自動加載圖片,但已經緩存過的圖片我們得讓他自動顯示出來。這個時候我們會發現,Glide沒有直接的、明確的接口去立馬判斷某圖片(Url等)是否已經緩存了。
爲了實現這個功能,我們就只能自己去Glide的緩存目錄尋找那圖片是否已經緩存下來了,但Glide緩存文件名字是不會是Url或圖片文件名,因此我們得采取一些其他手段。
若我的思路中有如何錯誤,歡迎指正。
另外,在此感謝郭神的Glide解析,爲我解決這個問題提供幫助(博客指路:Android圖片加載框架最全解析(三),深入探究Glide的緩存機制)
2、實現
(PS. 以下代碼是對在非WiFi環境下,需要手動點擊才能下載圖片這一需求的完全實現)
(1)、初步的判斷
/**
* 加載圖片
*
* @param context 加載這個行爲所處的Activity或Fragment
* @param url 圖片的網址
* @param imageView 加載到哪個ImageView上
* @param widthPixels 圖片的寬
* @param heightPixels 圖片的高
*/
public static void loadNetworkImage(final Context context, final String url, final ImageView imageView, final int widthPixels, final int heightPixels) {
if (isLoadFailed(imageView)) { // 判斷是否是正在加載中的ImageView
return;
}
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isLoadFailed(imageView)) { // 判斷是否加載失敗
loadImage(context, url, imageView, widthPixels, heightPixels);
} else if (!isLoading(imageView) && sOnImageViewClickListener != null) { // 判斷是否加載中 與 是否有設置監聽
sOnImageViewClickListener.onClick(v);
}
}
});
if (AppUtil.loadIsManualLoadPhotoInNotWiFi(context) && !NetworkUtil.isWifi()) { // 判斷用戶是否開啓了 在非WiFi環境下,需要手動點擊才能下載圖片,後在判斷是否處於WiFi中
loadCacheImage(context, url, imageView, widthPixels, heightPixels);
return;
}
loadImage(context, url, imageView, widthPixels, heightPixels);
}
這裏的第一個if判斷是爲了不讓ImageView在RecyclerView等控件中,應滑動關係而不斷刷新佈局而設定的,避免多次對同一控件調用加載圖片。
中間的OnClickListener自然是爲了讓圖片加載失敗後可以重新加載,或者是給用戶手動點擊加載而用,其中裏面第二個if判斷是爲了解決在加載成功後,還需要對圖片控件進行點擊,從而進行其他邏輯(如QQ裏對加載完成的圖片進行查看大圖)。
這裏需要注意,在每個Activity或Fragment被銷燬的時候,需要清空正在加載中的圖片集合與置空圖片點擊監聽,避免出現問題。置空代碼與判斷代碼如下:
/**
* 正在加載中的控件集合
*/
private static Set<ImageView> sImageViews = new HashSet<>();
/**
* 加載失敗所顯示的圖片
*/
private static Drawable sErrorImg = ViewUtil.getDrawable(R.mipmap.img_default);
/**
* 判斷圖片是否加載失敗
*/
private static boolean isLoadFailed(ImageView imageView) {
return sErrorImg.equals(imageView.getDrawable());
}
/**
* 判斷圖片是否在加載中
*/
private static boolean isLoading(ImageView imageView) {
return sImageViews.contains(imageView);
}
/**
* 取消所有圖片加載,並置空監聽
*/
public static void clearLoadingImg() {
for (ImageView imageView : sImageViews) {
Glide.clear(imageView);
}
sImageViews.clear();
sOnImageViewClickListener = null;
}
(2)、判斷圖片是否已經緩存了
private static void loadCacheImage(Context context, String url, ImageView imageView, int widthPixels, int heightPixels) {
// 尋找緩存圖片
File file = DiskLruCacheWrapper.get(Glide.getPhotoCacheDir(context), 250 * 1024 * 1024).get(new OriginalKey(url, EmptySignature.obtain()));
if (file != null) {
loadImage(context, url, imageView, widthPixels, heightPixels);
} else {
imageView.setImageDrawable(sErrorImg);
sImageViews.remove(imageView);
}
}
這裏就是這個需求中最大的難點了,如何去判斷Glide已經緩存了該圖片。
通過閱讀、分析Glide的源碼(也可以選擇去看郭神的Glide解析,網址也在上面發了),我們可以知道,Glide對其緩存Key的構建是比較複雜的,有着十多個參數。但我們也會發現,Glide所緩存的原圖的Key實際用到的參數只有兩個url和signature,而大多數情況下,signature只會是個空值。因此,我們只需要想辦法把url轉化成Glide的緩存Key就大功告成了。
不過通過源碼我們也知道,原圖的緩存Key類——OriginalKey是缺省的,也就是所,我們這些外部應用是無法使用。於是我就採用了一種取巧的方法,直接將Glide的這個類複製到自己的項目裏,該類代碼如下:
/**
* Glide原圖緩存Key
*/
private static class OriginalKey implements Key {
private final String id;
private final Key signature;
private OriginalKey(String id, Key signature) {
this.id = id;
this.signature = signature;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
OriginalKey that = (OriginalKey) o;
return id.equals(that.id) && signature.equals(that.signature);
}
@Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + signature.hashCode();
return result;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) throws UnsupportedEncodingException {
messageDigest.update(id.getBytes(STRING_CHARSET_NAME));
signature.updateDiskCacheKey(messageDigest);
}
}
這類裏有兩個參數,第一個就是我們圖片的Url,第二個則是簽名,用於方面標識圖片是否需要更新的,而這裏,我是直接選擇調用EmptySignature.obtain(),傳入一個空簽名。
在得到原圖的緩存Key之後,我們就只需要得到Glide的磁盤緩存了。
通過DiskLruCacheWrapper.get(File directory, int maxSize)方法,我們就可以得到Glide的磁盤緩存對象DiskCache了(在這裏建議傳入Glide的緩存目錄與250M。雖說Glide有進行判斷,如果緩存對象以存在就直接把已存在的返回過來,但爲了避免出現什麼莫名的異常,就按着Glide源碼裏的調用方式使用)。
接着通過DiskCache.get(Key key)方法去獲得緩存圖片文件。
在找到緩存文件後爲什麼不直接使用緩存文件加載呢,則是爲了避免Glide的二次緩存。我具體的Glide加載圖片方法都是用的同一個,裏面都是設定進行緩存,如果這裏傳入的是緩存文件,就會導致二次緩存,浪費!
(3)、具體的Glide加載圖片方法
private static void loadImage(final Context context, Object url, final ImageView imageView, int widthPixels, int heightPixels) {
DrawableRequestBuilder<Object> builder = Glide.with(context)
.load(url)
.placeholder(sLoadingImg)
.error(sErrorImg)
.diskCacheStrategy(DiskCacheStrategy.SOURCE) // 設置磁盤緩存只緩存原圖
.skipMemoryCache(false) //進行內存緩存
.centerCrop();
if (widthPixels != 0 || heightPixels != 0) {
builder.override(widthPixels, heightPixels);
}
builder.into(new GlideDrawableImageViewTarget(imageView) {
@Override
public void onLoadStarted(Drawable placeholder) {
super.onLoadStarted(placeholder);
Animation rotationAnimation = AnimationUtils.loadAnimation(context, R.anim.rotate_loading);
imageView.startAnimation(rotationAnimation);
sImageViews.add(imageView); // 將其添加到 正在加載中的控件集合 中
}
@Override
public void onLoadFailed(Exception e, Drawable errorDrawable) {
super.onLoadFailed(e, errorDrawable);
removeViewInLoadingSet();
}
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
super.onResourceReady(resource, animation);
removeViewInLoadingSet();
}
/**
* 將 已加載完成/加載失敗的控件 從 正在加載中的控件集合 中移除
*/
private void removeViewInLoadingSet() {
imageView.clearAnimation();
sImageViews.remove(imageView);
}
});
}
最後這裏就是Glide加載的具體實現了,裏面我通過GlideDrawableImageViewTarget對加載狀態進行監聽,設置加載中的動畫。
到此,整個需求就完成了。