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

鎮(zhèn)樓圖.png

簡(jiǎn)述

基于項(xiàng)目需求郁妈,用戶更換新頭像后,iOS绍申、Android噩咪、web 端三端需要能更新到最新的頭像。由于各種原因极阅,用戶頭像的URL始終是不變的胃碾。而一般App端的圖片加載框架都會(huì)把 URL 作為 key 對(duì)圖片進(jìn)行多級(jí)緩存,用戶更改了新頭像涂屁,此時(shí) URL 不變就會(huì)導(dǎo)致圖片框架始終都只會(huì)加載本地的緩存书在。
梳理以上需求,有以下問題需要解決:

1拆又、在 URL 不變的情況下儒旬,如何得知服務(wù)端的圖片是否已經(jīng)更改
2、在知道服務(wù)端圖片已經(jīng)更改的情況下帖族,如何讓圖片請(qǐng)求框架去請(qǐng)求服務(wù)端的最新圖片栈源,而不是加載本地緩存
  • 針對(duì)第一個(gè)問題,Http協(xié)議提供了 ETag 或者 Last-Modified 來(lái)判斷當(dāng)前請(qǐng)求資源是否改變竖般,具體可以查看鏈接了解甚垦,通俗解釋為第一次請(qǐng)求資源 A,會(huì)返回一個(gè)請(qǐng)求頭 H,第二次請(qǐng)求 A 時(shí)帶上該請(qǐng)求頭 H艰亮,返回的響應(yīng)碼為 304 代表資源沒有變(不會(huì)返回資源 A )闭翩,為 200 代表資源有更新(會(huì)返回資源 A )。

注意:

ETag 對(duì)比 Last-Modified 的優(yōu)勢(shì)

1迄埃、一些文件也許會(huì)周期性的更改疗韵,但是他的內(nèi)容并不改變( 僅僅改變的修改時(shí)間),這個(gè)時(shí)候我們并不希望客戶端認(rèn)為這個(gè)文件被修改了侄非,而重新GET;

2蕉汪、某些文件修改非常頻繁,比如在秒以下的時(shí)間內(nèi)進(jìn)行修改逞怨,(比方說 1s 內(nèi)修改了 N 次)者疤,If-Modified-Since 能檢查到的粒度是 s 級(jí)的,這種修改無(wú)法判斷(或者說 UNIX 記錄 MTIME 只能精確到秒)叠赦;

3驹马、某些服務(wù)器不能精確的得到文件的最后修改時(shí)間。

  • 針對(duì)第二個(gè)問題眯搭,項(xiàng)目中使用 Glide 作為圖片請(qǐng)求窥翩,設(shè)置 memery 和 disk 緩存都忽略是可以讓 glide 直接去請(qǐng)求服務(wù)端圖片的,但是沒有了緩存鳞仙,體驗(yàn)會(huì)比較差寇蚊,這里使用的是 Glide 的 signature,通過 ObjectKey 來(lái)作為圖片的標(biāo)識(shí)棍好,ObjectKey 大家可以看一下源碼仗岸,通過所傳參數(shù)的 hashCode 來(lái)區(qū)分,結(jié)合第一個(gè)問題的回答借笙,我們可以把 ETag 或者 Last-Modified 作為 ObjectKey 的參數(shù)
/**
 * 使用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不同則會(huì)加載網(wǎng)絡(luò)端的圖片資源
                    .signature(ObjectKey(key)).placeholder(imageView.drawable))
            .load(url)
            .into(imageView)
} 
  • 綜合上面的业稼,一個(gè)簡(jiǎn)單的方案就有了盗痒,接下來(lái)就是代碼實(shí)現(xiàn),首先是獲取資源的 ETag 或者 Last-Modified低散,這兩個(gè)都是存在于響應(yīng)頭里面俯邓,為了性能和流量( HEAD 請(qǐng)求只會(huì)返回響應(yīng)頭,不會(huì)響應(yīng)體)熔号,我們使用了 HEAD 請(qǐng)求稽鞭,代碼是用 Retrofit 實(shí)現(xiàn)
/**
 * 基礎(chǔ)api方法,包括POST引镊、GET朦蕴、UPLOAD篮条、DOWNLOAD等
 * @version 2.2.0
 * @date 2017/5/16 16:49
 */

