前言
android中圖片加載框架有很多,所有框架最終達(dá)到的目都是在Android平臺(tái)上以極度簡(jiǎn)單的方式加載和展示圖片希停,如果我們每個(gè)都拿來(lái)學(xué)習(xí)和使用的話,這樣會(huì)極大的浪費(fèi)我們的時(shí)間和經(jīng)歷裕寨,所以經(jīng)過(guò)我的一番比較之后试和,我決定用Glide作為我研究和今后使用的對(duì)象。經(jīng)過(guò)一個(gè)禮拜的學(xué)習(xí)和研究之后匾荆,我發(fā)現(xiàn)Glide是真的強(qiáng)大构罗,通過(guò)簡(jiǎn)單的一行代碼就可以展示圖片事甜,但是其背后所做的是事卻是我們沒(méi)法想象的,想深入去學(xué)習(xí)源碼的可以去看下郭神的Glide最全解析專欄。本人由于精力和實(shí)力的不足,源碼就不解析了鼠次,郭神的那個(gè)系列也寫(xiě)的很詳細(xì)了惠拭,自己看了差不多一個(gè)禮拜秀鞭,可能還是有很多不懂的地方,這里說(shuō)下看的方法绿饵,源碼看起來(lái)肯定很枯燥,一定要有耐心乳绕,不能急,不然可能就看不下去了逼纸,一遍看不懂可以多看幾遍洋措,來(lái)回看慢慢看就會(huì)有感覺(jué)的。郭神的那個(gè)系列第二篇是Glide源碼的整個(gè)流程杰刽,特別長(zhǎng)菠发,我的建議可以先跳過(guò)去王滤,先看后面幾篇關(guān)于源碼的細(xì)節(jié)部分,看完再回來(lái)看整個(gè)流程雷酪,這樣會(huì)好一點(diǎn)淑仆。不然第二篇不僅量大而且亂很容易走神,這樣一來(lái)很可能就看不下去直接放棄了哥力,后面幾篇先過(guò)下留個(gè)大致印象再回過(guò)來(lái)看整體流程這樣會(huì)容易一點(diǎn)蔗怠。還有一個(gè)就是可能有人會(huì)覺(jué)得我會(huì)用不就好了,我一個(gè)開(kāi)發(fā)人員我沒(méi)事看懂框架源碼干嘛吩跋,我又不需要寫(xiě)源碼寞射,我要會(huì)寫(xiě)我還用這個(gè)框架干嘛。這個(gè)首先你看了可以增長(zhǎng)你技術(shù)方面的見(jiàn)識(shí)锌钮,雖然可能能力增長(zhǎng)的也不多桥温,但是最起碼開(kāi)開(kāi)眼界。其次看懂了源碼如果在使用框架過(guò)程中有些方面不符合需求梁丘,你可以快速在源碼的基礎(chǔ)上做修改來(lái)滿足我們的需求侵浸。最后還有一點(diǎn)就是看了源碼之后可以更好的掌握這個(gè)框架的用法,了解的更深氛谜,對(duì)其內(nèi)部的實(shí)現(xiàn)最起碼有個(gè)大概的了解掏觉,不僅知其然,還知其所以然值漫。好了澳腹,前面的話就扯這么多了,在本篇博客中只對(duì)Glide(基于Glide v4)用法做個(gè)總結(jié)杨何,盡量把所有用法總結(jié)全酱塔,若有沒(méi)涉及到的歡迎評(píng)論補(bǔ)充,不勝感激危虱。
進(jìn)入正題
1. 添加依賴
注意:這里我們要添加兩個(gè)依賴庫(kù)羊娃,其中compiler庫(kù)是用于生成Generated API的,后面我們會(huì)講到它埃跷。
dependencies {
implementation 'com.github.bumptech.glide:glide:4.8.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
}
2. 下載圖片需要網(wǎng)絡(luò)權(quán)限迁沫,所以在AndroidManifest.xml中聲明一下網(wǎng)絡(luò)權(quán)限
<uses-permission android:name="android.permission.INTERNET" />
3. 加載圖片
3.1 首先來(lái)嘗試加載一張網(wǎng)絡(luò)圖片吧,圖片地址:http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg
3.2 布局文件:很簡(jiǎn)單就一個(gè)Button和一個(gè)用于展示的ImageView捌蚊,點(diǎn)擊Button之后就去加載該張圖片集畅,給按鈕添加了個(gè)點(diǎn)擊的方法doClick。布局用的是ConstraintLayout缅糟,不懂的自己可以去學(xué)習(xí)下挺智,之前我的博客android布局和屏幕適配相關(guān)里面也鏈接了兩個(gè)大神關(guān)于這個(gè)布局的博客,我都是直接拖拽出來(lái)的窗宦。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="加載圖片"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
</android.support.constraint.ConstraintLayout>
3.3 為了點(diǎn)擊Button的時(shí)候能夠?qū)偛诺膱D片顯示在ImageView上赦颇,需要修改MainActivity中的代碼二鳄,如下所示:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
}
public void doClick(View view) {
String url = "http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg";
Glide.with(this).load(url).into(imageView);
}
}
3.4 就一行代碼就搞定了,就是這么簡(jiǎn)單媒怯,我們來(lái)運(yùn)行下程序订讼,運(yùn)行效果如下圖所示:
可以看到,一張網(wǎng)絡(luò)上的圖片已經(jīng)被成功下載扇苞,并且展示到ImageView上了欺殿。而我們到底做了什么?實(shí)際上核心的代碼就只有這一行而已:
Glide.with(this).load(url).into(imageView);
千萬(wàn)不要小看這一行代碼鳖敷,實(shí)際上僅僅就這一行代碼脖苏,你已經(jīng)可以做非常非常多的事情了,包括加載網(wǎng)絡(luò)上的圖片定踱、加載手機(jī)本地的圖片棍潘、加載應(yīng)用資源中的圖片等等。
3.5 下面我們就來(lái)詳細(xì)解析一下這行代碼崖媚。
- 首先亦歉,調(diào)用Glide.with()方法用于創(chuàng)建一個(gè)加載圖片的實(shí)例。with()方法可以接收Context畅哑、Activity或者Fragment類型的參數(shù)肴楷。也就是說(shuō)我們選擇的范圍非常廣,不管是在Activity還是Fragment中調(diào)用with()方法敢课,都可以直接傳this。那如果調(diào)用的地方既不在Activity中也不在Fragment中呢绷杜?也沒(méi)關(guān)系直秆,我們可以獲取當(dāng)前應(yīng)用程序的ApplicationContext,傳入到with()方法當(dāng)中鞭盟。注意with()方法中傳入的實(shí)例會(huì)決定Glide加載圖片的生命周期圾结,如果傳入的是Activity或者Fragment的實(shí)例,那么當(dāng)這個(gè)Activity或Fragment被銷毀的時(shí)候齿诉,圖片加載也會(huì)停止筝野。如果傳入的是ApplicationContext,那么只有當(dāng)應(yīng)用程序被殺掉的時(shí)候粤剧,圖片加載才會(huì)停止歇竟。
- 接下來(lái)看一下load()方法,這個(gè)方法用于指定待加載的圖片資源抵恋。Glide支持加載各種各樣的圖片資源焕议,包括網(wǎng)絡(luò)圖片、本地圖片弧关、應(yīng)用資源盅安、二進(jìn)制流唤锉、Uri對(duì)象等等。因此load()方法也有很多個(gè)方法重載别瞭,除了我們剛才使用的加載一個(gè)字符串網(wǎng)址之外窿祥,你還可以這樣使用load()方法:
// 加載本地圖片
File file = new File(getExternalCacheDir() + "/image.jpg");
Glide.with(this).load(file).into(imageView);
// 加載應(yīng)用資源
int resource = R.drawable.image;
Glide.with(this).load(resource).into(imageView);
// 加載二進(jìn)制流
byte[] image = getImageBytes();
Glide.with(this).load(image).into(imageView);
// 加載Uri對(duì)象
Uri imageUri = getImageUri();
Glide.with(this).load(imageUri).into(imageView);
- 最后看一下into()方法,這個(gè)方法就很簡(jiǎn)單了蝙寨,我們希望讓圖片顯示在哪個(gè)ImageView上晒衩,把這個(gè)ImageView的實(shí)例傳進(jìn)去就可以了。當(dāng)然籽慢,into()方法不僅僅是只能接收ImageView類型的參數(shù)浸遗,還支持很多更豐富的用法,不過(guò)那個(gè)屬于高級(jí)技巧箱亿,我們后面再說(shuō)跛锌。
- 那么回顧一下Glide最基本的使用方式,其實(shí)就是關(guān)鍵的三步走:先with()届惋,再load()髓帽,最后into()。熟記這三步脑豹,你就已經(jīng)入門Glide了郑藏。
3. 6 設(shè)置加載動(dòng)畫(huà)
- 通常的用法如下,使用api提供的幾個(gè)常用動(dòng)畫(huà):
Glide.with(this)
.load(url)
//transition(TransitionOptions transitionOptions)
.transition(DrawableTransitionOptions.withCrossFade())
.into(imageView);
- TransitionOptions決定圖片加載完成如何從占位符圖片(或者之前的圖片)過(guò)渡瘩欺。
- 有三種TransitionOptions:
- GenericTransitionOptions 通用型
- DrawableTransitionOptions
- BitmapTransitionOptions
- TransitionOptions是和你要加載的資源的類型綁定的必盖,也就是說(shuō),如果你請(qǐng)求一張位圖Bitmap,你就需要使用BitmapTransitionOptions俱饿,而不是DrawableTransitionOptions歌粥。如果既不是Bitmap也不是Drawable就使用GenericTransitionOptions。Glide加載網(wǎng)絡(luò)圖片默認(rèn)的類型是Drawable拍埠,所以加載網(wǎng)絡(luò)圖片我們使用DrawableTransitionOptions即可失驶。
- 如果要使用自定義的動(dòng)畫(huà),可以使用GenericTransitionOptions.with(int viewAnimationId)或者BitmapTransitionOptions.withCrossFade(int animationId, int duration)或者DrawableTransitionOptions.withCrossFade(int animationId, int duration)枣购。
- 出于性能考慮嬉探,最好不要在ListView,GridView,RecycleView中使用過(guò)渡動(dòng)畫(huà),使用TransitionOptions.dontTransition()可以不加載動(dòng)畫(huà),也可以使用dontAnimate不加載動(dòng)畫(huà)棉圈。
RequestOptions options = new RequestOptions()
.dontTransform();
3. 7 設(shè)置下載優(yōu)先級(jí)
RequestOptions options = new RequestOptions()
.priority(Priority.NORMAL);
4. 占位圖
4.1 加載前占位圖
- 觀察剛才加載網(wǎng)絡(luò)圖片的效果涩堤,你會(huì)發(fā)現(xiàn),點(diǎn)擊了加載圖片按鈕之后分瘾,要稍微等一會(huì)圖片才會(huì)顯示出來(lái)定躏。這其實(shí)很容易理解,因?yàn)閺木W(wǎng)絡(luò)上下載圖片本來(lái)就是需要時(shí)間的。那么我們有沒(méi)有辦法再優(yōu)化一下用戶體驗(yàn)?zāi)厝叮慨?dāng)然可以垮抗,Glide提供了各種各樣非常豐富的API支持,其中就包括了占位圖功能碧聪。
- 顧名思義冒版,占位圖就是指在圖片的加載過(guò)程中,我們先顯示一張臨時(shí)的圖片逞姿,等圖片加載出來(lái)了再替換成要加載的圖片辞嗡。
- 下面我們就來(lái)學(xué)習(xí)一下Glide占位圖功能的使用方法,我用項(xiàng)目下的ic_launcher.png小機(jī)器人作為占位圖顯示滞造。然后修改Glide加載部分的代碼续室,如下所示:
RequestOptions options = new RequestOptions()
.placeholder(R.mipmap.ic_launcher);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
- 沒(méi)錯(cuò),就是這么簡(jiǎn)單谒养。這里我們先創(chuàng)建了一個(gè)RequestOptions對(duì)象挺狰,然后調(diào)用它的placeholder()方法來(lái)指定占位圖,再將占位圖片的資源id傳入到這個(gè)方法中买窟。最后丰泊,在Glide的三步走之間加入一個(gè)apply()方法,來(lái)應(yīng)用我們剛才創(chuàng)建的RequestOptions對(duì)象始绍。
- 不過(guò)如果你現(xiàn)在重新運(yùn)行一下代碼并點(diǎn)擊Load Image瞳购,很可能是根本看不到占位圖效果的。因?yàn)镚lide有非常強(qiáng)大的緩存機(jī)制亏推,我們剛才加載圖片的時(shí)候Glide自動(dòng)就已經(jīng)將它緩存下來(lái)了学赛,下次加載的時(shí)候?qū)?huì)直接從緩存中讀取,不會(huì)再去網(wǎng)絡(luò)下載了吞杭,因而加載的速度非痴到剑快,所以占位圖可能根本來(lái)不及顯示篇亭。
- 因此這里我們還需要稍微做一點(diǎn)修改缠捌,來(lái)讓占位圖能有機(jī)會(huì)顯示出來(lái)锄贷,修改代碼如下所示:
RequestOptions options = new RequestOptions()
.placeholder(R.mipmap.ic_launcher)
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
- 可以看到译蒂,這里在RequestOptions對(duì)象中又串接了一個(gè)diskCacheStrategy()方法,并傳入DiskCacheStrategy.NONE參數(shù)谊却,這樣就可以禁用掉Glide的緩存功能柔昼。
-關(guān)于Glide緩存方面的內(nèi)容我們待會(huì)兒會(huì)進(jìn)行更詳細(xì)的講解,這里只是為了測(cè)試占位圖功能而加的一個(gè)額外配置炎辨,暫時(shí)你只需要知道禁用緩存必須這么寫(xiě)就可以了捕透。 -
現(xiàn)在重新運(yùn)行一下代碼,效果如下圖所示:
- 可以看到,當(dāng)點(diǎn)擊加載圖片按鈕之后會(huì)立即顯示一張占位圖乙嘀,然后等真正的圖片加載完成之后會(huì)將占位圖替換掉末购。
4.2 異常占位圖
- 當(dāng)然,這只是占位圖的一種虎谢,除了這種加載占位圖之外盟榴,還有一種異常占位圖。異常占位圖就是指婴噩,如果因?yàn)槟承┊惓G闆r導(dǎo)致圖片加載失敗擎场,比如說(shuō)手機(jī)網(wǎng)絡(luò)信號(hào)不好,這個(gè)時(shí)候就顯示這張異常占位圖几莽。
- 異常占位圖的用法相信你已經(jīng)可以猜到了迅办,首先準(zhǔn)備一張error.jpg圖片,然后修改Glide加載部分的代碼章蚣,如下所示:
RequestOptions options = new RequestOptions()
.placeholder(R.mipmap.ic_launcher)
.error(R.drawable.error)
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
- 很簡(jiǎn)單站欺,這里又串接了一個(gè)error()方法就可以指定異常占位圖了。
-
現(xiàn)在你可以將圖片的url地址修改成一個(gè)不存在的圖片地址究驴,或者干脆直接將手機(jī)的網(wǎng)絡(luò)給關(guān)了镊绪,然后重新運(yùn)行程序,效果如下圖所示:
這樣我們就把Glide提供的占位圖功能都掌握了洒忧。
5. 指定圖片加載格式
- 我們都知道蝴韭,Glide其中一個(gè)非常亮眼的功能就是可以加載GIF圖片崇呵,而同樣作為非常出色的圖片加載框架的Picasso是不支持這個(gè)功能的撩炊。
- 而且使用Glide加載GIF圖并不需要編寫(xiě)什么額外的代碼熄驼,Glide內(nèi)部會(huì)自動(dòng)判斷圖片格式帐姻。我們將剛才那段加載圖片代碼中的URL地址替換成下面的GIF圖片的URL地址就可以了税朴。
現(xiàn)在重新運(yùn)行一下代碼撑碴,效果如下圖所示:
- 也就是說(shuō)允懂,不管我們傳入的是一張普通圖片空骚,還是一張GIF圖片巷送,Glide都會(huì)自動(dòng)進(jìn)行判斷驶忌,并且可以正確地把它解析并展示出來(lái)。
- 但是如果我想指定圖片的格式該怎么辦呢笑跛?就比如說(shuō)付魔,我希望加載的這張圖必須是一張靜態(tài)圖片,我不需要Glide自動(dòng)幫我判斷它到底是靜圖還是GIF圖飞蹂。
- 想實(shí)現(xiàn)這個(gè)功能仍然非常簡(jiǎn)單几苍,我們只需要再串接一個(gè)新的方法就可以了,代碼如下所示:
Glide.with(this)
.asBitmap()
.load(url)
.apply(options)
.into(imageView);
- 可以看到陈哑,這里在with()方法的后面加入了一個(gè)asBitmap()方法妻坝,這個(gè)方法的意思就是說(shuō)這里只允許加載靜態(tài)圖片伸眶,不需要Glide去幫我們自動(dòng)進(jìn)行圖片格式的判斷了。如果你傳入的還是一張GIF圖的話刽宪,Glide會(huì)展示這張GIF圖的第一幀厘贼,而不會(huì)去播放它。
- 這里注意asBitmap()方法必須跟在with()方法的后面圣拄,load()方法的前面涂臣,如果你寫(xiě)錯(cuò)了順序就肯定會(huì)報(bào)錯(cuò)了。
- 那么類似地售担,既然我們能強(qiáng)制指定加載靜態(tài)圖片赁遗,就也能強(qiáng)制指定加載動(dòng)態(tài)圖片,對(duì)應(yīng)的方法是asGif()族铆,代碼中只需用asGif()方法去替代asBitmap()方法就好了岩四。如果指定了只能加載動(dòng)態(tài)圖片,而傳入的圖片卻是一張靜圖的話哥攘,那么結(jié)果自然就只有加載失敗剖煌,如果指定了異常占位圖的話異常占位圖會(huì)展示出來(lái)。
- Glide 4中新增了asFile()方法和asDrawable()方法逝淹,分別用于強(qiáng)制指定文件格式的加載和Drawable格式的加載耕姊,用法都比較簡(jiǎn)單,就不再進(jìn)行演示了栅葡。
6. 指定圖片大小
- 在這之前茉兰,你可能還需要先了解一個(gè)概念,就是我們平時(shí)在加載圖片的時(shí)候很容易會(huì)造成內(nèi)存浪費(fèi)欣簇。什么叫內(nèi)存浪費(fèi)呢规脸?比如說(shuō)一張圖片的尺寸是1000 * 1000像素,但是我們界面上的ImageView可能只有200 * 200像素熊咽,這個(gè)時(shí)候如果你不對(duì)圖片進(jìn)行任何壓縮就直接讀取到內(nèi)存中莫鸭,這就屬于內(nèi)存浪費(fèi)了,因?yàn)槌绦蛑懈揪陀貌坏竭@么高像素的圖片横殴。
- 關(guān)于圖片壓縮這方面被因,我之前也有篇文章是這方面的,感興趣的朋友可以去閱讀一下Bitmap高效加載衫仑。
- 使用Glide在絕大多數(shù)情況下我們都是不需要指定圖片大小的梨与,完全不用擔(dān)心圖片內(nèi)存浪費(fèi),甚至是內(nèi)存溢出的問(wèn)題惑畴。因?yàn)镚lide從來(lái)都不會(huì)直接將圖片的完整尺寸全部加載到內(nèi)存中蛋欣,而是用多少加載多少航徙。Glide會(huì)自動(dòng)判斷ImageView的大小如贷,對(duì)圖片進(jìn)行壓縮陷虎,然后只將這么大的圖片像素加載到內(nèi)存當(dāng)中,幫助我們節(jié)省內(nèi)存開(kāi)支杠袱。
- 不過(guò)尚猿,如果你真的有這樣的需求,必須給圖片指定一個(gè)固定的大小楣富,Glide仍然是支持這個(gè)功能的凿掂。修改Glide加載部分的代碼,如下所示:
RequestOptions options = new RequestOptions()
.override(100, 100);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
- 仍然非常簡(jiǎn)單纹蝴,這里使用override()方法指定了一個(gè)圖片的尺寸庄萎。也就是說(shuō),Glide現(xiàn)在只會(huì)將圖片加載成100*100像素的尺寸塘安,而不會(huì)管你的ImageView的大小是多少了糠涛。
- 如果你想加載一張圖片的原始尺寸的話,可以使用Target.SIZE_ORIGINAL關(guān)鍵字兼犯,如下所示:
RequestOptions options = new RequestOptions()
override(Target.SIZE_ORIGINAL);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
- 這樣的話忍捡,Glide就不會(huì)再去自動(dòng)壓縮圖片,而是會(huì)去加載圖片的原始尺寸切黔。當(dāng)然砸脊,這種寫(xiě)法也會(huì)面臨著更高的OOM風(fēng)險(xiǎn)。
7. 緩存機(jī)制
- Glide的緩存設(shè)計(jì)可以說(shuō)是非常先進(jìn)的纬霞,考慮的場(chǎng)景也很周全凌埂。在緩存這一功能上,Glide又將它分成了兩個(gè)模塊诗芜,一個(gè)是內(nèi)存緩存侨舆,一個(gè)是硬盤緩存。
- 這兩個(gè)緩存模塊的作用各不相同绢陌,內(nèi)存緩存的主要作用是防止應(yīng)用重復(fù)將圖片數(shù)據(jù)讀取到內(nèi)存當(dāng)中挨下,而硬盤緩存的主要作用是防止應(yīng)用重復(fù)從網(wǎng)絡(luò)或其他地方重復(fù)下載和讀取數(shù)據(jù)。
- 內(nèi)存緩存和硬盤緩存的相互結(jié)合才構(gòu)成了Glide極佳的圖片緩存效果脐湾,那么接下來(lái)我們就來(lái)分別學(xué)習(xí)一下這兩種緩存的使用方法臭笆。
7.1 首先來(lái)看內(nèi)存緩存
- 你要知道,默認(rèn)情況下秤掌,Glide自動(dòng)就是開(kāi)啟內(nèi)存緩存的愁铺。也就是說(shuō),當(dāng)我們使用Glide加載了一張圖片之后闻鉴,這張圖片就會(huì)被緩存到內(nèi)存當(dāng)中茵乱,只要在它還沒(méi)從內(nèi)存中被清除之前,下次使用Glide再加載這張圖片都會(huì)直接從內(nèi)存當(dāng)中讀取孟岛,而不用重新從網(wǎng)絡(luò)或硬盤上讀取了瓶竭,這樣無(wú)疑就可以大幅度提升圖片的加載效率督勺。比方說(shuō)你在一個(gè)RecyclerView當(dāng)中反復(fù)上下滑動(dòng),RecyclerView中只要是Glide加載過(guò)的圖片都可以直接從內(nèi)存當(dāng)中迅速讀取并展示出來(lái)斤贰,從而大大提升了用戶體驗(yàn)智哀。
- 而Glide最為人性化的是,你甚至不需要編寫(xiě)任何額外的代碼就能自動(dòng)享受到這個(gè)極為便利的內(nèi)存緩存功能荧恍,因?yàn)镚lide默認(rèn)就已經(jīng)將它開(kāi)啟了瓷叫。
- 那么既然已經(jīng)默認(rèn)開(kāi)啟了這個(gè)功能,還有什么可講的用法呢送巡?只有一點(diǎn)摹菠,如果你有什么特殊的原因需要禁用內(nèi)存緩存功能,Glide對(duì)此提供了接口:
RequestOptions options = new RequestOptions()
.skipMemoryCache(true);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
- 可以看到骗爆,只需要調(diào)用skipMemoryCache()方法并傳入true辨嗽,就表示禁用掉Glide的內(nèi)存緩存功能。
7.2 接下來(lái)我們開(kāi)始學(xué)習(xí)硬盤緩存方面的內(nèi)容
- 磁盤緩存Glide默認(rèn)也是開(kāi)啟的,其實(shí)在剛剛學(xué)習(xí)占位圖功能的時(shí)候淮腾,我們就使用過(guò)硬盤緩存的功能了糟需。當(dāng)時(shí)為了禁止Glide對(duì)圖片進(jìn)行硬盤緩存而使用了如下代碼:
RequestOptions options = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
- 調(diào)用diskCacheStrategy()方法并傳入DiskCacheStrategy.NONE,就可以禁用掉Glide的硬盤緩存功能了谷朝。
- 這個(gè)diskCacheStrategy()方法基本上就是Glide硬盤緩存功能的一切洲押,它可以接收五種參數(shù):
- DiskCacheStrategy.NONE: 表示不緩存任何內(nèi)容。
- DiskCacheStrategy.DATA: 表示只緩存原始圖片圆凰。
- DiskCacheStrategy.RESOURCE: 表示只緩存轉(zhuǎn)換過(guò)后的圖片杈帐。
- DiskCacheStrategy.ALL : 表示既緩存原始圖片,也緩存轉(zhuǎn)換過(guò)后的圖片专钉。
- DiskCacheStrategy.AUTOMATIC: 表示讓Glide根據(jù)圖片資源智能地選擇使用哪一種緩存策略(默認(rèn)選項(xiàng))挑童。
- 上面五種參數(shù)的解釋本身并沒(méi)有什么難理解的地方,但是關(guān)于轉(zhuǎn)換過(guò)后的圖片這個(gè)概念大家可能需要了解一下跃须。就是當(dāng)我們使用Glide去加載一張圖片的時(shí)候站叼,Glide默認(rèn)并不會(huì)將原始圖片展示出來(lái),而是會(huì)對(duì)圖片進(jìn)行壓縮和變換(我們會(huì)在稍后學(xué)習(xí)這方面的內(nèi)容)菇民【⌒ǎ總之就是經(jīng)過(guò)種種一系列操作之后得到的圖片,就叫轉(zhuǎn)換過(guò)后的圖片第练。
7.3 清理緩存阔馋,注意清理磁盤緩存因?yàn)樯婕暗轿募僮鞑荒芊胖骶€程中
//清理內(nèi)存緩存 可以在UI主線程中進(jìn)行
Glide.get(this).clearMemory();
//清理磁盤緩存 需要在子線程中執(zhí)行
new Thread(new Runnable() {
@Override
public void run() {
Glide.get(MainActivity.this).clearDiskCache();
}
}).start();
7.4 讀取磁盤緩存數(shù)據(jù)大小
- 有些APP里面比如新聞?lì)怉PP里面會(huì)有手動(dòng)清理緩存功能,然后會(huì)把緩存數(shù)據(jù)大小顯示出來(lái)娇掏,這個(gè)其實(shí)就是磁盤緩存數(shù)據(jù)大小呕寝,那么這個(gè)我們要如何實(shí)現(xiàn)呢,其實(shí)很簡(jiǎn)單婴梧,只要讀取出Glide磁盤緩存文件下所有文件大小然后把算出所有數(shù)據(jù)大小之和就好了下梢。
- 這里創(chuàng)建了個(gè)Glide磁盤緩存數(shù)據(jù)的工具類客蹋,使用時(shí)只需要調(diào)用獲取磁盤緩存大小的方法并傳入Context就可獲取到Glide磁盤緩存數(shù)據(jù)大小了,代碼如下:
import android.content.Context;
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
import java.io.File;
import java.math.BigDecimal;
public class GlideDiskCacheUtil {
/**
* 獲取Glide造成的磁盤緩存大小
* @return DiskCacheSize
*/
public static String getDiskCacheSize(Context context) {
try {
return getFormatSize(getFolderSize(new File(context.getCacheDir() + "/"+ InternalCacheDiskCacheFactory.DEFAULT_DISK_CACHE_DIR)));
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 獲取指定文件夾內(nèi)所有文件大小的和
* @param file file
* @return size
* @throws Exception
*/
private static long getFolderSize(File file) throws Exception {
long size = 0;
try {
File[] fileList = file.listFiles();
for (File aFileList : fileList) {
if (aFileList.isDirectory()) {
size = size + getFolderSize(aFileList);
} else {
size = size + aFileList.length();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return size;
}
/**
* 格式化單位
* @param size size
* @return size
*/
private static String getFormatSize(double size) {
double kiloByte = size / 1024;
if (kiloByte < 1) {
return size + "Byte";
}
double megaByte = kiloByte / 1024;
if (megaByte < 1) {
BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB";
}
double gigaByte = megaByte / 1024;
if (gigaByte < 1) {
BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB";
}
double teraBytes = gigaByte / 1024;
if (teraBytes < 1) {
BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB";
}
BigDecimal result4 = new BigDecimal(teraBytes);
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB";
}
}
7.5 高級(jí)技巧(圖片的url中帶用于保護(hù)圖片資源的變化的token)
- 雖說(shuō)Glide將緩存功能高度封裝之后怔球,使得用法變得非常簡(jiǎn)單,但同時(shí)也帶來(lái)了一些問(wèn)題浮还。
- 比如當(dāng)項(xiàng)目的圖片資源是存放在七牛云上面的竟坛,而七牛云為了對(duì)圖片資源進(jìn)行保護(hù),會(huì)在圖片url地址的基礎(chǔ)之上再加上一個(gè)token參數(shù)钧舌。也就是說(shuō)担汤,一張圖片的url地址可能會(huì)是如下格式:
- 而使用Glide加載這張圖片的話,也就會(huì)使用這個(gè)url地址來(lái)組成緩存Key洼冻。
- 但是接下來(lái)問(wèn)題就來(lái)了崭歧,token作為一個(gè)驗(yàn)證身份的參數(shù)并不是一成不變的,很有可能時(shí)時(shí)刻刻都在變化撞牢。而如果token變了率碾,那么圖片的url也就跟著變了,圖片url變了屋彪,緩存Key也就跟著變了所宰。結(jié)果就造成了,明明是同一張圖片畜挥,就因?yàn)閠oken不斷在改變仔粥,導(dǎo)致Glide的緩存功能完全失效了。
- 這其實(shí)是個(gè)挺棘手的問(wèn)題蟹但,大家在使用Glide的時(shí)候很有可能都會(huì)遇到這個(gè)問(wèn)題躯泰。那么該如何解決這個(gè)問(wèn)題呢?這個(gè)時(shí)候就體現(xiàn)出了研究源碼的作用了华糖,通過(guò)研究源碼我們可以知道緩存Key是從GlideUrl類中的getCacheKey()方法將圖片的url地址直接進(jìn)行返回得到的麦向,因此我們只需要?jiǎng)?chuàng)建一個(gè)MyGlideUrl繼承自GlideUrl,在getCacheKey()方法方法中加入一些自己的處理邏輯把url中token這部分參數(shù)去掉然后在Glide加載圖片時(shí)使用我們創(chuàng)建的MyGlideUrl傳入url就可以了客叉,代碼如下所示:
import com.bumptech.glide.load.model.GlideUrl;
public class MyGlideUrl extends GlideUrl {
private String mUrl;
public MyGlideUrl(String url) {
super(url);
}
@Override
public String getCacheKey() {
//將token部分參數(shù)替換為空字符串后返回作為緩存Key
return mUrl.replace(findTokenParam(), "");
}
/**
* 查找token部分參數(shù)的方法
* @return token部分參數(shù)String
*/
private String findTokenParam() {
String tokenParam = "";
int tokenKeyIndex = mUrl.indexOf("?token=") >= 0 ? mUrl.indexOf("?token=") : mUrl.indexOf("&token=");
if (tokenKeyIndex != -1) {
int nextAndIndex = mUrl.indexOf("&", tokenKeyIndex + 1);
if (nextAndIndex != -1) {
tokenParam = mUrl.substring(tokenKeyIndex + 1, nextAndIndex + 1);
} else {
tokenParam = mUrl.substring(tokenKeyIndex);
}
}
return tokenParam;
}
}
- 使用我們創(chuàng)建的MyGlideUrl傳入url代碼:
Glide.with(this)
.load(new MyGlideUrl(url))
.into(imageView);
8. 回調(diào)與監(jiān)聽(tīng)
8.1 into()方法
- 我們都知道Glide的into()方法中是可以傳入ImageView的磕蛇。那么into()方法還可以傳入別的參數(shù)嗎?我們可以讓Glide加載出來(lái)的圖片不顯示到ImageView上嗎十办?答案是肯定的秀撇,這就需要用到自定義Target功能,如果我們要進(jìn)行自定義的話向族,通常只需要在兩種Target的基礎(chǔ)上去自定義就可以了呵燕,一種是SimpleTarget,一種是ViewTarget件相。
SimpleTarget再扭,它是一種極為簡(jiǎn)單的Target氧苍,我們使用它可以將Glide加載出來(lái)的圖片對(duì)象 resource獲取到,而不是像之前那樣只能將圖片在ImageView上顯示出來(lái)泛范,雖然我們下面實(shí)現(xiàn)的也是將圖片在ImageView上顯示让虐,但是我們能拿到圖片對(duì)象resource,有了這個(gè)對(duì)象之后就可以使用它進(jìn)行任意的邏輯操作了罢荡。
SimpleTarget<Drawable> simpleTarget = new SimpleTarget<Drawable>() {
@Override
public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {
imageView.setImageDrawable(resource);
}
};
public void loadImage(View view) {
Glide.with(this)
.load("https://www.baidu.com/img/bd_logo1.png")
.into(simpleTarget);
}
ViewTarget要復(fù)雜一點(diǎn)赡突,這里我們通過(guò)一個(gè)例子來(lái)演示一下吧,比如我創(chuàng)建了一個(gè)自定義布局MyLayout区赵,代碼如下所示:
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import com.bumptech.glide.request.target.ViewTarget;
import com.bumptech.glide.request.transition.Transition;
public class MyLayout extends LinearLayout{
private ViewTarget<MyLayout,Drawable> viewTarget;
public MyLayout(Context context) {
this(context,null);
}
public MyLayout(Context context, @Nullable AttributeSet attrs) {
this(context,attrs,0);
}
public MyLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
viewTarget = new ViewTarget<MyLayout,Drawable>(this) {
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
MyLayout myLayout = getView();
myLayout.setBackgroundDrawable(resource);
}
};
}
public ViewTarget<MyLayout,Drawable> getTarget() {
return viewTarget;
}
}
- 在MyLayout的構(gòu)造函數(shù)中惭缰,我們創(chuàng)建了一個(gè)ViewTarget的實(shí)例,并將Mylayout當(dāng)前的實(shí)例this傳了進(jìn)去笼才。ViewTarget中需要指定兩個(gè)泛型漱受,一個(gè)是View的類型,一個(gè)圖片的類型(Drawable或Bitmap)骡送。然后在onResourceReady()方法中昂羡,我們就可以通過(guò)getView()方法獲取到MyLayout的實(shí)例,并調(diào)用它的任意方法了摔踱。比如說(shuō)這里我們調(diào)用了setBackgroundDrawable()方法來(lái)將加載出來(lái)的圖片作為MyLayout布局的背景圖紧憾。
- 接下來(lái)看一下怎么使用這個(gè)Target吧,由于MyLayout中已經(jīng)提供了getTarget()接口昌渤,我們只需要在加載圖片的地方這樣寫(xiě)就可以了:
public class MainActivity extends AppCompatActivity {
private MyLayout myLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myLayout = (MyLayout) findViewById(R.id.background);
}
public void loadImage(View view) {
String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
Glide.with(this)
.load(url)
.into(myLayout.getTarget());
}
}
- 就是這么簡(jiǎn)單赴穗,在into()方法中傳入myLayout.getTarget()即可。
8.2 preload()方法
- Glide加載圖片雖說(shuō)非常智能膀息,它會(huì)自動(dòng)判斷該圖片是否已經(jīng)有緩存了般眉,如果有的話就直接從緩存中讀取,沒(méi)有的話再?gòu)木W(wǎng)絡(luò)去下載潜支。但是如果我希望提前對(duì)圖片進(jìn)行一個(gè)預(yù)加載甸赃,等真正需要加載圖片的時(shí)候就直接從緩存中讀取,不想再等待慢長(zhǎng)的網(wǎng)絡(luò)加載時(shí)間了冗酿,這該怎么辦呢埠对?
- 不用擔(dān)心,Glide專門給我們提供了預(yù)加載的接口裁替,也就是preload()方法项玛,我們只需要直接使用就可以了。
- preload()方法有兩個(gè)方法重載弱判,一個(gè)不帶參數(shù)襟沮,表示將會(huì)加載圖片的原始尺寸,另一個(gè)可以通過(guò)參數(shù)指定加載圖片的寬和高。
- 這里有一點(diǎn)值得關(guān)注的是在Glide v3時(shí)我們?nèi)绻褂昧藀reload()方法开伏,要將diskCacheStrategy的緩存策略指定成加載原始圖片尺寸大小膀跌。因?yàn)閜reload()方法默認(rèn)是預(yù)加載的原始圖片大小,而into()方法則默認(rèn)會(huì)根據(jù)ImageView控件的大小來(lái)動(dòng)態(tài)決定加載圖片的大小固灵,所以同樣into()方法也要將diskCacheStrategy的緩存策略指定成加載原始圖片尺寸大小捅伤。否則,如果兩者(預(yù)加載和顯示)diskCacheStrategy的緩存策略不一致的話巫玻,很容易會(huì)造成我們?cè)陬A(yù)加載完成之后再使用into()方法加載圖片丛忆,卻仍然還是要從網(wǎng)絡(luò)上去請(qǐng)求圖片這種現(xiàn)象姜骡。但是到了Glide v4就不需要指定磁盤緩存策略圈澈,因?yàn)镚lide v4的默認(rèn)磁盤緩存策略是DiskCacheStrategy.AUTOMATIC,Glide會(huì)根據(jù)圖片資源智能地選擇使用哪一種緩存策略康栈,智能幫我們把這些工作做了悬荣,所以我們就不需要指定了驰坊。
- preload()方法的用法也非常簡(jiǎn)單痒钝,直接使用它來(lái)替換into()方法即可爱沟,如下所示:
Glide.with(this)
.load("https://www.baidu.com/img/bd_logo1.png")
.preload();
- 調(diào)用了預(yù)加載之后摸袁,我們以后想再去加載這張圖片就會(huì)非吃客纾快了,因?yàn)镚lide會(huì)直接從緩存當(dāng)中去讀取圖片并顯示出來(lái)靠汁,代碼如下所示:
Glide.with(this)
.load("https://www.baidu.com/img/bd_logo1.png")
.into(imageView);
8.3 submit()方法
- 一直以來(lái)蜂大,我們使用Glide都是為了將圖片顯示到界面上。雖然我們知道Glide會(huì)在圖片的加載過(guò)程中對(duì)圖片進(jìn)行緩存蝶怔,但是緩存文件到底是存在哪里的奶浦,以及如何去直接訪問(wèn)這些緩存文件?我們都還不知道踢星。
- 其實(shí)Glide將圖片加載接口設(shè)計(jì)成這樣也是希望我們使用起來(lái)更加的方便澳叉,不用過(guò)多去考慮底層的實(shí)現(xiàn)細(xì)節(jié)。但如果我現(xiàn)在就是想要去訪問(wèn)圖片的緩存文件該怎么辦呢?這就需要用到submit()方法了成洗。
- 和preload()方法類似五督,submit()方法也是可以替換into()方法的,不過(guò)submit()方法的用法明顯要比preload()方法復(fù)雜不少瓶殃。這個(gè)方法只會(huì)下載圖片充包,而不會(huì)對(duì)圖片進(jìn)行加載。當(dāng)圖片下載完成之后遥椿,我們可以得到圖片的存儲(chǔ)路徑基矮,以便后續(xù)進(jìn)行操作。
- 那么首先我們還是先來(lái)看下基本用法冠场。submit()方法有兩個(gè)方法重載:
- submit():下載原始尺寸的圖片家浇。
- submit(int width, int height):下載指定尺寸的圖片。
- 這里就以submit()方法來(lái)舉例碴裙。當(dāng)調(diào)用了submit()方法后會(huì)立即返回一個(gè)FutureTarget對(duì)象钢悲,然后Glide會(huì)在后臺(tái)開(kāi)始下載圖片文件。接下來(lái)我們調(diào)用FutureTarget的get()方法就可以去獲取下載好的圖片文件了青团,如果此時(shí)圖片還沒(méi)有下載完譬巫,那么get()方法就會(huì)阻塞住咖楣,一直等到圖片下載完成才會(huì)有值返回督笆,所以get()方法必須在子線程中執(zhí)行。
- 下面我們通過(guò)一個(gè)例子來(lái)演示一下吧诱贿,代碼如下所示:
private void downloadImage() {
new Thread(new Runnable() {
@Override
public void run() {
try {
String url = "http://p1.pstatp.com/large/166200019850062839d3";
final Context context = getApplicationContext();
FutureTarget<File> target = Glide.with(context)
.asFile()
.load(url)
.submit();
final File imageFile = target.get();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, imageFile.getPath(), Toast.LENGTH_LONG).show();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
- 這段代碼稍微有一點(diǎn)點(diǎn)長(zhǎng)娃肿,我?guī)е蠹医庾x一下。首先珠十,submit()方法必須要用在子線程當(dāng)中料扰,因?yàn)閯偛耪f(shuō)了FutureTarget的get()方法是會(huì)阻塞線程的,因此這里的第一步就是new了一個(gè)Thread焙蹭。在子線程當(dāng)中晒杈,我們先獲取了一個(gè)Application Context,這個(gè)時(shí)候不能再用Activity作為Context了孔厉,因?yàn)闀?huì)有Activity銷毀了但子線程下載還沒(méi)執(zhí)行完這種可能出現(xiàn)拯钻。
- 接下來(lái)就是Glide的基本用法,只不過(guò)將into()方法替換成了submit()方法撰豺,并且還使用了一個(gè)asFile()方法來(lái)指定加載格式粪般。submit()方法會(huì)返回一個(gè)FutureTarget對(duì)象,這個(gè)時(shí)候其實(shí)Glide已經(jīng)開(kāi)始在后臺(tái)下載圖片了污桦,我們隨時(shí)都可以調(diào)用FutureTarget的get()方法來(lái)獲取下載的圖片文件亩歹,只不過(guò)如果圖片還沒(méi)下載好線程會(huì)暫時(shí)阻塞住,等下載完成了才會(huì)把圖片的File對(duì)象返回。
- 最后小作,我們使用runOnUiThread()切回到主線程亭姥,然后使用Toast將下載好的圖片文件路徑顯示出來(lái)。
8.4 下載圖片并保存到指定路徑
submit方法可以下載圖片并獲取到圖片緩存路徑路徑顾稀,但是不知道大家有沒(méi)有這樣的需要就是想讓圖片下載到指定的路徑致份,因?yàn)檫@樣我們之后快速使用這部分圖片,也方便對(duì)這部分圖片進(jìn)行管理础拨,同時(shí)不需要受限于Glide的磁盤緩存機(jī)制氮块,因?yàn)槿绻蒅lide自動(dòng)管理緩存的話,當(dāng)下載的圖片超過(guò)設(shè)定的緩存大小诡宗,一些比如長(zhǎng)時(shí)間不使用的圖片就會(huì)被Glide刪除滔蝉,但是其實(shí)這張圖片我們之后還是需要使用的,這就很尷尬了塔沃,所以我們需要把圖片下載到我們指定的位置蝠引,由我們自己來(lái)進(jìn)行管理。代碼如下:
- 添加讀寫(xiě)數(shù)據(jù)權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- 編寫(xiě)下載圖片線程類
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.Target;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class DownloadImage implements Runnable{
//要下載圖片的url
private String url;
//Glide下載所需的Context蛀柴,最好用ApplicationContext螃概,
//如果再用Activity作為Context了,可能會(huì)有Activity銷毀了但子線程下載還沒(méi)執(zhí)行完這種情況出現(xiàn)鸽疾。
private Context context;
//指定下載寬度树姨,如果想下載原始寬帶指定為0
private int width;
//指定下載高度,如果想下載原始高帶指定為0
private int height;
//指定下載位置
private File mFile;
//下載完之后的回調(diào)
private ImagedownLoadCallBack callBack;
public interface ImagedownLoadCallBack{
void onDownLoadSuccess(Bitmap bitmap);
void onDownLoadFailed();
}
//用于回調(diào)到主線程的Handler尺栖,便于在回調(diào)回去的方法中執(zhí)行UI操作
private Handler mHandler;
public DownloadImage(String url, Context context, int width, int height, File mFile, ImagedownLoadCallBack callBack) {
this.url = url;
this.context = context;
this.width = width;
this.height = height;
this.mFile = mFile;
this.callBack = callBack;
mHandler = new Handler(Looper.getMainLooper());
}
@Override
public void run() {
Bitmap bitmap = null;
FileOutputStream fos = null;
try {
if (width==0){
width = Target.SIZE_ORIGINAL;
}
if (height==0){
height = Target.SIZE_ORIGINAL;
}
bitmap = Glide.with(context)
.asBitmap()
.load(url)
.submit(width,height)
.get();
if (bitmap != null){
//上級(jí)文件夾不存在則創(chuàng)建
if (!mFile.getParentFile().exists()){
mFile.getParentFile().mkdirs();
}
//文件不存在則創(chuàng)建
if (!mFile.exists()){
mFile.createNewFile();
}
fos = new FileOutputStream(mFile);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bitmap != null && mFile.exists()) {
final Bitmap finalBitmap = bitmap;
mHandler.post(new Runnable() {
@Override
public void run() {
callBack.onDownLoadSuccess(finalBitmap);
}
});
} else {
mHandler.post(new Runnable() {
@Override
public void run() {
callBack.onDownLoadFailed();
}
});
}
}
}
}
- 在按鈕點(diǎn)擊方法中開(kāi)啟線程調(diào)用DownloadImage去下載圖片到指定路徑中
public void doClick(View view) {
String url = "http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg";
File mFile = new File(Environment.getExternalStorageDirectory()+File.separator+"Glide","glideDownload.png");
DownloadImage downloadImage = new DownloadImage(url, getApplicationContext(), 600, 600, mFile, new DownloadImage.ImagedownLoadCallBack() {
@Override
public void onDownLoadSuccess(Bitmap bitmap) {
Toast.makeText(MainActivity.this, "下載完成", Toast.LENGTH_SHORT).show();
}
@Override
public void onDownLoadFailed() {
Toast.makeText(MainActivity.this, "下載失敗", Toast.LENGTH_SHORT).show();
}
});
new Thread(downloadImage).start();
}
注意:這里submit(width,height)方法指定寬高后下載下來(lái)的圖片尺寸并不是完全按我們指定尺寸來(lái)的碳胳,Glide是會(huì)做處理的,會(huì)保留原始尺寸的寬高比豺鼻,以縮小比例較小邊取我們指定的值的大小综液。比如上面我們下載的那張圖片原始尺寸是1920 * 1080,我們指定尺寸為600 * 600儒飒,最后下載下來(lái)的圖片尺寸是1067 * 600(寬是縮小比例較小邊(1080/600<1920/600)取指定值600谬莹,長(zhǎng)則按長(zhǎng)寬比不變進(jìn)行計(jì)算求得,計(jì)算過(guò)程:長(zhǎng)=600 * (1920/1080)=1067)桩了。除此之外我還試了下override()方法和preload()方法指定尺寸Glide是否也做了同樣的處理附帽,結(jié)果是是的。其實(shí)這如果源碼讀的很細(xì)應(yīng)該是可以看出來(lái)的圣猎,不過(guò)這也確實(shí)有點(diǎn)太細(xì)節(jié)了士葫,畢竟源碼篇幅還是很多的,這種細(xì)節(jié)還是挺難注意到的送悔,如果有精力可以找這部分去精讀下慢显,理解會(huì)更深爪模。
8.5 listener()方法
- 其實(shí)listener()方法的作用非常普遍,它可以用來(lái)監(jiān)聽(tīng)Glide加載圖片的狀態(tài)荚藻。舉個(gè)例子屋灌,比如說(shuō)我們剛才使用了preload()方法來(lái)對(duì)圖片進(jìn)行預(yù)加載,但是我怎樣確定預(yù)加載有沒(méi)有完成呢应狱?還有如果Glide加載圖片失敗了共郭,我該怎樣調(diào)試錯(cuò)誤的原因呢?答案都在listener()方法當(dāng)中疾呻。
- 下面來(lái)看下listener()方法的基本用法吧除嘹,不同于剛才幾個(gè)方法都是要替換into()方法的,listener()是結(jié)合into()方法一起使用的岸蜗,當(dāng)然也可以結(jié)合preload()方法一起使用尉咕。最基本的用法如下所示:
Glide.with(this)
.load("http://cn.bing.com/az/hprichbg/rb/Dongdaemun_ZH-CN10736487148_1920x1080.jpg")
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
})
.into(imageView);
- 這里我們?cè)趇nto()方法之前串接了一個(gè)listener()方法,然后實(shí)現(xiàn)了一個(gè)RequestListener的實(shí)例璃岳。其中RequestListener需要實(shí)現(xiàn)兩個(gè)方法年缎,一個(gè)onResourceReady()方法,一個(gè)onLoadFailed()方法铃慷。從方法名上就可以看出來(lái)了单芜,當(dāng)圖片加載完成的時(shí)候就會(huì)回調(diào)onResourceReady()方法,而當(dāng)圖片加載失敗的時(shí)候就會(huì)回調(diào)onLoadFailed()方法犁柜,onLoadFailed()方法中會(huì)將失敗的GlideException參數(shù)傳進(jìn)來(lái)洲鸠,這樣我們就可以定位具體失敗的原因了。
- 沒(méi)錯(cuò)赁温,listener()方法就是這么簡(jiǎn)單坛怪。不過(guò)還有一點(diǎn)需要處理淤齐,onResourceReady()方法和onLoadFailed()方法都有一個(gè)布爾值的返回值股囊,返回false就表示這個(gè)事件沒(méi)有被處理,還會(huì)繼續(xù)向下傳遞更啄,返回true就表示這個(gè)事件已經(jīng)被處理掉了稚疹,從而不會(huì)再繼續(xù)向下傳遞。舉個(gè)簡(jiǎn)單點(diǎn)的例子祭务,如果我們?cè)赗equestListener的onResourceReady()方法中返回了true内狗,那么就不會(huì)再回調(diào)Target的onResourceReady()方法了。
9. 圖片變換功能
- 圖片變換的意思就是說(shuō)义锥,Glide從加載了原始圖片到最終展示給用戶之前柳沙,又進(jìn)行了一些變換處理,從而能夠?qū)崿F(xiàn)一些更加豐富的圖片效果拌倍,如圖片圓角化赂鲤、圓形化噪径、模糊化等等。
- 添加圖片變換的用法非常簡(jiǎn)單数初,我們只需要在RequestOptions中串接transforms()方法找爱,并將想要執(zhí)行的圖片變換操作作為參數(shù)傳入transforms()方法即可,如下所示:
RequestOptions options = new RequestOptions()
.transforms(...);
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
- 至于具體要進(jìn)行什么樣的圖片變換操作泡孩,這個(gè)通常都是需要我們自己來(lái)寫(xiě)的车摄。不過(guò)Glide已經(jīng)內(nèi)置了幾種圖片變換操作,我們可以直接拿來(lái)使用仑鸥,比如CenterCrop吮播、FitCenter、CircleCrop等眼俊。
- ImageView默認(rèn)的scaleType是FIT_CENTER薄料,因此加載圖片時(shí)會(huì)自動(dòng)添加一個(gè)FitCenter的圖片變換,而在這個(gè)圖片變換過(guò)程中做了某些操作泵琳,會(huì)導(dǎo)致圖片充滿整個(gè)布局摄职,可是有時(shí)我們不想要讓它充滿整個(gè)布局,怎么辦呢获列?實(shí)際上谷市,Glide給我們提供了專門的API來(lái)取消圖片變換,使所有的圖片變換(默認(rèn)的和我們自己添加的)操作就全部失效了击孩。取消API如下:
RequestOptions options = new RequestOptions()
.dontTransform();
- 但所有的內(nèi)置圖片變換操作其實(shí)都不需要使用transform()方法迫悠,Glide為了方便我們使用直接提供了現(xiàn)成的API:
RequestOptions options = new RequestOptions()
.centerCrop();
RequestOptions options = new RequestOptions()
.fitCenter();
RequestOptions options = new RequestOptions()
.circleCrop();
- 當(dāng)然,這些內(nèi)置的圖片變換API其實(shí)也只是對(duì)transform()方法進(jìn)行了一層封裝而已巩梢,它們背后的源碼仍然還是借助transform()方法來(lái)實(shí)現(xiàn)的创泄。
- 這里我們就選擇其中一種內(nèi)置的圖片變換操作來(lái)演示一下吧,circleCrop()方法是用來(lái)對(duì)圖片進(jìn)行圓形化裁剪的括蝠,我們動(dòng)手試一下鞠抑,代碼如下所示:
String url = "http://guolin.tech/book.png";
RequestOptions options = new RequestOptions()
.circleCrop();
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
重新運(yùn)行一下程序并點(diǎn)擊加載圖片按鈕,效果如下圖所示忌警。
- 可以看到搁拙,現(xiàn)在展示的圖片是對(duì)原圖進(jìn)行圓形化裁剪后得到的圖片。
- 當(dāng)然法绵,除了使用內(nèi)置的圖片變換操作之外箕速,我們完全可以自定義自己的圖片變換操作。理論上朋譬,在對(duì)圖片進(jìn)行變換這個(gè)步驟中我們可以進(jìn)行任何的操作盐茎,你想對(duì)圖片怎么樣都可以。包括圓角化徙赢、圓形化字柠、黑白化滑进、模糊化等等,甚至你將原圖片完全替換成另外一張圖都是可以的募谎。
- 為了方便我們的使用我們來(lái)看一個(gè)非常優(yōu)秀的開(kāi)源庫(kù)扶关,glide-transformations。它實(shí)現(xiàn)了很多通用的圖片變換效果数冬,如裁剪變換节槐、顏色變換、模糊變換等等拐纱,使得我們可以非常輕松地進(jìn)行各種各樣的圖片變換铜异,這樣就可以省去我們?nèi)ゾ帉?xiě)自定義圖片變換操作的代碼了,避免重復(fù)造輪子秸架。
- glide-transformations的項(xiàng)目主頁(yè)地址是 https://github.com/wasabeef/glide-transformations
- 下面我們就來(lái)體驗(yàn)一下這個(gè)庫(kù)的強(qiáng)大功能吧揍庄。首先需要將這個(gè)庫(kù)引入到我們的項(xiàng)目當(dāng)中,在app/build.gradle文件當(dāng)中添加如下依賴:
dependencies {
implementation 'jp.wasabeef:glide-transformations:3.0.1'
}
- 我們可以對(duì)圖片進(jìn)行單個(gè)變換處理东抹,也可以將多種圖片變換疊加在一起使用蚂子。比如我想同時(shí)對(duì)圖片進(jìn)行模糊化和黑白化處理,就可以這么寫(xiě):
String url = "http://guolin.tech/book.png";
RequestOptions options = new RequestOptions()
.transforms(new BlurTransformation(), new GrayscaleTransformation());
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
可以看到缭黔,同時(shí)執(zhí)行多種圖片變換的時(shí)候食茎,只需要將它們都傳入到transforms()方法中即可。現(xiàn)在重新運(yùn)行一下程序馏谨,效果如下圖所示别渔。
- 當(dāng)然,這只是glide-transformations庫(kù)的一小部分功能而已惧互,更多的圖片變換效果你可以到它的GitHub項(xiàng)目主頁(yè)去學(xué)習(xí)哎媚。所有變換的用法都是這么簡(jiǎn)單哦。
10. 自定義模塊
- 自定義模塊功能可以將更改Glide配置喊儡,替換Glide組件等操作獨(dú)立出來(lái)拨与,使得我們能輕松地對(duì)Glide的各種配置進(jìn)行自定義,并且又和Glide的圖片加載邏輯沒(méi)有任何交集管宵,這也是一種低耦合編程方式的體現(xiàn)截珍。下面我們就來(lái)學(xué)習(xí)一下自定義模塊要如何實(shí)現(xiàn)。
- 首先定義一個(gè)我們自己的模塊類箩朴,并讓它繼承自AppGlideModule,如下所示:
import android.content.Context;
import android.support.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
}
//替換Glide組件
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
super.registerComponents(context, glide, registry);
}
}
- 可以看到秋度,在MyAppGlideModule類當(dāng)中炸庞,我們重寫(xiě)了applyOptions()和registerComponents()方法,這兩個(gè)方法分別就是用來(lái)更改Glide配置以及替換Glide組件的荚斯。
- 注意在MyAppGlideModule類在上面埠居,我們加入了一個(gè)@GlideModule的注解查牌,有了@GlideModule注解Glide才能夠識(shí)別這個(gè)自定義模塊。
- 這樣的話滥壕,我們就將Glide自定義模塊的功能完成了纸颜。后面只需要在applyOptions()和registerComponents()這兩個(gè)方法中加入具體的邏輯,就能實(shí)現(xiàn)更改Glide配置或者替換Glide組件的功能了绎橘,下面我們馬上就來(lái)學(xué)習(xí)一下胁孙。
更改Glide配置
- 剛才在分析自定義模式工作原理的時(shí)候其實(shí)就已經(jīng)提到了,如果想要更改Glide的默認(rèn)配置称鳞,其實(shí)只需要在applyOptions()方法中提前將Glide的配置項(xiàng)進(jìn)行初始化就可以了涮较。那么Glide一共有哪些配置項(xiàng)呢?這里我給大家做了一個(gè)列舉:
- setMemoryCache()
用于配置Glide的內(nèi)存緩存策略冈止,默認(rèn)配置是LruResourceCache狂票。- setBitmapPool()
用于配置Glide的Bitmap緩存池,默認(rèn)配置是LruBitmapPool熙暴。- setDiskCache()
用于配置Glide的硬盤緩存策略闺属,默認(rèn)配置是InternalCacheDiskCacheFactory。- setDiskCacheExecutor()
用于配置Glide讀取緩存中圖片的異步執(zhí)行器周霉,默認(rèn)配置是FifoPriorityThreadPoolExecutor屋剑,也就是先入先出原則。- setResizeService()
用于配置Glide讀取非緩存中圖片的異步執(zhí)行器诗眨,默認(rèn)配置也是FifoPriorityThreadPoolExecutor唉匾。- setDefaultRequestOptions()
用于配置Glide加載圖片的默認(rèn)請(qǐng)求選項(xiàng),其中解碼模式的配置就包含在里面匠楚,默認(rèn)配置是RGB_565巍膘。
- 其實(shí)Glide的這些默認(rèn)配置都非常科學(xué)且合理芋簿,使用的緩存算法也都是效率極高的峡懈,因此在絕大多數(shù)情況下我們并不需要去修改這些默認(rèn)配置,這也是Glide用法能如此簡(jiǎn)潔的一個(gè)原因与斤。
- 但是Glide科學(xué)的默認(rèn)配置并不影響我們?nèi)W(xué)習(xí)自定義Glide模塊的功能肪康,因此總有某些情況下,默認(rèn)的配置可能將無(wú)法滿足你撩穿,這個(gè)時(shí)候就需要我們自己動(dòng)手來(lái)修改默認(rèn)配置了磷支。
將加載的圖片緩存到SD卡
- 下面就通過(guò)具體的實(shí)例來(lái)看一下吧。剛才說(shuō)到食寡,Glide默認(rèn)的硬盤緩存策略使用的是InternalCacheDiskCacheFactory雾狈,這種緩存會(huì)將所有Glide加載的圖片都存儲(chǔ)到當(dāng)前應(yīng)用的私有目錄下。這是一種非常安全的做法抵皱,但同時(shí)這種做法也造成了一些不便善榛,因?yàn)樗接心夸浵录词故情_(kāi)發(fā)者自己也是無(wú)法查看的辩蛋,如果我想要去驗(yàn)證一下圖片到底有沒(méi)有成功緩存下來(lái),這就有點(diǎn)不太好辦了移盆。
- 這種情況下悼院,就非常適合使用自定義模塊來(lái)更改Glide的默認(rèn)配置。我們完全可以自己去實(shí)現(xiàn)DiskCache.Factory接口來(lái)自定義一個(gè)硬盤緩存策略咒循,不過(guò)卻大大沒(méi)有必要這么做据途,因?yàn)镚lide本身就內(nèi)置了一個(gè)ExternalCacheDiskCacheFactory,可以允許將加載的圖片都緩存到SD卡剑鞍。
- 那么接下來(lái)昨凡,我們就嘗試使用這個(gè)ExternalPreferredCacheDiskCacheFactory來(lái)替換默認(rèn)的InternalCacheDiskCacheFactory,從而將所有Glide加載的圖片都緩存到SD卡上蚁署。
- 由于在前面我們已經(jīng)創(chuàng)建好了一個(gè)自定義模塊MyGlideModule便脊,那么現(xiàn)在就可以直接在這里編寫(xiě)邏輯了,代碼如下所示:
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context));
}
- 沒(méi)錯(cuò)光戈,就是這么簡(jiǎn)單哪痰,現(xiàn)在所有Glide加載的圖片都會(huì)緩存到SD卡上了。
修改Glide默認(rèn)的緩存大小
- InternalCacheDiskCacheFactory和ExternalPreferredCacheDiskCacheFactory的默認(rèn)硬盤緩存大小都是250M久妆。也就是說(shuō)晌杰,如果你的應(yīng)用緩存的圖片總大小超出了250M,那么Glide就會(huì)按照DiskLruCache算法的原則來(lái)清理緩存的圖片筷弦。
- 當(dāng)然肋演,我們是可以對(duì)這個(gè)默認(rèn)的緩存大小進(jìn)行修改的,而且修改方式非常簡(jiǎn)單烂琴,如下所示:
private static final long DISK_CACHE_SIZE = 500*1024*1024;
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context,DISK_CACHE_SIZE));
}
- 只需要向ExternalPreferredCacheDiskCacheFactory或者InternalCacheDiskCacheFactory再傳入一個(gè)參數(shù)就可以了爹殊,現(xiàn)在我們就將Glide硬盤緩存的大小調(diào)整成了500M。
- 好了奸绷,更改Glide配置的功能就是這么簡(jiǎn)單梗夸,那么接下來(lái)我們就來(lái)驗(yàn)證一下更改的配置到底有沒(méi)有生效吧。
- 這里還是使用最基本的Glide加載語(yǔ)句來(lái)去加載一張網(wǎng)絡(luò)圖片:
String url = "http://guolin.tech/book.png";
Glide.with(this)
.load(url)
.into(imageView);
- 運(yùn)行一下程序号醉,圖片加載出來(lái)之后我們?nèi)フ艺宜木彺姘伞?/li>
-
ExternalPreferredCacheDiskCacheFactory的默認(rèn)緩存路徑是在Android/data/包名/cache/image_manager_disk_cache目錄當(dāng)中反症,我們使用文件瀏覽器進(jìn)入到這個(gè)目錄,結(jié)果如下圖所示畔派。
-
可以看到铅碍,這里有兩個(gè)文件,其中journal文件是DiskLruCache算法的日志文件父虑,這個(gè)文件必不可少该酗,且只會(huì)有一個(gè)。而另外一個(gè)文件就是那張緩存的圖片了士嚎,它的文件名雖然看上去很奇怪呜魄,但是我們只需要把這個(gè)文件的后綴改成.png,然后用圖片瀏覽器打開(kāi)莱衩,結(jié)果就一目了然了爵嗅,如下圖所示。
- 由此證明笨蚁,我們已經(jīng)成功將Glide的硬盤緩存路徑修改到SD卡上了睹晒。
修改Glide加載圖片的格式為ARGB_8888
- 我們都知道Glide和Picasso的用法是非常相似的,但是有一點(diǎn)差別卻很大括细。Glide加載圖片的默認(rèn)格式是RGB_565伪很,而Picasso加載圖片的默認(rèn)格式是ARGB_8888。ARGB_8888格式的圖片效果會(huì)更加細(xì)膩奋单,但是內(nèi)存開(kāi)銷會(huì)比較大锉试。而RGB_565格式的圖片則更加節(jié)省內(nèi)存,但是圖片效果上會(huì)差一些览濒。
- Glide和Picasso各自采取的默認(rèn)圖片格式談不上熟優(yōu)熟劣呆盖,只能說(shuō)各自的取舍不一樣。但是如果你希望Glide也能使用ARGB_8888的圖片格式贷笛,這當(dāng)然也是可以的应又。我們只需要在MyGlideModule中更改一下默認(rèn)配置即可,如下所示:
private static final long DISK_CACHE_SIZE = 500*1024*1024;
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context,DISK_CACHE_SIZE));
builder.setDefaultRequestOptions(
new RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
);
}
- 通過(guò)這樣配置之后乏苦,使用Glide加載的所有圖片都將會(huì)使用ARGB_8888的格式株扛,雖然圖片質(zhì)量變好了,但同時(shí)內(nèi)存開(kāi)銷也會(huì)明顯增大汇荐,所以你要做好心理準(zhǔn)備哦洞就。
替換Glide組件(替換HTTP通訊組件)
- 替換Glide組件功能需要在自定義模塊的registerComponents()方法中加入具體的替換邏輯。相比于更改Glide配置拢驾,替換Glide組件這個(gè)功能的難度就明顯大了不少奖磁。Glide中的組件非常繁多,也非常復(fù)雜繁疤,但其實(shí)大多數(shù)情況下并不需要我們?nèi)プ鍪裁刺鎿Q咖为。不過(guò),有一個(gè)組件卻有著比較大的替換需求稠腊,那就是Glide的HTTP通訊組件躁染。
- 默認(rèn)情況下,Glide使用的是基于原生HttpURLConnection進(jìn)行訂制的HTTP通訊組件架忌,但是現(xiàn)在大多數(shù)的Android開(kāi)發(fā)者都更喜歡使用OkHttp吞彤,因此將Glide中的HTTP通訊組件修改成OkHttp的這個(gè)需求比較常見(jiàn),那么我們就來(lái)實(shí)現(xiàn)下這個(gè)功能,基本就是將HTTP的通訊部分代碼用到的通訊組件HttpURLConnection替換成我們需要的OkHttp饰恕。
- 將OkHttp的庫(kù)引入到當(dāng)前項(xiàng)目中挠羔,如下所示:
dependencies {
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
}
- 新建一個(gè)OkHttpFetcher類,并且實(shí)現(xiàn)DataFetcher接口埋嵌,代碼如下所示:
import android.support.annotation.NonNull;
import android.util.Log;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.HttpException;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.util.ContentLengthInputStream;
import com.bumptech.glide.util.Preconditions;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import okhttp3.Call;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class OkHttpFetcher implements DataFetcher<InputStream>, okhttp3.Callback {
private static final String TAG = "OkHttpFetcher";
private final Call.Factory client;
private final GlideUrl url;
private InputStream stream;
private ResponseBody responseBody;
private DataFetcher.DataCallback<? super InputStream> callback;
private volatile Call call;
@SuppressWarnings("WeakerAccess")
public OkHttpFetcher(Call.Factory client, GlideUrl url) {
this.client = client;
this.url = url;
}
@Override
public void loadData(@NonNull Priority priority,
@NonNull final DataCallback<? super InputStream> callback) {
Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
Request request = requestBuilder.build();
this.callback = callback;
call = client.newCall(request);
call.enqueue(this);
}
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "OkHttp failed to obtain result", e);
}
callback.onLoadFailed(e);
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
responseBody = response.body();
if (response.isSuccessful()) {
long contentLength = Preconditions.checkNotNull(responseBody).contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
callback.onDataReady(stream);
} else {
callback.onLoadFailed(new HttpException(response.message(), response.code()));
}
}
@Override
public void cleanup() {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
// Ignored
}
if (responseBody != null) {
responseBody.close();
}
callback = null;
}
@Override
public void cancel() {
Call local = call;
if (local != null) {
local.cancel();
}
}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
}
- 然后新建一個(gè)OkHttpGlideUrlLoader類破加,并且實(shí)現(xiàn)ModelLoader
import android.support.annotation.NonNull;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import java.io.InputStream;
import okhttp3.Call;
import okhttp3.OkHttpClient;
public class OkHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
private final Call.Factory client;
@SuppressWarnings("WeakerAccess")
public OkHttpGlideUrlLoader(@NonNull Call.Factory client) {
this.client = client;
}
@Override
public boolean handles(@NonNull GlideUrl url) {
return true;
}
@Override
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
@NonNull Options options) {
return new LoadData<>(model, new OkHttpFetcher(client, model));
}
@SuppressWarnings("WeakerAccess")
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private static volatile Call.Factory internalClient;
private final Call.Factory client;
private static Call.Factory getInternalClient() {
if (internalClient == null) {
synchronized (OkHttpGlideUrlLoader.Factory.class) {
if (internalClient == null) {
internalClient = new OkHttpClient();
}
}
}
return internalClient;
}
public Factory() {
this(getInternalClient());
}
public Factory(@NonNull Call.Factory client) {
this.client = client;
}
@NonNull
@Override
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new OkHttpGlideUrlLoader(client);
}
@Override
public void teardown() {}
}
}
- 接下來(lái),新建一個(gè)MyGlideModule類并實(shí)現(xiàn)GlideModule接口雹嗦,然后在registerComponents()方法中將我們剛剛創(chuàng)建的OkHttpGlideUrlLoader和OkHttpFetcher注冊(cè)到Glide當(dāng)中范舀,將原來(lái)的HTTP通訊組件給替換掉,如下所示:
import android.content.Context;
import android.support.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.cache.ExternalPreferredCacheDiskCacheFactory;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.AppGlideModule;
import com.bumptech.glide.request.RequestOptions;
import java.io.InputStream;
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
private static final long DISK_CACHE_SIZE = 500*1024*1024;
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context,DISK_CACHE_SIZE));
builder.setDefaultRequestOptions(
new RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
);
}
//替換Glide組件
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
super.registerComponents(context, glide, registry);
registry.replace(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory());
}
}
- 可以看到了罪,這里是調(diào)用了registry的replace()方法來(lái)替換組件的锭环。
更簡(jiǎn)單的組件替換
- 上述方法是我們純手工地將Glide的HTTP通訊組件進(jìn)行了替換,如果你不想這么麻煩也是可以的泊藕,Glide官方給我們提供了非常簡(jiǎn)便的HTTP組件替換方式辅辩。并且除了支持OkHttp3之外,還支持Volley吱七。
- 我們只需要在gradle當(dāng)中添加幾行庫(kù)的配置就行了汽久。比如使用OkHttp3來(lái)作為HTTP通訊組件的配置如下:
implementation "com.github.bumptech.glide:okhttp3-integration:4.7.1"
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
- 使用Volley來(lái)作為HTTP通訊組件的配置如下:
implementation "com.github.bumptech.glide:volley-integration:4.7.1"
implementation 'com.mcxiaoke.volley:library:1.0.19'
- 當(dāng)然了,這些庫(kù)背后的工作原理和我們剛才自己手動(dòng)實(shí)現(xiàn)替換HTTP組件的原理是一模一樣的踊餐。而學(xué)會(huì)了手動(dòng)替換組件的原理我們就能更加輕松地?cái)U(kuò)展更多豐富的功能景醇,因此掌握這一技能還是非常重要的。
實(shí)現(xiàn)下載進(jìn)度監(jiān)聽(tīng)
- 我們都知道吝岭,使用Glide來(lái)加載一張網(wǎng)絡(luò)上的圖片是非常簡(jiǎn)單的三痰,但是讓人頭疼的是,我們卻無(wú)從得知當(dāng)前圖片的下載進(jìn)度窜管。如果這張圖片很小的話散劫,那么問(wèn)題也不大,反正很快就會(huì)被加載出來(lái)幕帆。但如果這是一張比較大的GIF圖获搏,用戶耐心等了很久結(jié)果圖片還沒(méi)顯示出來(lái),這個(gè)時(shí)候你就會(huì)覺(jué)得下載進(jìn)度功能是十分有必要的了失乾。
- 好的常熙,那么下面我們就對(duì)Glide進(jìn)行功能擴(kuò)展,使其支持監(jiān)聽(tīng)圖片下載進(jìn)度的功能碱茁。我們是基于把組件替換成了OkHttp的基礎(chǔ)上進(jìn)行實(shí)現(xiàn)的裸卫。
- 那么,將HTTP通訊組件替換成OkHttp之后纽竣,我們又該如何去實(shí)現(xiàn)監(jiān)聽(tīng)下載進(jìn)度的功能呢墓贿?這就要依靠OkHttp強(qiáng)大的攔截器機(jī)制了茧泪。
- 我們只要向OkHttp中添加一個(gè)自定義的攔截器,就可以在攔截器中捕獲到整個(gè)HTTP的通訊過(guò)程聋袋,然后加入一些自己的邏輯來(lái)計(jì)算下載進(jìn)度队伟,這樣就可以實(shí)現(xiàn)下載進(jìn)度監(jiān)聽(tīng)的功能了。
- 攔截器屬于OkHttp的高級(jí)功能舱馅,不過(guò)即使你之前并沒(méi)有接觸過(guò)攔截器缰泡,我相信你也能輕松看懂的刀荒,因?yàn)樗旧聿⒉浑y代嗤。
- 確定了實(shí)現(xiàn)思路之后,那我們就開(kāi)始動(dòng)手吧缠借。首先創(chuàng)建一個(gè)沒(méi)有任何邏輯的空攔截器干毅,新建ProgressInterceptor類并實(shí)現(xiàn)Interceptor接口,代碼如下所示:
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class ProgressInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
return response;
}
}
- 這個(gè)攔截器中我們可以說(shuō)是什么都沒(méi)有做泼返。就是攔截到了OkHttp的請(qǐng)求硝逢,然后調(diào)用proceed()方法去處理這個(gè)請(qǐng)求,最終將服務(wù)器響應(yīng)的Response返回绅喉。
- 接下來(lái)我們需要啟用這個(gè)攔截器渠鸽,修改MyGlideModule中的代碼,如下所示:
import android.content.Context;
import android.support.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.AppGlideModule;
import java.io.InputStream;
import okhttp3.OkHttpClient;
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
private static final long DISK_CACHE_SIZE = 500*1024*1024;
//更改Glide配置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
}
//替換Glide組件
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
super.registerComponents(context, glide, registry);
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new ProgressInterceptor());
OkHttpClient okHttpClient = builder.build();
registry.replace(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory(okHttpClient));
}
}
- 這里我們創(chuàng)建了一個(gè)OkHttpClient.Builder柴罐,然后調(diào)用addInterceptor()方法將剛才創(chuàng)建的ProgressInterceptor添加進(jìn)去徽缚,最后將構(gòu)建出來(lái)的新OkHttpClient對(duì)象傳入到OkHttpGlideUrlLoader.Factory中即可。
- 好的革屠,現(xiàn)在自定義的攔截器已經(jīng)啟用了凿试,接下來(lái)就可以開(kāi)始去實(shí)現(xiàn)下載進(jìn)度監(jiān)聽(tīng)的具體邏輯了。首先新建一個(gè)ProgressListener接口似芝,用于作為進(jìn)度監(jiān)聽(tīng)回調(diào)的工具那婉,如下所示:
public interface ProgressListener {
void onProgress(int progress);
}
- 然后我們?cè)谏厦婵盏腜rogressInterceptor攔截器類中加入注冊(cè)下載監(jiān)聽(tīng)和取消注冊(cè)下載監(jiān)聽(tīng)的方法。修改ProgressInterceptor中的代碼党瓮,如下所示:
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class ProgressInterceptor implements Interceptor {
static final Map<String, ProgressListener> LISTENER_MAP = new HashMap<>();
public static void addListener(String url, ProgressListener listener) {
LISTENER_MAP.put(url, listener);
}
public static void removeListener(String url) {
LISTENER_MAP.remove(url);
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
return response;
}
}
- 可以看到详炬,這里使用了一個(gè)Map來(lái)保存注冊(cè)的監(jiān)聽(tīng)器,Map的鍵是一個(gè)URL地址寞奸。之所以要這么做呛谜,是因?yàn)槟憧赡軙?huì)使用Glide同時(shí)加載很多張圖片,而這種情況下蝇闭,必須要能區(qū)分出來(lái)每個(gè)下載進(jìn)度的回調(diào)到底是對(duì)應(yīng)哪個(gè)圖片URL地址的呻率。
- 接下來(lái)就要到最復(fù)雜的部分了,也就是下載進(jìn)度的具體計(jì)算呻引。我們需要新建一個(gè)ProgressResponseBody類礼仗,并讓它繼承自O(shè)kHttp的ResponseBody,然后在這個(gè)類當(dāng)中去編寫(xiě)具體的監(jiān)聽(tīng)下載進(jìn)度的邏輯,代碼如下所示:
import android.util.Log;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
public class ProgressResponseBody extends ResponseBody {
private static final String TAG = "ProgressResponseBody";
private BufferedSource bufferedSource;
private ResponseBody responseBody;
private ProgressListener listener;
public ProgressResponseBody(String url, ResponseBody responseBody) {
this.responseBody = responseBody;
listener = ProgressInterceptor.LISTENER_MAP.get(url);
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(new ProgressSource(responseBody.source()));
}
return bufferedSource;
}
private class ProgressSource extends ForwardingSource {
long totalBytesRead = 0;
int currentProgress;
ProgressSource(Source source) {
super(source);
}
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
long fullLength = responseBody.contentLength();
if (bytesRead == -1) {
totalBytesRead = fullLength;
} else {
totalBytesRead += bytesRead;
}
int progress = (int) (100f * totalBytesRead / fullLength);
Log.d(TAG, "download progress is " + progress);
if (listener != null && progress != currentProgress) {
listener.onProgress(progress);
}
if (listener != null && totalBytesRead == fullLength) {
listener = null;
}
currentProgress = progress;
return bytesRead;
}
}
}
- 其實(shí)這段代碼也不是很難元践,下面我來(lái)簡(jiǎn)單解釋一下韭脊。首先,我們定義了一個(gè)ProgressResponseBody的構(gòu)造方法单旁,該構(gòu)造方法中要求傳入一個(gè)url參數(shù)和一個(gè)ResponseBody參數(shù)沪羔。那么很顯然,url參數(shù)就是圖片的url地址了象浑,而ResponseBody參數(shù)則是OkHttp攔截到的原始的ResponseBody對(duì)象蔫饰。然后在構(gòu)造方法中,我們調(diào)用了ProgressInterceptor中的LISTENER_MAP來(lái)去獲取該url對(duì)應(yīng)的監(jiān)聽(tīng)器回調(diào)對(duì)象愉豺,有了這個(gè)對(duì)象篓吁,待會(huì)就可以回調(diào)計(jì)算出來(lái)的下載進(jìn)度了。
- 由于繼承了ResponseBody類之后一定要重寫(xiě)contentType()蚪拦、contentLength()和source()這三個(gè)方法杖剪,我們?cè)赾ontentType()和contentLength()方法中直接就調(diào)用傳入的原始ResponseBody的contentType()和contentLength()方法即可,這相當(dāng)于一種委托模式驰贷。但是在source()方法中盛嘿,我們就必須加入點(diǎn)自己的邏輯了,因?yàn)檫@里要涉及到具體的下載進(jìn)度計(jì)算括袒。
- 那么我們具體看一下source()方法次兆,這里先是調(diào)用了原始ResponseBody的source()方法來(lái)去獲取Source對(duì)象,接下來(lái)將這個(gè)Source對(duì)象封裝到了一個(gè)ProgressSource對(duì)象當(dāng)中箱熬,最終再用Okio的buffer()方法封裝成BufferedSource對(duì)象返回类垦。
- 那么這個(gè)ProgressSource是什么呢爬迟?它是一個(gè)我們自定義的繼承自ForwardingSource的實(shí)現(xiàn)類指巡。ForwardingSource也是一個(gè)使用委托模式的工具,它不處理任何具體的邏輯限寞,只是負(fù)責(zé)將傳入的原始Source對(duì)象進(jìn)行中轉(zhuǎn)糕伐。但是砰琢,我們使用ProgressSource繼承自ForwardingSource,那么就可以在中轉(zhuǎn)的過(guò)程中加入自己的邏輯了良瞧。
- 可以看到陪汽,在ProgressSource中我們重寫(xiě)了read()方法,然后在read()方法中獲取該次讀取到的字節(jié)數(shù)以及下載文件的總字節(jié)數(shù)褥蚯,并進(jìn)行一些簡(jiǎn)單的數(shù)學(xué)計(jì)算就能算出當(dāng)前的下載進(jìn)度了挚冤。這里我先使用Log工具將算出的結(jié)果打印了一下,再通過(guò)前面獲取到的回調(diào)監(jiān)聽(tīng)器對(duì)象將結(jié)果進(jìn)行回調(diào)赞庶。
- 好的训挡,現(xiàn)在計(jì)算下載進(jìn)度的邏輯已經(jīng)完成了澳骤,那么我們快點(diǎn)在攔截器當(dāng)中使用它吧。修改ProgressInterceptor中的代碼澜薄,如下所示:
public class ProgressInterceptor implements Interceptor {
...
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
String url = request.url().toString();
ResponseBody body = response.body();
Response newResponse = response.newBuilder().body(new ProgressResponseBody(url, body)).build();
return newResponse;
}
}
- 這里也都是一些OkHttp的簡(jiǎn)單用法为肮。我們通過(guò)Response的newBuilder()方法來(lái)創(chuàng)建一個(gè)新的Response對(duì)象,并把它的body替換成剛才實(shí)現(xiàn)的ProgressResponseBody肤京,最終將新的Response對(duì)象進(jìn)行返回颊艳,這樣計(jì)算下載進(jìn)度的邏輯就能生效了。
-
代碼寫(xiě)到這里忘分,我們就可以來(lái)運(yùn)行一下程序了∑逭恚現(xiàn)在無(wú)論是加載任何網(wǎng)絡(luò)上的圖片,都應(yīng)該是可以監(jiān)聽(tīng)到它的下載進(jìn)度的饭庞。運(yùn)行下程序戒悠,圖片加載出來(lái)后可以看到我們?cè)赑rogressResponseBody中加的日志打印出來(lái)如下所示:
11. 使用Generated API
使用和Glide 3一樣的流式API接口
- Generated API是Glide 4中全新引入的一個(gè)功能,它的工作原理是使用注解處理器 (Annotation Processor) 來(lái)生成出一個(gè)API舟山,在自定義模塊中可使用該流式API一次性調(diào)用到RequestBuilder,RequestOptions和集成庫(kù)中所有的選項(xiàng)卤恳。簡(jiǎn)單點(diǎn)說(shuō)累盗,就是Glide 4仍然給我們提供了一套和Glide 3一模一樣的流式API接口。
- 對(duì)于熟悉Glide 3的朋友來(lái)說(shuō)那是再簡(jiǎn)單不過(guò)了突琳,基本上就是和Glide 3一模一樣的用法若债,只不過(guò)需要把Glide關(guān)鍵字替換成GlideApp關(guān)鍵字,如下所示:
GlideApp.with(this)
.load(url)
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.override(Target.SIZE_ORIGINAL)
.circleCrop()
.into(imageView);
- 不過(guò)拆融,有可能你的IDE中會(huì)提示找不到GlideApp這個(gè)類蠢琳。這個(gè)類是通過(guò)編譯時(shí)注解自動(dòng)生成的,首先確保你的代碼中有一個(gè)自定義的模塊镜豹,并且給它加上了@GlideModule注解傲须,也就是我們?cè)谏厦嫠v的內(nèi)容。然后在Android Studio中點(diǎn)擊菜單欄Build -> Rebuild Project趟脂,GlideApp這個(gè)類就會(huì)自動(dòng)生成了泰讽。
對(duì)現(xiàn)有的API進(jìn)行擴(kuò)展,定制出任何屬于你自己的API
- 下面具體舉個(gè)例子昔期,比如說(shuō)我們要求項(xiàng)目中所有圖片的緩存策略全部都要緩存原始圖片已卸,那么每次在使用Glide加載圖片的時(shí)候,都去指定diskCacheStrategy(DiskCacheStrategy.DATA)這么長(zhǎng)長(zhǎng)的一串代碼硼一,確實(shí)是讓人比較心煩累澡。這種情況我們就可以去定制一個(gè)自己的API了。
- 定制自己的API需要借助@GlideExtension和@GlideOption這兩個(gè)注解般贼。創(chuàng)建一個(gè)我們自定義的擴(kuò)展類愧哟,代碼如下所示:
import com.bumptech.glide.annotation.GlideExtension;
import com.bumptech.glide.annotation.GlideOption;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
@GlideExtension
public class MyGlideExtension {
private MyGlideExtension() {
}
@GlideOption
public static void cacheSource(RequestOptions options) {
options.diskCacheStrategy(DiskCacheStrategy.DATA);
}
}
- 這里我們定義了一個(gè)MyGlideExtension類惑申,并且給加上了一個(gè)@GlideExtension注解,然后要將這個(gè)類的構(gòu)造函數(shù)聲明成private翅雏,這都是必須要求的寫(xiě)法圈驼。
- 接下來(lái)就可以開(kāi)始自定義API了,這里我們定義了一個(gè)cacheSource()方法望几,表示只緩存原始圖片绩脆,并給這個(gè)方法加上了@GlideOption注解。注意自定義API的方法都必須是靜態(tài)方法橄抹,而且第一個(gè)參數(shù)必須是RequestOptions靴迫,后面你可以加入任意多個(gè)你想自定義的參數(shù)。
- 在cacheSource()方法中楼誓,我們?nèi)匀贿€是調(diào)用的diskCacheStrategy(DiskCacheStrategy.DATA)方法玉锌,所以說(shuō)cacheSource()就是一層簡(jiǎn)化API的封裝而已。
- 然后在Android Studio中點(diǎn)擊菜單欄Build -> Rebuild Project疟羹,神奇的事情就會(huì)發(fā)生了主守,你會(huì)發(fā)現(xiàn)你已經(jīng)可以使用這樣的語(yǔ)句來(lái)加載圖片了:
GlideApp.with(this)
.load(url)
.cacheSource()
.into(imageView);
有了這個(gè)強(qiáng)大的功能之后,我們使用Glide就能變得更加靈活了榄融。
結(jié)束語(yǔ)
到此為止参淫,Glide 4的用法我基本也總結(jié)完了。這篇博客也是我寫(xiě)博客以來(lái)不論是篇幅和用時(shí)都是最長(zhǎng)的的一篇愧杯,如果你能從頭看到這里你對(duì)Glide的用法也可以說(shuō)是深度掌握了涎才,基本上可以滿足你項(xiàng)目上的所有要求了。同時(shí)你能堅(jiān)持看下來(lái)你也是很棒的哦力九,加油吧耍铜,少年!