使用Glide實現在非WiFi環境手動點擊下載圖片(判斷Glide是否緩存了圖片)

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對加載狀態進行監聽,設置加載中的動畫。

到此,整個需求就完成了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章