interface BaseApiService {
    /** HEAD請(qǐng)求,只會(huì)返回響應(yīng)頭吩抓,沒有返回響應(yīng)體涉茧,節(jié)省流量 */
    /** HEAD請(qǐng)求琴拧,帶上Last-Modified或Etag的請(qǐng)求頭 */
    @HEAD
    fun getImg(@Url url: String, @Header(IF_NONE_MATCH) lastModify: String): Observable<Response<Void>>
}
/**
 * 圖片相關(guān)工具類
 *
 * @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) {
    //當(dāng)url為空時(shí)降瞳,不請(qǐng)求網(wǎng)絡(luò),加載默認(rèn)圖片
    if (url.isEmpty()) {
        Glide.with(context).load(R.drawable.default_head).into(this)
    } else {
        //獲取url對(duì)應(yīng)存儲(chǔ)在sp中的Last-Modified或Etag
        val key = SharedPreferencesUtils[context, SP_FILE_COMMON, url, ""]
        if (key.isNotEmpty()) {
            //key非空嘱支,即本地存在緩存蚓胸,先加載本地緩存
            glideLoadImg(context, key, url, this)
        } else {
            //key為空,不存在緩存除师,加載默認(rèn)圖片
            Glide.with(context).load(R.drawable.default_head).into(this)
        }
        RetrofitClient.getInstance(context).getApiService().getImg(url, key)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    //獲取響應(yīng)頭中的Last-Modified或Etag
                    val head = it.headers().get(ETAG)
                    if (it.code() == 304) {
                        //304的時(shí)候返回的lastModified或者Etag為null沛膳,此時(shí)使用上次存儲(chǔ)的key來(lái)加載圖片
                        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()})
    }
}

總結(jié)上面的實(shí)現(xiàn),大概就是 Retrofit 攜帶 ETag 或者 Last-Modified 作為請(qǐng)求頭發(fā)起 HEAD 請(qǐng)求汛聚,根據(jù)返回的請(qǐng)求碼( 304 或者 200 )來(lái)判斷服務(wù)端的圖片是否更改锹安,如果有更改,再將服務(wù)端返回的 ETag 或者 Last-Modified 作為 Signature 讓 Glide 去加載新圖片倚舀。上面的代碼更細(xì)致一點(diǎn)叹哭,還加了本地是否已經(jīng)存在緩存圖片的校驗(yàn),體驗(yàn)稍微好一些痕貌。

上面的實(shí)現(xiàn)可優(yōu)化的地方還有很多风罩,正常的圖片加載,本地緩存存在的情況下根本不需要進(jìn)行網(wǎng)絡(luò)請(qǐng)求舵稠,上面的實(shí)現(xiàn)會(huì)先進(jìn)行一次 HEAD 請(qǐng)求超升,是為了判斷服務(wù)端圖片是否有更改,下圖是 Android studio 監(jiān)測(cè)的流量哺徊,HEAD 請(qǐng)求是黃色室琢,加載圖片的是藍(lán)色,雖然流量消耗很少落追,但是增加一次網(wǎng)絡(luò)請(qǐng)求盈滴,圖片多的情況下還是會(huì)有影響的。如果大家仔細(xì)了解了 ETag 或者 Last-Modified轿钠,會(huì)發(fā)現(xiàn)巢钓,如果資源有更新,返回 200 時(shí)谣膳,同時(shí)資源也會(huì)返回回來(lái)竿报,這里返回的應(yīng)該是圖片的二進(jìn)制,此時(shí)已經(jīng)拿到圖片的二進(jìn)制了继谚,本來(lái)可以不用 Glide 再次發(fā)起請(qǐng)求烈菌,只需要讓 Glide 加載二進(jìn)制流即可,但是這里存在一個(gè)問題,直接加載二進(jìn)制流芽世,下次需要加載緩存的時(shí)候就沒辦法加載了挚赊,因?yàn)橐话愣际怯?url 作為加載圖片的路徑,這里直接給流济瓢,那么 url 跟圖片之間就沒有關(guān)聯(lián)了荠割,如果有更好的方案,歡迎拍磚旺矾。

流量.png

本地搞了個(gè) Tomcat蔑鹦,把圖片放在 webapps\ROOT 路徑下,直接就可以測(cè)試訪問箕宙。

代碼地址:https://github.com/czblse/SameUrl4Image
參考鏈接
  1. 百度百科
  2. iOS Download Image In The Same URL
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嚎朽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子柬帕,更是在濱河造成了極大的恐慌哟忍,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陷寝,死亡現(xiàn)場(chǎng)離奇詭異锅很,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)凤跑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門爆安,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人饶火,你說我怎么就攤上這事鹏控。” “怎么了肤寝?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵当辐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我鲤看,道長(zhǎng)缘揪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任义桂,我火速辦了婚禮找筝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘慷吊。我一直安慰自己袖裕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布溉瓶。 她就那樣靜靜地躺著急鳄,像睡著了一般谤民。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上疾宏,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天张足,我揣著相機(jī)與錄音,去河邊找鬼坎藐。 笑死为牍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的岩馍。 我是一名探鬼主播碉咆,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼兼雄!你這毒婦竟也來(lái)了吟逝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赦肋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后励稳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體佃乘,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年驹尼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了趣避。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡新翎,死狀恐怖程帕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情地啰,我是刑警寧澤愁拭,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站亏吝,受9級(jí)特大地震影響岭埠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔚鸥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一惜论、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧止喷,春花似錦馆类、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)技羔。三九已至,卻和暖如春卧抗,著一層夾襖步出監(jiān)牢的瞬間藤滥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工社裆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拙绊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓泳秀,卻偏偏與公主長(zhǎng)得像标沪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嗜傅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容