Glide筆記
一、簡(jiǎn)介
在泰國(guó)舉行的谷歌開(kāi)發(fā)者論壇上,谷歌為我們介紹了一個(gè)名叫Glide的圖片加載庫(kù)痢毒,作者是bumptech。這個(gè)庫(kù)被廣泛的運(yùn)用在Google的開(kāi)源項(xiàng)目中蚕甥,包括2014年Google I/O大會(huì)上發(fā)布的官方App哪替。
Glide是一款由Bump Technologies開(kāi)發(fā)的圖片加載框架,使得我們可以在Android平臺(tái)上以極度簡(jiǎn)單的方式加載和展示圖片菇怀。Glide默認(rèn)使用HttpUrlConnection進(jìn)行網(wǎng)絡(luò)請(qǐng)求凭舶,為了讓App保持一致的網(wǎng)絡(luò)請(qǐng)求形式,可以讓Glide使用我們指定的網(wǎng)絡(luò)請(qǐng)求形式請(qǐng)求網(wǎng)絡(luò)資源爱沟。
二帅霜、依賴
1.jar包
Github地址:https://github.com/bumptech/glide/releases
2.Gradle
dependencies {
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.android.support:support-v4:23.3.0'
}
三、權(quán)限
<uses-permission android:name="android.permission.INTERNET" />
四呼伸、混淆
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
五身冀、使用
-
Glide.with(context).load(imageUrl).into(imageView);
//從URL中加載 -
Glide.with(context).load(R.mipmap.ic_launcher).into(imageView);
//從Res資源中加載 -
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Running.jpg");
Glide.with(context).load(file).into(imageView);
//從文件加載 -
Uri uri = resourceIdToUri(context, R.mipmap.ic_launcher);
Glide.with(context).load(uri).into(imageView);
//從Uri加載
Glide.with()方法用于創(chuàng)建一個(gè)加載圖片的實(shí)例。with()方法可以接收Context括享、Activity搂根、Fragment或者FragmentActivity類型的參數(shù)。特別需要注意的是with()方法中傳入的實(shí)例會(huì)決定Glide加載圖片的生命周期铃辖,如果傳入的是Activity剩愧、Fragment或者FragmentActivity的實(shí)例,那么當(dāng)其被銷(xiāo)毀時(shí)圖片加載也會(huì)停止娇斩,如果傳入的是ApplicationContext時(shí)只有當(dāng)應(yīng)用程序被殺掉的時(shí)候圖片加載才會(huì)停止仁卷。
使用Glide加載圖片不用擔(dān)心內(nèi)存浪費(fèi)穴翩,甚至是內(nèi)存溢出的問(wèn)題。因?yàn)镚lide不會(huì)直接將圖片的完整尺寸全部加載到內(nèi)存中锦积,而是用多少加載多少藏否。Glide會(huì)自動(dòng)判斷ImageView的大小,然后只將這么大的圖片像素加載到內(nèi)存當(dāng)中充包,幫助我們節(jié)省內(nèi)存開(kāi)支副签。
下面一個(gè)小的工具函數(shù)可以將資源id轉(zhuǎn)換為一個(gè)Uri:
public static final String ANDROID_RESOURCE = "android.resource://";
public static final String FOREWARD_SLASH = "/";
private static Uri resourceIdToUri(Context context, int resourceId) {
return Uri.parse(ANDROID_RESOURCE + context.getPackageName() + FOREWARD_SLASH + resourceId);
}
六、方法
1基矮、指定圖片格式
如果調(diào)用了.asBitmap()
方法淆储,則.load()中的參數(shù)指向的可以是一個(gè)靜態(tài)圖片也可以是GIF圖片,如果是一張GIF圖片家浇,則加載之后只會(huì)展示GIF圖片的第一幀本砰。
如果調(diào)用的.asGif()
方法,則.load()方法中的參數(shù)指向的必須是一個(gè)GIF圖片钢悲,如果是一張靜態(tài)圖片点额,則圖片加載完成之后展示的只是出錯(cuò)占位符(如果沒(méi)有設(shè)置出錯(cuò)占位符,則什么也不展示)莺琳。
//顯示靜態(tài)圖片(若加載的是gif圖那么就會(huì)顯示第一幀的圖片)
.asBitmap()
//顯示動(dòng)態(tài)圖片(若加載的是靜態(tài)圖會(huì)加載失敾估狻)
.asGif()
2、指定占位圖顯示
//加載時(shí)顯示的圖片
.placeholder(R.drawable.image_load)
//加載失敗時(shí)顯示的圖片
.error(R.drawable.image_error)
3惭等、設(shè)置緩存
Android應(yīng)用中一個(gè)較好的圖片處理框架珍手,會(huì)最小化網(wǎng)絡(luò)請(qǐng)求的消耗。Glide也是一樣辞做,默認(rèn)使用內(nèi)存和磁盤(pán)緩存來(lái)避免不必要的網(wǎng)絡(luò)請(qǐng)求琳要。然而,如果你的圖片變化的非吵用快稚补,你需要禁止一些緩存。
比如你請(qǐng)求一個(gè)1000x1000像素的圖片框喳,你的ImageView是500x500像素课幕,Glide會(huì)保存兩個(gè)版本的圖片到緩存里。
//禁止內(nèi)存緩存帖努,但仍然會(huì)緩存到磁盤(pán)
.skipMemoryCache(true)
//禁止磁盤(pán)緩存(Glide默認(rèn)緩存策略是:DiskCacheStrategy.RESULT)
.diskCacheStrategy(DiskCacheStrategy.NONE)
//緩存參數(shù)
//ALL:緩存源資源和轉(zhuǎn)換后的資源(即緩存所有版本圖像撰豺,默認(rèn)行為)
//NONE:不作任何磁盤(pán)緩存,然而默認(rèn)的它將仍然使用內(nèi)存緩存
//SOURCE:僅緩存源資源(原來(lái)的全分辨率的圖像)拼余,上面例子里的1000x1000像素的圖片
//RESULT:緩存轉(zhuǎn)換后的資源(最終的圖像,即降低分辨率后的或者是轉(zhuǎn)換后的)
如果你有一個(gè)圖片你需要經(jīng)常處理它亩歹,會(huì)生成各種不同的版本的圖片匙监,緩存它的原始的分辨率圖片才有意義凡橱。我們
使用DiskCacheStrategy.SOURCE
去告訴Glide只緩存原始版本:
Glide.with(context).load("url").diskCacheStrategy(DiskCacheStrategy.SOURCE).into(imageView);
4、設(shè)置加載尺寸
Glide在緩存和內(nèi)存里自動(dòng)限制圖片的大小去適配ImageView的尺寸亭姥。用Glide時(shí)稼钩,如果圖片不需要自動(dòng)適配ImageView,調(diào)用override(horizontalSize, verticalSize)达罗,
它會(huì)在將圖片顯示在ImageView之前調(diào)整圖片的大小坝撑。
//加載圖片為100*100像素的尺寸
.override(100, 100)
5、設(shè)置圖片縮放
如果調(diào)用了.centerCrop()
方法粮揉,則顯示圖片的時(shí)候短的一邊填充容器巡李,長(zhǎng)的一邊跟隨縮放;
如果調(diào)用了.fitCenter()
方法扶认,則顯示圖片的時(shí)候長(zhǎng)的一邊填充容器侨拦,短的一邊跟隨縮放;
這兩個(gè)方法可以都調(diào)用狱从,如果都調(diào)用叠纹,則最終顯示的效果是后調(diào)用的方法展示的效果。
//它是一個(gè)裁剪技術(shù)训貌,即縮放圖像讓它填充到ImageView界限內(nèi)并且裁剪額外的部分冒窍,ImageView可能會(huì)完全填充递沪,但圖像可能不會(huì)完整顯示
.centerCrop()
//它是一個(gè)裁剪技術(shù),即縮放圖像讓圖像都測(cè)量出來(lái)等于或小于ImageView的邊界范圍综液,該圖像將會(huì)完全顯示款慨,但可能不會(huì)填滿整個(gè)ImageView
.fitCenter()
Glide
.with(context)
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.override(600, 200) // resizes the image to these dimensions (in pixel)
.centerCrop() // this cropping technique scales the image so that it fills the requested bounds and then crops the extra.
.into(imageViewResizeCenterCrop);
6、設(shè)置資源加載優(yōu)先級(jí)
假設(shè)你正在創(chuàng)建一個(gè)信息展示界面谬莹,包含頂部的一個(gè)主要照片檩奠,還有底部的2個(gè)并不重要的小圖。對(duì)于用戶體驗(yàn)附帽,我們最好先加載主角照片埠戳,然后再加載底部不緊急的圖片。Glide里的.priority()方法和Priority的枚舉變量支持你的想法蕉扮。
Priority枚舉變量整胃,以遞增方式列出:
- Priority.LOW
- Priority.NORMAL
- Priority.HIGH
- Priority.IMMEDIATE
你應(yīng)當(dāng)明白優(yōu)先級(jí)并不是非常嚴(yán)格的。Glide會(huì)將它們作為一個(gè)指導(dǎo)來(lái)最優(yōu)化處理請(qǐng)求喳钟。但并不意味著所有的圖片都能夠按請(qǐng)求的順序加載屁使。
.priority(Priority.HIGH)
7在岂、設(shè)置圓角或圓形圖片
//圓角圖片
.transform(new GlideRoundTransform(this))
//圓形圖片
.transform(new GlideCircleTransform(this))
8、設(shè)置縮略圖
縮略圖的優(yōu)點(diǎn)
縮略圖不同于前面提到的占位圖蔽午。占位圖應(yīng)當(dāng)是跟app綁定在一起的資源及老〗径瘢縮略圖是一個(gè)動(dòng)態(tài)的占位圖尉咕,可以從網(wǎng)絡(luò)加載年缎〉ノ撸縮略圖也會(huì)被先加載,直到實(shí)際圖片請(qǐng)求加載完畢堂淡。如果因?yàn)槟承┰蚓畹恚s略圖獲得的時(shí)間晚于原始圖片皆的,它并不會(huì)替代原始圖片费薄,而是簡(jiǎn)單地被忽略掉楞抡。
提示:另外一個(gè)非常棒的平滑圖片顯示的方法召廷,通過(guò)加載圖片主色調(diào)的占位圖。
Glide提供了2個(gè)不同的方法產(chǎn)生縮略圖数初。
第一種:簡(jiǎn)單的縮略圖
通過(guò)在加載的時(shí)候指定一個(gè)小的分辨率梗顺,產(chǎn)生一個(gè)縮略圖寺谤。這個(gè)方法在ListView和詳細(xì)視圖的組合中非常有用变屁。如果你已經(jīng)在ListView中用到了250x250像素的圖片粟关,那么在在詳細(xì)視圖中會(huì)需要一個(gè)更大分辨率的圖片闷板。然而從用戶的角度遮晚,我們已經(jīng)看見(jiàn)了一個(gè)小版本的圖片县遣,為什么需要好幾秒萧求,同樣的圖片(高分辨率的)才能被再次加載出來(lái)呢顶瞒?
在這種情況下搁拙,從顯示250x250像素版本的圖片平滑過(guò)渡到詳細(xì)視圖里查看大圖更有意義箕速。Glide里的.thumbnail()方法讓這個(gè)變?yōu)榭赡苎尉ァ_@里,.thumbnal()的參數(shù)是一個(gè)(0,1)之間浮點(diǎn)數(shù):
Glide.with(context).load("url")
.thumbnail(0.1f)//系數(shù)需在(0,1)之間狡赐,這樣會(huì)先加載縮略圖然后加載全圖
.into(imageView);
這里傳遞一個(gè)0.1f作為參數(shù)枕屉,Glide會(huì)加載原始圖片大小的10%的圖片搀擂。如果原始圖片有1000x1000像素卷玉,縮略圖的分辨率為100x100像素相种。由于圖片將會(huì)比ImageView小寝并,你需要確笔尘ィ縮放類型是否正確别渔。
注意到你所有的請(qǐng)求設(shè)置都會(huì)影響到你的縮略圖。例如喇伯,如果你使用了一個(gè)變換讓你的圖片變?yōu)榛叶葓D稻据,縮略圖也同樣將會(huì)是灰度圖捻悯。
第二種:高級(jí)縮略圖請(qǐng)求(原圖與縮略圖完全不同 )
.thumbnail()傳入一個(gè)浮點(diǎn)類型的參數(shù)今缚,非常簡(jiǎn)單有效姓言,但并不是總是有意義。如果縮略圖需要從網(wǎng)絡(luò)加載同樣全分辨率圖片囱淋,可能根本都不快妥衣。這樣称鳞,Glide提供了另一個(gè)方法去加載和顯示縮略圖:傳遞一個(gè)新的Glide請(qǐng)求作為參數(shù)稠鼻。
private void loadImageThumbnailRequest() {
// setup Glide request without the into() method
DrawableRequestBuilder<String> thumbnailRequest = Glide
.with( context )
.load( eatFoodyImages[2] );
// pass the request as a a parameter to the thumbnail request
Glide
.with( context )
.load( UsageExampleGifAndVideos.gifUrl )
.thumbnail( thumbnailRequest )
.into( imageView3 );
}
區(qū)別在于第一個(gè)縮略圖請(qǐng)求是完全獨(dú)立于第二個(gè)原始請(qǐng)求的候齿』哦ⅲ縮略圖可以來(lái)自不同資源或者圖片URL亚皂,你可以在它上面應(yīng)用不同的變換灭必。
9禁漓、設(shè)置動(dòng)畫(huà)
加載圖片時(shí)所展示的動(dòng)畫(huà)播歼,可以是Animator類型的屬性動(dòng)畫(huà)秘狞,也可以是int類型的動(dòng)畫(huà)資源烁试。這個(gè)動(dòng)畫(huà)只在第一次加載的時(shí)候會(huì)展示廓潜,以后都會(huì)從緩存中獲取圖片辩蛋,因此也就不會(huì)展示動(dòng)畫(huà)了(圖片的改變時(shí)才會(huì)有用)。
//設(shè)置加載動(dòng)畫(huà)
.animate(R.anim.alpha_in)
//實(shí)現(xiàn)ViewPropertyAnimation.Animator接口
.animate(ViewPropertyAnimation.Animator animator)
//淡入淡出動(dòng)畫(huà)伤为,也是默認(rèn)動(dòng)畫(huà)绞愚,動(dòng)畫(huà)默認(rèn)的持續(xù)時(shí)間是300毫秒位衩。類似:.crossFade(int duration)
.crossFade()
//移除所有動(dòng)畫(huà)
.dontAnimate()
ViewPropertyAnimation.Animator animationObject = new ViewPropertyAnimation.Animator() {
@Override
public void animate(View view) {
// if it's a custom view class, cast it here
// then find subviews and do the animations
// here, we just use the entire view for the fade animation
view.setAlpha( 0f );
ObjectAnimator fadeAnim = ObjectAnimator.ofFloat( view, "alpha", 0f, 1f );
fadeAnim.setDuration( 2500 );
fadeAnim.start();
}
};
10糖驴、加載本地視頻(相當(dāng)于一張縮略圖)
//只能加載本地視頻(顯示的只是視頻的第一幀圖像贮缕,相當(dāng)于一張縮略圖感昼,不能播
//放視頻)定嗓,網(wǎng)絡(luò)視頻無(wú)法加載蜕乡。如果你想要從網(wǎng)絡(luò)URL播放視頻层玲,參考VideoView
String files = Environment.getExternalStorageDirectory().getAbsolutePath() + "/glide.avi";
Glide.with(this).load(files).into(view);
11反症、定制view中使用SimpleTarget和ViewTarget
Glide中的回調(diào):Target
假設(shè)我們并沒(méi)有ImageView作為圖片加載的目標(biāo)铅碍。我們只需要Bitmap本身。Glide提供了一個(gè)用Target獲取Bitmap資源的方法尘盼。Target只是用來(lái)回調(diào)卿捎,它會(huì)在所有的加載和處理完畢時(shí)返回想要的結(jié)果午阵。
Glide提供了多種多樣有各自明確目的Target。先從SimpleTarget介紹植袍。
SimpleTarget
private SimpleTarget target = new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
// do something with the bitmap
// for demonstration purposes, let's just set it to an ImageView
imageView1.setImageBitmap( bitmap );
}
};
Glide
.with(context) // could be an issue!
.load(eatFoodyImages[0])
.asBitmap()
.into(target);
使用Target注意事項(xiàng):
- 第一個(gè)是SimpleTarget對(duì)象的定義于个。java/Android可以允許你在.into()內(nèi)匿名定義览濒,但這會(huì)顯著增加在Glide處理完
圖片請(qǐng)求前Android垃圾回收清理匿名target對(duì)象的可能性。最終宙项,會(huì)導(dǎo)致圖片被加載了尤筐,但是回調(diào)永遠(yuǎn)不會(huì)被調(diào)用盆繁。
所以油昂,請(qǐng)確保將你的回調(diào)定義為一個(gè)字段對(duì)象冕碟,防止被萬(wàn)惡的Android垃圾回收給清理掉安寺。 - 第二個(gè)關(guān)鍵部分是Glide的.with( context )挑庶。這個(gè)問(wèn)題實(shí)際上是Glide一個(gè)特性問(wèn)題:當(dāng)你傳遞了一個(gè)context,例如
當(dāng)前app的activity井仰,當(dāng)activity停止后俱恶,Glide會(huì)自動(dòng)停止當(dāng)前的請(qǐng)求合是。這種整合到app生命周期內(nèi)是非常有用的聪全,但也
是很難處理的难礼。如果你的target是獨(dú)立于app的生命周期蛾茉。這里的解決方案是使用application的context:.with(context.getApplicationContext())撩鹿。當(dāng)app自己停止運(yùn)行的時(shí)候节沦,Glide會(huì)只取消掉圖片的請(qǐng)求甫贯。
特定大小的Target
另外一個(gè)潛在問(wèn)題是Target沒(méi)有一個(gè)明確的大小叫搁。如果你傳遞一個(gè)ImageView作為.into()的參數(shù)常熙,Glide會(huì)使用ImageView的
大小來(lái)限制圖片的大小裸卫。例如如果要加載的圖片是1000x1000像素墓贿,但是ImageView的尺寸只有250x250像素,Glide會(huì)降低圖片到小尺寸队伟,以節(jié)省處理時(shí)間和內(nèi)存嗜侮。顯然锈颗,由于target沒(méi)有具體大小击吱,這對(duì)target并不起效覆醇。但是永脓,如果你有個(gè)期望的具體大小,你可以增強(qiáng)回調(diào)革屠。如果你知道圖片應(yīng)當(dāng)為多大,那么在你的回調(diào)定義里應(yīng)當(dāng)指明板甘,以節(jié)省內(nèi)存:
private SimpleTarget target2 = new SimpleTarget<Bitmap>(250, 250) {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
imageView2.setImageBitmap(bitmap);
}
};
ViewTarget
有很多原因?qū)е挛覀儾荒苤苯邮褂肐mageView盐类。前面已經(jīng)介紹了如何獲取Bitmap≡谔現(xiàn)在猫妙,我們將更深入學(xué)習(xí)割坠。假設(shè)你有個(gè)自定義的View。由于沒(méi)有已知的方法在哪里設(shè)置圖片对妄,Glide并不支持加載圖片到定制的View內(nèi)剪菱。然而用ViewTarget會(huì)讓這個(gè)更簡(jiǎn)單孝常。
讓我們看一個(gè)簡(jiǎn)單的定制View茫因,它繼承于FrameLayout冻押,內(nèi)部使用了一個(gè)ImageView:
public class FutureStudioView extends FrameLayout {
...
public void setImage(Drawable drawable) {
iv = (ImageView) findViewById(R.id.custom_view_image);
iv.setImageDrawable(drawable);
}
}
由于我們定制的View并不是繼承自ImageView洛巢,這里不能使用常規(guī)的.into()方法稿茉。因此漓库,我們只能創(chuàng)建一個(gè)ViewTarget渺蒿,用來(lái)傳遞給.into()方法:
void loadImageViewTarget() {
FutureStudioView customView = (FutureStudioView) findViewById(R.id.custom_view);
viewTarget = new ViewTarget<FutureStudioView, GlideDrawable>(customView) {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
this.view.setImage(resource.getCurrent());
}
};
Glide.with(context.getApplicationContext()) // safer!
.load(eatFoodyImages[2])
.into(viewTarget);
}
在target的回調(diào)方法中茂装,我們?cè)诙ㄖ苬iew上使用我們創(chuàng)建的setImage(Drawable drawable)方法設(shè)置圖片少态。同時(shí)彼妻,確保你注意到我們已經(jīng)在ViewTarget的構(gòu)造方法里傳遞了我們的定制view:new ViewTarget<FutureStudioView, GlideDrawable>(customView)澳骤。
12、設(shè)置監(jiān)聽(tīng)請(qǐng)求接口
首先摊册,創(chuàng)建一個(gè)listener作為一個(gè)字段對(duì)象茅特,避免被垃圾回收:
private RequestListener<String, GlideDrawable> requestListener
= new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String model,
Target<GlideDrawable> target, boolean isFirstResource) {
// todo log exception
// important to return false so the error placeholder can be placed
//加載異常
return false;
}
@Override
public boolean onResourceReady(GlideDrawable resource, String model,
Target<GlideDrawable> target, boolean isFromMemoryCache,
boolean isFirstResource) {
//加載成功
//view.setImageDrawable(resource);
return false;
}
};
在onException方法中白修,你可以抓取問(wèn)題兵睛,并決定你需要做什么祖很,比如記錄日志假颇。如果Glide應(yīng)當(dāng)處理這個(gè)后果笨鸡,比如顯示一個(gè)出錯(cuò)占位圖形耗,在onException方法中返回false是很重要的辙浑。
Glide
.with( context )
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.listener( requestListener )
.error( R.drawable.cupcake )
.into( imageViewPlaceholder );
只有在listener的onException方法里返回false,R.drawable.cupcake才會(huì)顯示出來(lái)已卸。
13累澡、設(shè)置取消或恢復(fù)請(qǐng)求
以下兩個(gè)方法是為了保證用戶界面的滑動(dòng)流暢而設(shè)計(jì)的愧哟。當(dāng)在ListView中加載圖片的時(shí)候,如果用戶滑動(dòng)ListView的時(shí)候繼續(xù)加載圖片霞赫,就很有可能造成滑動(dòng)不流暢端衰、卡頓的現(xiàn)象旅东,這是由于Activity需要同時(shí)處理滑動(dòng)事件以及Glide加載圖片抵代。Glide為我們提供了這兩個(gè)方法荤牍,讓我們可以在ListView等滑動(dòng)控件滑動(dòng)的過(guò)程中控制Glide停止加載或繼續(xù)加載参淫,可以有效的保證界面操作的流暢涎才。
//當(dāng)列表在滑動(dòng)的時(shí)候可以調(diào)用pauseRequests()取消請(qǐng)求
Glide.with(context).pauseRequests();
//當(dāng)列表滑動(dòng)停止時(shí)可以調(diào)用resumeRequests()恢復(fù)請(qǐng)求
Glide.with(context).resumeRequests();
// ListView滑動(dòng)時(shí)觸發(fā)的事件
lv.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
// 當(dāng)ListView處于滑動(dòng)狀態(tài)時(shí)耍铜,停止加載圖片棕兼,保證操作界面流暢
Glide.with(MainActivity.this).pauseRequests();
break;
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
// 當(dāng)ListView處于靜止?fàn)顟B(tài)時(shí)伴挚,繼續(xù)加載圖片
Glide.with(MainActivity.this).resumeRequests();
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
14颅眶、獲取緩存大小
new GetDiskCacheSizeTask(textView).execute(new File(getCacheDir(), DiskCache.Factory.DEFAULT_DISK_CACHE_DIR));
private class GetDiskCacheSizeTask extends AsyncTask<File, Long, Long> {
private final TextView resultView;
public GetDiskCacheSizeTask(TextView resultView) {
this.resultView = resultView;
}
@Override
protected void onPreExecute() {
resultView.setText("Calculating...");
}
@Override
protected void onProgressUpdate(Long... values) {
super.onProgressUpdate(values);
}
@Override
protected Long doInBackground(File... dirs) {
try {
long totalSize = 0;
for (File dir : dirs) {
publishProgress(totalSize);
totalSize += calculateSize(dir);
}
return totalSize;
} catch (RuntimeException ex) {
final String message = String.format("Cannot get size of %s: %s", Arrays.toString(dirs), ex);
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
resultView.setText("error");
Toast.makeText(resultView.getContext(), message, Toast.LENGTH_LONG).show();
}
});
}
return 0L;
}
@Override
protected void onPostExecute(Long size) {
String sizeText = android.text.format.Formatter.formatFileSize(resultView.getContext(), size);
resultView.setText(sizeText);
}
private long calculateSize(File dir) {
if (dir == null) return 0;
if (!dir.isDirectory()) return dir.length();
long result = 0;
File[] children = dir.listFiles();
if (children != null)
for (File child : children)
result += calculateSize(child);
return result;
}
}
15涛酗、清除內(nèi)存緩存
//可以在UI主線程中進(jìn)行
Glide.get(this).clearMemory();
16肃续、清除磁盤(pán)緩存
//需要在子線程中執(zhí)行
Glide.get(this).clearDiskCache();
17饿凛、圖片裁剪卵洗、模糊忌怎、濾鏡等處理
變換
在圖片顯示出之前可以對(duì)圖片進(jìn)行變換處理榴啸。例如鸥印,如果你的app需要顯示一張灰度圖库说,但只能獲取到一個(gè)原始全色彩的版本潜的,你可以使用一個(gè)變換去將圖片從有明艷色彩的版本轉(zhuǎn)換成慘淡的黑白版啰挪。不要誤會(huì)我們亡呵,變換不僅限于顏色锰什。你可以改變圖片的很多屬性:大小汁胆、邊框沦泌、色彩、像素點(diǎn),等等回挽!在之前介紹用Glide調(diào)整圖片大小時(shí)千劈,已經(jīng)介紹了自帶的兩個(gè)
變換fitCenter和 centerCrop墙牌。這兩個(gè)方案都有一個(gè)顯著的特征喜滨,他們有他們自己的Glide轉(zhuǎn)換方法虽风,所以辜膝,這篇文章不再介紹了厂抖。
實(shí)現(xiàn)自己的變換
為了實(shí)現(xiàn)你自己自定義的變換忱辅,你需要?jiǎng)?chuàng)建一個(gè)新的類去實(shí)現(xiàn)變換接口耕蝉。這個(gè)方法需要實(shí)現(xiàn)的內(nèi)容還是相當(dāng)復(fù)雜的垒在,你需要深入探索Glide的內(nèi)部結(jié)構(gòu)才能讓其工作好场躯。如果你只是想要常規(guī)的圖片(不包括Gif和視頻)變換踢关,我們建議只要處理抽象的BitmapTransformation類签舞。它簡(jiǎn)化了相當(dāng)多的實(shí)現(xiàn),能覆蓋95%的使用范圍吠架。
所以傍药,讓我們先看一下BitmapTransformation實(shí)現(xiàn)的一個(gè)例子拐辽。用Renderscript去模糊圖片俱诸。我們可以用之前用過(guò)的代碼去實(shí)現(xiàn)一個(gè)Glide變換乙埃。我們的框架必須繼承BitmapTransformation類:
public class BlurTransformation extends BitmapTransformation {
public BlurTransformation(Context context) {
super( context );
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
return null; // todo
}
@Override
public String getId() {
return null; // todo
}
}
現(xiàn)在介袜,我們用前面文章的代碼出吹,借助Renderscript來(lái)實(shí)現(xiàn)圖片的模糊處理捶牢。
public class BlurTransformation extends BitmapTransformation {
private RenderScript rs;
public BlurTransformation(Context context) {
super( context );
rs = RenderScript.create( context );
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
Bitmap blurredBitmap = toTransform.copy( Bitmap.Config.ARGB_8888, true );
// Allocate memory for Renderscript to work with
Allocation input = Allocation.createFromBitmap(
rs,
blurredBitmap,
Allocation.MipmapControl.MIPMAP_FULL,
Allocation.USAGE_SHARED
);
Allocation output = Allocation.createTyped(rs, input.getType());
// Load up an instance of the specific script that we want to use.
ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setInput(input);
// Set the blur radius
script.setRadius(10);
// Start the ScriptIntrinisicBlur
script.forEach(output);
// Copy the output to the blurred bitmap
output.copyTo(blurredBitmap);
toTransform.recycle();
return blurredBitmap;
}
@Override
public String getId() {
return "blur";
}
}
getId()方法為這個(gè)變換描述了一個(gè)獨(dú)有的識(shí)別渐排。Glide使用那個(gè)關(guān)鍵字作為緩存系統(tǒng)的一部分驯耻。防止出現(xiàn)異常問(wèn)題可缚,確保其唯一帘靡。
應(yīng)用一個(gè)簡(jiǎn)單的變換
Glide有兩個(gè)不同的方式進(jìn)行變換描姚。第一個(gè)是傳遞一個(gè)你的類的實(shí)例作為.transform()的參數(shù)。不管是圖片還是gif谒主,都可以進(jìn)行變換。另一個(gè)則是使用.bitmapTransform()榛斯,
它只接受bitmap的變換驮俗。由于我們的實(shí)現(xiàn)都是基于bitmap王凑,我們可以使用第一個(gè):
Glide.with(context)
.load(eatFoodyImages[0])
.transform(new BlurTransformation(context))
//.bitmapTransform(new BlurTransformation(context)) // this would work too!
.into(imageView1);
這足夠讓Glide從網(wǎng)絡(luò)下載的圖片自動(dòng)實(shí)現(xiàn)模糊算法索烹。非常有用百姓!
實(shí)現(xiàn)多重變換
通常垒拢,Glide的流接口(fluent interface)允許方法被連接在一起火惊,然而變換并不是這樣的屹耐。確保你只調(diào)用.transform()或者.bitmapTransform()一次张症,不然俗他,之前的設(shè)置將會(huì)被覆蓋兆衅!然而,你可以通過(guò)傳遞多個(gè)轉(zhuǎn)換對(duì)象當(dāng)作參數(shù)到.transform()(或者.bitmapTransform())中來(lái)進(jìn)行多重變換:
Glide.with(context)
.load(eatFoodyImages[1])
.transform(new GreyscaleTransformation(context), new BlurTransformation(context))
.into(imageView2);
在這段代碼中危融,我們先對(duì)圖片進(jìn)行了灰度變換吉殃,然后模糊處理蛋勺。Glide會(huì)為你自動(dòng)進(jìn)行兩個(gè)轉(zhuǎn)換抱完。牛逼吧巧娱!
提示:當(dāng)你使用變換的時(shí)候禁添,你不能使用.centerCrop()或者.fitCenter()庙楚。
Glide的變換集合
如果你已經(jīng)對(duì)你的app里要用什么變換有了想法馒闷,在花點(diǎn)時(shí)間看看下面的庫(kù)吧:Glide-transformations(https://github.com/wasabeef/glide-transformations)纳账。它提供了許多變換的集合疏虫。值得去看一下你的idea是否已經(jīng)被實(shí)現(xiàn)了卧秘。
這個(gè)庫(kù)有2個(gè)不同版本翅敌。擴(kuò)展庫(kù)包括更多的變換蚯涮,并且是用手機(jī)的GPU進(jìn)行計(jì)算。需要一個(gè)額外的依賴泪蔫,所以這兩個(gè)版本的設(shè)置還有點(diǎn)不一樣撩荣。你應(yīng)當(dāng)看看支持的變換的列表婿滓,再?zèng)Q定你需要用哪個(gè)版本。
Glide變換的設(shè)置
設(shè)置是很簡(jiǎn)單的橘券!對(duì)于基本版旁舰,你可以在你的build.gradle里加一行:
dependencies {
compile 'jp.wasabeef:glide-transformations:2.0.0'
}
如果你想要使用GPU變換:
repositories {
jcenter()
mavenCentral()
}
dependencies {
compile 'jp.wasabeef:glide-transformations:2.0.0'
compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.3.0'
}
Glide變換的使用
在你同步了Android Studio的builde.gradle文件后毯焕,你已經(jīng)可以進(jìn)行使用變換集合了磺樱。使用方式與使用自定義變換一樣。假如我們要用glide變換集合去模糊圖片:
Glide
.with( context )
.load( eatFoodyImages[2] )
.bitmapTransform( new jp.wasabeef.glide.transformations.BlurTransformation( context, 25 ) )
.into( imageView3 );
你也可以像上面一樣應(yīng)用一組變換芜辕。一個(gè)單獨(dú)的變換或者一組變換侵续,.bitmapTransform()都可以接受状蜗!
示例:圓角處理
Glide.with(mContext)
.load(R.drawable.image_example)
.bitmapTransform(new RoundedCornersTransformation(mContext, 30, 0, RoundedCornersTransformation.CornerType.BOTTOM))
.into(imageView);
可實(shí)現(xiàn)Transformation接口轧坎,進(jìn)行更靈活的圖片處理眶根,如進(jìn)行簡(jiǎn)單地圓角處理属百。
public class RoundedCornersTransformation implements Transformation<Bitmap> {
private BitmapPool mBitmapPool;
private int mRadius;
public RoundedCornersTransformation(Context context, int mRadius) {
this(Glide.get(context).getBitmapPool(), mRadius);
}
public RoundedCornersTransformation(BitmapPool mBitmapPool, int mRadius) {
this.mBitmapPool = mBitmapPool;
this.mRadius = mRadius;
}
@Override
public Resource<Bitmap> transform(Resource<Bitmap> resource, int outWidth, int outHeight) {
//從其包裝類中拿出Bitmap
Bitmap source = resource.get();
int width = source.getWidth();
int height = source.getHeight();
Bitmap result = mBitmapPool.get(width, height, Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
canvas.drawRoundRect(new RectF(0, 0, width, height), mRadius, mRadius, paint);
//返回包裝成Resource的最終Bitmap
return BitmapResource.obtain(result, mBitmapPool);
}
@Override
public String getId() {
return "com.wiggins.glide.widget.GlideCircleTransform(radius=" + mRadius + ")";
}
}
自定義圖片處理時(shí)為了避免創(chuàng)建大量Bitmap以及減少GC厌丑,可以考慮重用Bitmap怒竿,這就需要使用BitmapPool耕驰,例如從Bitmap池中取一個(gè)Bitmap朦肘,用這個(gè)Bitmap生成一個(gè)Canvas,然后在這個(gè)Canvas上畫(huà)初始的Bitmap并使用Matrix媒抠、Paint或者Shader處理這張圖片趴生。為了有效并正確重用Bitmap需要遵循以下三條準(zhǔn)則:
- 永遠(yuǎn)不要把transform()傳給你的原始resource或原始Bitmap給recycle()了苍匆,更不要放回BitmapPool锉桑,因?yàn)檫@些都自動(dòng)完成了民轴。值得注意的是后裸,任何從BitmapPool取出的用于自定義圖片變換的輔助Bitmap微驶,如果不經(jīng)過(guò)transform()方法返回因苹,就必須主動(dòng)放回BitmapPool或者調(diào)用recycle()回收扶檐。
- 如果你從BitmapPool拿出多個(gè)Bitmap或不使用你從BitmapPool拿出的一個(gè)Bitmap款筑,一定要返回extras給BitmapPool奈梳。
- 如果你的圖片處理沒(méi)有替換原始resource(例如由于一張圖片已經(jīng)匹配了你想要的尺寸攘须,你需要提前返回)于宙,transform()方法就返回原始resource或原始Bitmap限煞。例如:
private static class MyTransformation extends BitmapTransformation {
public MyTransformation(Context context) {
super(context);
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
Bitmap result = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888);
// 如果BitmapPool中找不到符合該條件的Bitmap署驻,get()方法會(huì)返回null旺上,就需要我們自己創(chuàng)建Bitmap了
if (result == null) {
// 如果想讓Bitmap支持透明度宣吱,就需要使用ARGB_8888
result = Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.ARGB_8888);
}
//創(chuàng)建最終Bitmap的Canvas.
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setAlpha(128);
// 將原始Bitmap處理后畫(huà)到最終Bitmap中
canvas.drawBitmap(toTransform, 0, 0, paint);
// 由于我們的圖片處理替換了原始Bitmap征候,就return我們新的Bitmap就行疤坝。
// Glide會(huì)自動(dòng)幫我們回收原始Bitmap跑揉。
return result;
}
@Override
public String getId() {
// Return some id that uniquely identifies your transformation.
return "com.wiggins.glide.MyTransformation";
}
}
七历谍、GlideModule使用
GlideModule是一個(gè)接口,全局改變Glide行為的一種方式印蔬,通過(guò)全局GlideModule配置Glide(GlideModule#applyOptions
)扛点,用GlideBuilder設(shè)置選項(xiàng)陵究,用Glide注冊(cè)ModelLoader(GlideModule#registerComponents
)等铜邮。你需要?jiǎng)?chuàng)建Glide的實(shí)例松蒜,來(lái)訪問(wèn)GlideBuilder秸苗≡送剩可以通過(guò)創(chuàng)建一個(gè)公共的類秸讹,實(shí)現(xiàn)GlideModule的接口來(lái)定制Glide弧可。所有的GlideModule實(shí)現(xiàn)類必須是public的劣欢,并且只擁有一個(gè)空的構(gòu)造器凿将,以便在Glide延遲初始化時(shí)丸相,可以通過(guò)反射將它們實(shí)例化。
1座硕、自定義一個(gè)GlideModule
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// Apply options to the builder here.
}
@Override
public void registerComponents(Context context, Glide glide) {
// register ModelLoaders here.
}
}
2华匾、AndroidManifest.xml注冊(cè)
<meta-data
android:name="com.wiggins.glide.MyGlideModule"
android:value="GlideModule" />//value是固定的
3蜘拉、混淆處理
-keepnames class com.wiggins.glide.MyGlideModule
# or more generally
#-keep public class * implements com.bumptech.glide.module.GlideModule
4旭旭、多個(gè)GlideModule沖突問(wèn)題
GlideModule不能指定調(diào)用順序持寄,所以應(yīng)該避免不同的GlideModule之間有沖突的選項(xiàng)設(shè)置稍味,可以考慮將所有的設(shè)置都放到一個(gè)GlideModule里面模庐,或者排除掉某個(gè)manifest文件的某個(gè)Module赖欣。
<meta-data
android:name="com.wiggins.glide.MyGlideModule"
tools:node="remove" />
5、更改Glide配置
已經(jīng)知道如何使用Glide module去自定義Glide》嘌Γ現(xiàn)在我們看一下接口的第一個(gè)方法:applyOptions(Context context, GlideBuilder builder)
违寿。這個(gè)方法將GlideBuilder的對(duì)象當(dāng)作參數(shù)藤巢,并且是void返回類型掂咒,所以你在這個(gè)方法里能調(diào)用GlideBuilder可以用的方法。
.setMemoryCache(MemoryCache memoryCache)
.setBitmapPool(BitmapPool bitmapPool)
.setDiskCache(DiskCache.Factory diskCacheFactory)
.setDiskCacheService(ExecutorService service)
.setResizeService(ExecutorService service)
.setDecodeFormat(DecodeFormat decodeFormat)
顯而易見(jiàn)温圆,GlideBuilder對(duì)象可以讓你訪問(wèn)到Glide的核心部分岁歉。使用文中的方法锅移,你可以改變磁盤(pán)緩存非剃、內(nèi)存緩存等等努潘。
5.1 設(shè)置Glide內(nèi)存緩存大小
MemoryCache用來(lái)把resources緩存在內(nèi)存里疯坤,以便能馬上能拿出來(lái)顯示压怠。默認(rèn)情況下Glide使用LruResourceCache菌瘫,我們可以通過(guò)它的構(gòu)造器設(shè)置最大緩存內(nèi)存大小。
//獲取系統(tǒng)分配給應(yīng)用的總內(nèi)存大小
int maxMemory = (int) Runtime.getRuntime().maxMemory();
//設(shè)置圖片內(nèi)存緩存占用八分之一
int memoryCacheSize = maxMemory / 8;
//設(shè)置內(nèi)存緩存大小
builder.setMemoryCache(new LruResourceCache(memoryCacheSize));
獲取默認(rèn)的使用內(nèi)存
//MemoryCache和BitmapPool的默認(rèn)大小由MemorySizeCalculator類決定栖忠,MemorySizeCalculator會(huì)根據(jù)給定屏幕大小可用內(nèi)存算出合適的緩存大小庵寞,
這也是推薦的緩存大小捐川,我們可以根據(jù)這個(gè)推薦大小做出調(diào)整
MemorySizeCalculator calculator = new MemorySizeCalculator(context);
int defaultMemoryCacheSize = calculator.getMemoryCacheSize();
int defaultBitmapPoolSize = calculator.getBitmapPoolSize();
5.2 設(shè)置Glide磁盤(pán)緩存大小
//方式一
//指定的是數(shù)據(jù)的緩存地址
File cacheDir = context.getExternalCacheDir();
//最多可以緩存多少字節(jié)的數(shù)據(jù)
int diskCacheSize = 1024 * 1024 * 30;
//設(shè)置磁盤(pán)緩存大小
builder.setDiskCache(new DiskLruCacheFactory(cacheDir.getPath(), "glide", diskCacheSize));
//方式二
//存放在data/data/xxxx/cache/
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "glide", diskCacheSize));
//方式三
//存放在外置文件
builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, "glide", diskCacheSize));
5.3 設(shè)置圖片解碼格式
默認(rèn)格式RGB_565相對(duì)于ARGB_8888的4字節(jié)/像素可以節(jié)省一半的內(nèi)存古沥,但是圖片質(zhì)量就沒(méi)那么高了尊浓,而且不支持透明度纯衍。
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
5.4 設(shè)置BitmapPool緩存內(nèi)存大小
Bitmap池用來(lái)允許不同尺寸的Bitmap被重用瓦堵,這可以顯著地減少因?yàn)閳D片解碼像素?cái)?shù)組分配內(nèi)存而引發(fā)的垃圾回收歌亲。默認(rèn)情況下Glide使用LruBitmapPool作為Bitmap池惋鸥,LruBitmapPool采用Lru算法保存最近使用的尺寸的Bitmap悍缠,我們可以通過(guò)它的構(gòu)造器設(shè)置最大緩存內(nèi)存大小滤港。
builder.setBitmapPool(new LruBitmapPool(memoryCacheSize));
5.5 設(shè)置用來(lái)檢索cache中沒(méi)有的Resource的ExecutorService
//為了使縮略圖請(qǐng)求正確工作溅漾,實(shí)現(xiàn)類必須把請(qǐng)求根據(jù)Priority優(yōu)先級(jí)排好序
builder.setDiskCacheService(ExecutorService service);
builder.setResizeService(ExecutorService service);
6添履、集成網(wǎng)絡(luò)框架
Glide包含一些小的暮胧、可選的集成庫(kù)瞎饲,目前Glide集成庫(kù)當(dāng)中包含了訪問(wèn)網(wǎng)絡(luò)操作的Volley和OkHttp妄田,也可以通過(guò)Glide的ModelLoader接口自己寫(xiě)網(wǎng)絡(luò)請(qǐng)求。
6.1 將OkHttp集成到Glide當(dāng)中
a)添加依賴
dependencies {
//OkHttp 2.x
compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar'
compile 'com.squareup.okhttp:okhttp:2.7.5'
//OkHttp 3.x
compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
compile 'com.squareup.okhttp3:okhttp:3.2.0'
}
結(jié)尾的@aar可以將庫(kù)中的AndroidManifest.xml文件一起導(dǎo)出,Gradle自動(dòng)合并必要的GlideModule到AndroidManifest.xml启具,然后使用所集成的網(wǎng)絡(luò)連接拷沸,所以不用再
將以下文本添加到項(xiàng)目的AndroidManifest.xml文件中:
<meta-data
android:name="com.bumptech.glide.integration.okhttp.OkHttpGlideModule"
android:value="GlideModule" />
b)創(chuàng)建OkHttp集成庫(kù)的GlideModule
<meta-data
android:name="com.wiggins.glide.okhttp.OkHttpGlideModule"
android:value="GlideModule" />
c)混淆配置
-keep class com.wiggins.glide.okhttp.OkHttpGlideModule
#or
-keep public class * implements com.bumptech.glide.module.GlideModule
注意:
a.OkHttp 2.x和OkHttp 3.x需使用不同的集成庫(kù)撞芍;
b.Gradle會(huì)自動(dòng)將OkHttpGlideModule合并到應(yīng)用的manifest文件中序无;
c.如果你沒(méi)有對(duì)所有的GlideModule配置混淆規(guī)則(即沒(méi)有使用-keep public class * implements com.bumptech.glide.module.GlideModule
)帝嗡,則需要把OkHttp的GlideModule進(jìn)行混淆配置:-keep class com.wiggins.glide.okhttp.OkHttpGlideModule
丈探。
6.2 將Volley集成到Glide當(dāng)中
a)添加依賴
dependencies {
compile 'com.github.bumptech.glide:volley-integration:1.4.0@aar'
compile 'com.mcxiaoke.volley:library:1.0.8'
}
b)創(chuàng)建Volley集成庫(kù)的GlideModule
<meta-data
android:name="com.wiggins.glide.volley.VolleyGlideModule"
android:value="GlideModule" />
c)混淆配置
-keep class com.wiggins.glide.volley.VolleyGlideModule
#or
-keep public class * implements com.bumptech.glide.module.GlideModule
7、替換Glide組件拔莱、使用ModelLoader自定義數(shù)據(jù)源
7.1碗降、替換Glide組件
替換Glide組件功能需要在自定義模塊的registerComponents()方法中加入具體的替換邏輯。相比于更改Glide配置塘秦,替換Glide組件這個(gè)功能的難度就明顯大了不少讼渊。Glide中的組件非常繁多尊剔,也非常復(fù)雜挨稿,但其實(shí)大多數(shù)情況下并不需要我們?nèi)プ鍪裁刺鎿Q。不過(guò)疲陕,有一個(gè)組件卻有著比較大的替換需求,那就是Glide的HTTP通訊組件诅岩。
替換Glide組件功能需要在自定義模塊的GlideModule#registerComponents(Context context, Glide glide)
方法中加入具體的替換邏輯,需要在方法中調(diào)用Glide#register(Class<T> modelClass, Class<Y> resourceClass, ModelLoaderFactory<T, Y> factory)
,其中modelClass表示 數(shù)據(jù)模型的類型懒棉,一般為GlideUrl
,Glide.with(context).load("url")底層就是將轉(zhuǎn)化為了GlideUrl;resourceClass表示URL所指向的資源的類型倔韭,一般為InputStream。
默認(rèn)情況下,Glide使用的是基于原生HttpURLConnection進(jìn)行訂制的HTTP通訊組件秧荆,但是現(xiàn)在大多數(shù)的Android開(kāi)發(fā)者都更喜歡使用OkHttp,因此將Glide中的HTTP通訊組件修改成OkHttp的這個(gè)需求比較常見(jiàn),那么今天我們也會(huì)以這個(gè)功能來(lái)作為例子進(jìn)行講解。
Model:數(shù)據(jù)模型梧疲,一般為URL字符串
Resource:URL所指向的網(wǎng)絡(luò)資源
它主要和三個(gè)接口有關(guān):
ModelLoader:數(shù)據(jù)模型Loader,將任意復(fù)雜的數(shù)據(jù)模型轉(zhuǎn)化為具體的可被DataFetcher使用的數(shù)據(jù)類型该互。需要返回一個(gè)從url拉取數(shù)據(jù)的DataFetcher胰丁,泛型類型為上面指定的類型机蔗。
public interface ModelLoader<T, Y> {
DataFetcher<Y> getResourceFetcher(T var1, int var2, int var3);
}
ModelLoaderFactory:ModelLoader的工廠幔嗦,build方法返回ModelLoader嬉挡。
public interface ModelLoaderFactory<T, Y> {
ModelLoader<T, Y> build(Context var1, GenericLoaderFactory var2);
void teardown();
}
DataFetcher:獲取 由model表示的resource要解碼的數(shù)據(jù)
public interface DataFetcher<T> {
T loadData(Priority var1) throws Exception; //重要方法因谎,返回給glide的數(shù)據(jù)
void cleanup();
String getId();
void cancel();
}
默認(rèn)地河爹,Glide內(nèi)部使用標(biāo)準(zhǔn)的HTTPUrlConnection去下載圖片。Glide也提供兩個(gè)集成庫(kù)。這三個(gè)方法優(yōu)點(diǎn)是在安全設(shè)置上都是相當(dāng)嚴(yán)格的侄刽。唯一的不足之處是當(dāng)你從一個(gè)使用HTTPS杂彭,還是self-signed的服務(wù)器下載圖片時(shí),Glide并不會(huì)下載或者顯示圖片醉箕,因?yàn)閟elf-signed認(rèn)證會(huì)被認(rèn)為存在安全問(wèn)題姻报。
首先創(chuàng)建一個(gè)跳過(guò)SSL認(rèn)證的OkHttpClient
public class UnsafeOkHttpClient {
public static OkHttpClient getUnsafeOkHttpClient() {
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
//return null;//刪除這行,多謝下面評(píng)論的幾位小伙伴指出空指針問(wèn)題,并提供解決方案。
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setSslSocketFactory(sslSocketFactory);
okHttpClient.setProtocols(Arrays.asList(Protocol.HTTP_1_1));
okHttpClient.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
return okHttpClient;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
創(chuàng)建的OkHttpClient關(guān)閉了所有的SSL認(rèn)證檢查荞驴。
集成到 Glide
Glide的OkHTTP集成庫(kù)做的都是一樣的工作,所以我們可以跟隨他們的步驟悲雳。首先透典,我們需要在GlideModule里聲明我們的定制税弃。你應(yīng)該想到西壮,我們需要在registerComponents()方法里做適配。我們可以調(diào)用.register()方法去交換Glide基礎(chǔ)構(gòu)成。Glide使用一個(gè)ModelLoader去鏈接到數(shù)據(jù)模型創(chuàng)建一個(gè)具體的數(shù)據(jù)類型。我們的例子中,我們需要?jiǎng)?chuàng)建一個(gè)ModelLoader铣除,它連接到一個(gè)URL敲长,通過(guò)GlideUrl類響應(yīng)并轉(zhuǎn)化為輸入流泽铛。Glide需要能夠創(chuàng)建我們的新ModelLoader的實(shí)例弛随,所以我們?cè)?register()方法中傳入一個(gè)工廠:
public class UnsafeOkHttpGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder glideBuilder) {
}
@Override
public void registerComponents(Context context, Glide glide) {
glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
@Override
public DataFetcher<InputStream> getResourceFetcher(GlideUrl glideUrl, int i, int i1) {
return new OkHttpStreamFetcher(client, glideUrl);
}
private final OkHttpClient client;
public OkHttpUrlLoader(OkHttpClient client) {
this.client = client;
}
/**
* The default factory for {@link OkHttpUrlLoader}s.
*/
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private static volatile OkHttpClient internalClient;
private OkHttpClient client;
private static OkHttpClient getInternalClient() {
if (internalClient == null) {
synchronized (Factory.class) {
if (internalClient == null) {
internalClient = UnsafeOkHttpClient.getUnsafeOkHttpClient();
}
}
}
return internalClient;
}
/**
* Constructor for a new Factory that runs requests using a static singleton client.
*/
public Factory() {
this(getInternalClient());
}
/**
* Constructor for a new Factory that runs requests using given client.
*/
public Factory(OkHttpClient client) {
this.client = client;
}
@Override
public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
return new OkHttpUrlLoader(client);
}
@Override
public void teardown() {
// Do nothing, this instance doesn't own the client.
}
}
}
public class OkHttpStreamFetcher implements DataFetcher<InputStream> {
private final OkHttpClient client;
private final GlideUrl url;
private InputStream stream;
private ResponseBody responseBody;
public OkHttpStreamFetcher(OkHttpClient client, GlideUrl url) {
this.client = client;
this.url = url;
}
@Override
public InputStream loadData(Priority priority) throws Exception {
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();
Response response = client.newCall(request).execute();
responseBody = response.body();
if (!response.isSuccessful()) {
throw new IOException("Request failed with code: " + response.code());
}
long contentLength = responseBody.contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
return stream;
}
@Override
public void cleanup() {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignored
}
}
if (responseBody != null) {
responseBody.close();
}
}
@Override
public String getId() {
return url.getCacheKey();
}
@Override
public void cancel() {
}
}
7.2链烈、使用ModelLoader自定義數(shù)據(jù)源
如果需要根據(jù)不同的要求請(qǐng)求不同尺寸不同質(zhì)量的圖片码荔,這時(shí)我們就可以使用自定義數(shù)據(jù)源。
a)定義處理URL接口
public interface IDataModel {
String buildDataModelUrl(int width, int height);
}
b)實(shí)現(xiàn)不同的處理URL接口
public class JpgDataModel implements IDataModel {
private String dataModelUrl;
public JpgDataModel(String dataModelUrl) {
this.dataModelUrl = dataModelUrl;
}
@Override
public String buildDataModelUrl(int width, int height) {
//http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg?imageView2/1/w/200/h/200/format/jpg
return String.format("%s?imageView2/1/w/%d/h/%d/format/jpg", dataModelUrl, width, height);
}
}
c)實(shí)現(xiàn)ModelLoader
public class MyDataLoader extends BaseGlideUrlLoader<IDataModel> {
public MyDataLoader(Context context) {
super(context);
}
public MyDataLoader(ModelLoader<GlideUrl, InputStream> urlLoader) {
super(urlLoader, null);
}
@Override
protected String getUrl(IDataModel model, int width, int height) {
return model.buildDataModelUrl(width, height);
}
public static class Factory implements ModelLoaderFactory<IDataModel, InputStream> {
@Override
public ModelLoader<IDataModel, InputStream> build(Context context, GenericLoaderFactory factories) {
return new MyDataLoader(factories.buildModelLoader(GlideUrl.class, InputStream.class));
}
@Override
public void teardown() {
}
}
}
d)根據(jù)不同的要求采用不同的策略加載圖片
//加載jpg圖片
Glide.with(this).using(new MyDataLoader(this)).load(new JpgDataModel(imageUrl)).into(imageView);
e)如何跳過(guò).using()
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
@Override
public void registerComponents(Context context, Glide glide) {
glide.register(IDataModel.class, InputStream.class, new MyDataLoader.Factory());
}
}
//加載jpg圖片
Glide.with(this).load(new JpgDataModel(imageUrl)).into(imageView);
八媒峡、特點(diǎn)
使用簡(jiǎn)單酬滤;
可配置度及自適應(yīng)程度高;
支持常見(jiàn)圖片格式如jpg蔬墩、png、gif、webp等;
支持多種數(shù)據(jù)源如網(wǎng)絡(luò)、本地、資源、Uri等;
高效緩存策略(支持Memory和Disk圖片緩存千绪,默認(rèn)Bitmap格式采用RGB_565內(nèi)存使用至少減少一半)炸茧;
生命周期集成(根據(jù)Context/Activity/Fragment/FragmentActivity生命周期自動(dòng)管理請(qǐng)求)控漠;
高效處理Bitmap(使用BitmapPool使Bitmap復(fù)用,主動(dòng)調(diào)用recycle回收需要回收的Bitmap,減小系統(tǒng)回收壓力)。
九、優(yōu)點(diǎn)
多樣化媒體加載,支持Gif、WebP、Video及縮略圖以等類型厨剪;
生命周期集成,提供多種方式與生命周期綁定,可以更好的讓加載圖片請(qǐng)求的生命周期動(dòng)態(tài)管理起來(lái);
高效的緩存策略
- 支持Memory和Disk圖片緩存;
- 緩存相應(yīng)大小的圖片尺寸(Picasso只會(huì)緩存原始尺寸圖片,而Glide會(huì)根據(jù)你ImageView的大小來(lái)緩存相應(yīng)大小的圖片尺寸,因此Glide會(huì)比Picasso加載的速度要快)薯鳍;
- 內(nèi)存開(kāi)銷(xiāo)星骋邸(Picasso默認(rèn)的是ARGB_8888格式瞪讼,而Glide默認(rèn)的Bitmap格式是RGB_565格式希柿,這個(gè)內(nèi)存開(kāi)銷(xiāo)大約可以減小一半)挤悉。
十、缺點(diǎn)
使用方法復(fù)雜:由于Glide功能強(qiáng)大畏梆,所以使用的方法非常多捏卓,其源碼也相對(duì)的復(fù)雜蒜田;
包較大:Glide(v3.7.0)的大小約465kb梯捕。
十一锣笨、使用場(chǎng)景
需要更多的內(nèi)容表現(xiàn)形式(如Gif、縮略圖等)判哥;
更高的性能要求(緩存章钾、加載速度等)府寒。
十二祷蝌、特別說(shuō)明
1.ImageView的setTag問(wèn)題
問(wèn)題描述:如果使用Glide的into(imageView)為ImageView設(shè)置圖片的同時(shí)使用ImageView的setTag(final Object tag)方法糊啡,將會(huì)導(dǎo)致java.lang.IllegalArgumentException: You must not call setTag() on a view Glide is targeting異常稍算。因?yàn)镚lide的ViewTarget中通過(guò)view.setTag(tag)和view.getTag()標(biāo)記請(qǐng)求的褥紫,由于Android 4.0之前Tag存儲(chǔ)在靜態(tài)map里,如果Glide使用setTag(int key, final Object tag)方法標(biāo)記請(qǐng)求則可能會(huì)導(dǎo)致內(nèi)存泄露,所以Glide默認(rèn)使用view.setTag(tag)標(biāo)記請(qǐng)求竣灌,你就不能重復(fù)調(diào)用了坷随。
解決辦法:如果你需要為ImageView設(shè)置Tag类溢,必須使用setTag(int key, final Object tag)及getTag(int key)方法计贰,其中key必須是合法的資源id以確保key
的唯一性,典型做法就是在資源文件中聲明type="id"的item資源呛踊。
2.placeholder()導(dǎo)致的圖片變形問(wèn)題
問(wèn)題描述:使用.placeholder()方法在某些情況下會(huì)導(dǎo)致圖片顯示的時(shí)候出現(xiàn)圖片變形的情況。這是因?yàn)镚lide默認(rèn)開(kāi)啟的crossFade動(dòng)畫(huà)導(dǎo)致的TransitionDrawable繪制異常层坠,具體描述可以查看https://github.com/bumptech/glide/issues/363磅氨。根本原因就是你的placeholder圖片和你要加載顯示的圖片寬高比不一樣者蠕,而Android的
TransitionDrawable無(wú)法很好地處理不同寬高比的過(guò)渡問(wèn)題,這是Android也是Glide的Bug流济。
解決辦法:使用.dontAnimate()方法禁用過(guò)渡動(dòng)畫(huà),或者使用animate()方法自己寫(xiě)動(dòng)畫(huà)蟋恬,再或者自己修復(fù)TransitionDrawable的問(wèn)題。
3.ImageView的資源回收問(wèn)題
問(wèn)題描述:默認(rèn)情況下Glide會(huì)根據(jù)with()使用的Activity或Fragment的生命周期自動(dòng)調(diào)整資源請(qǐng)求以及資源回收出刷。但是如果有很占內(nèi)存的Fragment或Activity不銷(xiāo)毀而僅僅是隱藏視圖,那么這些圖片資源就沒(méi)辦法及時(shí)回收裸删,即使是GC的時(shí)候。
解決辦法:可以考慮使用WeakReference腹泌,如:
final WeakReference<ImageView> imageViewWeakReference = new WeakReference<>(imageView);
ImageView target = imageViewWeakReference.get();
if (target != null) {
Glide.with(context).load(uri).into(target);
}
4.由于Bitmap復(fù)用導(dǎo)致的在某些設(shè)備上圖片錯(cuò)亂的問(wèn)題
問(wèn)題描述: Glide默認(rèn)使用BitmapPool的方式對(duì)應(yīng)用中用到的Bitmap進(jìn)行復(fù)用涤躲,以減少頻繁的內(nèi)存申請(qǐng)和內(nèi)存回收瘾蛋,而且默認(rèn)使用的Bitmap模式為RGB565以減少內(nèi)存開(kāi)銷(xiāo)取董。但在某些設(shè)備上(通常在Galaxy系列5.X設(shè)備上很容易復(fù)現(xiàn))某些情況下會(huì)出現(xiàn)圖片加載錯(cuò)亂的問(wèn)題奥洼,具體詳見(jiàn)https://github.com/bumptech/glide/issues/601尉尾。原因初步確定是OpenGL紋理渲染異常吆豹。
解決辦法:GlideModule使用PREFER_ARGB_8888(Glide4.X已經(jīng)默認(rèn)使用該模式了)蘸拔,雖然內(nèi)存占用比RGB565更多一點(diǎn)宝剖,但可以更好地處理有透明度Bitmap的復(fù)用問(wèn)
題铃芦,或者禁用Bitmap復(fù)用setBitmapPool(new BitmapPoolAdapter())來(lái)修復(fù)這個(gè)問(wèn)題(不推薦這種處理方式)征唬。
5.異步線程完成后加載圖片的崩潰問(wèn)題
問(wèn)題描述:通常情況下異步線程會(huì)被約束在Activity生命周期內(nèi)年枕,所以異步線程完成后使用Glide加載圖片是沒(méi)有問(wèn)題的典格。但如果你的異步線程在Activity銷(xiāo)毀時(shí)沒(méi)
有取消掉防嗡,那么異步線程完成后Glide就無(wú)法為一個(gè)已銷(xiāo)毀的Activity加載圖片資源,拋出的異常如下(在with()方法中就進(jìn)行判斷并拋出異常):
java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
at com.bumptech.glide.manager.RequestManagerRetriever.assertNotDestroyed(RequestManagerRetriever.java:134)
at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:102)
at com.bumptech.glide.Glide.with(Glide.java:653)
at com.frank.glidedemo.TestActivity.onGetDataCompleted(TestActivity.java:23)
at com.frank.glidedemo.TestActivity.access$000(TestActivity.java:10)
at com.frank.glidedemo.TestActivity$BackgroundTask.onPostExecute(TestActivity.java:46)
at com.frank.glidedemo.TestActivity$BackgroundTask.onPostExecute(TestActivity.java:28)
at android.os.AsyncTask.finish(AsyncTask.java:632)
at android.os.AsyncTask.access$600(AsyncTask.java:177)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:157)
at android.app.ActivityThread.main(ActivityThread.java:5356)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081)
at dalvik.system.NativeStart.main(Native Method)
解決辦法:正確管理BackgroundThreads(異步線程)台谊,當(dāng)Activity停止或銷(xiāo)毀時(shí)阶冈,停止所有相關(guān)的異步線程及后續(xù)的UI操作瓮钥,或者加載前使用isFinishing()
或isDestroyed()進(jìn)行限制(不建議這種處理方式)跃惫。