Glide如何加載同一URL下的最新圖片

鎮樓圖.png

簡述

基于項目需求,用戶更換新頭像后,iOS、Android、web 端三端需要能更新到最新的頭像。由于各種原因,用戶頭像的URL始終是不變的。而一般App端的圖片加載框架都會把 URL 作為 key 對圖片進行多級緩存,用戶更改了新頭像,此時 URL 不變就會導致圖片框架始終都只會加載本地的緩存。
梳理以上需求,有以下問題需要解決:

1、在 URL 不變的情況下,如何得知服務端的圖片是否已經更改
2、在知道服務端圖片已經更改的情況下,如何讓圖片請求框架去請求服務端的最新圖片,而不是加載本地緩存
  • 針對第一個問題,Http協議提供了 ETag 或者 Last-Modified 來判斷當前請求資源是否改變,具體可以查看鏈接了解,通俗解釋為第一次請求資源 A,會返回一個請求頭 H,第二次請求 A 時帶上該請求頭 H,返回的響應碼為 304 代表資源沒有變(不會返回資源 A ),為 200 代表資源有更新(會返回資源 A )。

注意:

ETag 對比 Last-Modified 的優勢

1、一些文件也許會周期性的更改,但是他的內容并不改變( 僅僅改變的修改時間),這個時候我們并不希望客戶端認為這個文件被修改了,而重新GET;

2、某些文件修改非常頻繁,比如在秒以下的時間內進行修改,(比方說 1s 內修改了 N 次),If-Modified-Since 能檢查到的粒度是 s 級的,這種修改無法判斷(或者說 UNIX 記錄 MTIME 只能精確到秒);

3、某些服務器不能精確的得到文件的最后修改時間。

  • 針對第二個問題,項目中使用 Glide 作為圖片請求,設置 memery 和 disk 緩存都忽略是可以讓 glide 直接去請求服務端圖片的,但是沒有了緩存,體驗會比較差,這里使用的是 Glide 的 signature,通過 ObjectKey 來作為圖片的標識,ObjectKey 大家可以看一下源碼,通過所傳參數的 hashCode 來區分,結合第一個問題的回答,我們可以把 ETag 或者 Last-Modified 作為 ObjectKey 的參數
/**
 * 使用Glide加載圖片
 * @param context  上下文
 * @param key  Last-Modified或Etag
 * @param url  圖片url
 * @param imageView  圖片控件
 */
private fun glideLoadImg(context: Context, key: String, url: String, imageView: ImageView) {
    Glide.with(context)
            .setDefaultRequestOptions(RequestOptions.circleCropTransform()
                    //圖片簽名信息,相同url下如果需要刷新圖片,signature不同則會加載網絡端的圖片資源
                    .signature(ObjectKey(key)).placeholder(imageView.drawable))
            .load(url)
            .into(imageView)
} 
  • 綜合上面的,一個簡單的方案就有了,接下來就是代碼實現,首先是獲取資源的 ETag 或者 Last-Modified,這兩個都是存在于響應頭里面,為了性能和流量( HEAD 請求只會返回響應頭,不會響應體),我們使用了 HEAD 請求,代碼是用 Retrofit 實現
/**
 * 基礎api方法,包括POST、GET、UPLOAD、DOWNLOAD等
 * @version 2.2.0
 * @date 2017/5/16 16:49
 */

interface BaseApiService {
    /** HEAD請求,只會返回響應頭,沒有返回響應體,節省流量 */
    /** HEAD請求,帶上Last-Modified或Etag的請求頭 */
    @HEAD
    fun getImg(@Url url: String, @Header(IF_NONE_MATCH) lastModify: String): Observable<Response<Void>>
}
/**
 * 圖片相關工具類
 *
 * @date 2018/2/27 18:27
 * @version v4.0.0
 */
const val ETAG = "ETag"
const val IF_NONE_MATCH = "If-None-Match"
const val LAST_MODIFIED = "Last-Modified"
const val IF_MODIFIED_SINCE = "If-Modified-Since"
/**
 * 加載頭像
 * @param url  圖片url
 */
fun ImageView.loaderHeadImgWithHead(url: String) {
    //當url為空時,不請求網絡,加載默認圖片
    if (url.isEmpty()) {
        Glide.with(context).load(R.drawable.default_head).into(this)
    } else {
        //獲取url對應存儲在sp中的Last-Modified或Etag
        val key = SharedPreferencesUtils[context, SP_FILE_COMMON, url, ""]
        if (key.isNotEmpty()) {
            //key非空,即本地存在緩存,先加載本地緩存
            glideLoadImg(context, key, url, this)
        } else {
            //key為空,不存在緩存,加載默認圖片
            Glide.with(context).load(R.drawable.default_head).into(this)
        }
        RetrofitClient.getInstance(context).getApiService().getImg(url, key)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    //獲取響應頭中的Last-Modified或Etag
                    val head = it.headers().get(ETAG)
                    if (it.code() == 304) {
                        //304的時候返回的lastModified或者Etag為null,此時使用上次存儲的key來加載圖片
                        glideLoadImg(context, key, url, this)
                    } else if (it.code() == 200) {
                        //保存最新的lastModified或者Etag
                        SharedPreferencesUtils.save(context, SP_FILE_COMMON, url, head!!)
                        glideLoadImg(context, head, url, this)
                    }
                },{it.printStackTrace()})
    }
}

總結上面的實現,大概就是 Retrofit 攜帶 ETag 或者 Last-Modified 作為請求頭發起 HEAD 請求,根據返回的請求碼( 304 或者 200 )來判斷服務端的圖片是否更改,如果有更改,再將服務端返回的 ETag 或者 Last-Modified 作為 Signature 讓 Glide 去加載新圖片。上面的代碼更細致一點,還加了本地是否已經存在緩存圖片的校驗,體驗稍微好一些。

上面的實現可優化的地方還有很多,正常的圖片加載,本地緩存存在的情況下根本不需要進行網絡請求,上面的實現會先進行一次 HEAD 請求,是為了判斷服務端圖片是否有更改,下圖是 Android studio 監測的流量,HEAD 請求是黃色,加載圖片的是藍色,雖然流量消耗很少,但是增加一次網絡請求,圖片多的情況下還是會有影響的。如果大家仔細了解了 ETag 或者 Last-Modified,會發現,如果資源有更新,返回 200 時,同時資源也會返回回來,這里返回的應該是圖片的二進制,此時已經拿到圖片的二進制了,本來可以不用 Glide 再次發起請求,只需要讓 Glide 加載二進制流即可,但是這里存在一個問題,直接加載二進制流,下次需要加載緩存的時候就沒辦法加載了,因為一般都是用 url 作為加載圖片的路徑,這里直接給流,那么 url 跟圖片之間就沒有關聯了,如果有更好的方案,歡迎拍磚。

流量.png

本地搞了個 Tomcat,把圖片放在 webapps\ROOT 路徑下,直接就可以測試訪問。

代碼地址:https://github.com/czblse/SameUrl4Image
參考鏈接
  1. 百度百科
  2. iOS Download Image In The Same URL
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容