Glide的強大和靈活相信不需要多介紹了
本文使用Glide版本爲4.8.0,因爲使用的Java語言進行開發,涉及到使用Kotlin的部分還請參考官方文檔
SDK要求
- 最小SDK版本需要使用
API 14
(或者更高版本) -
Complie SDK Version
需要使用API 27
(或者更高版本) -
Glide
使用的SupportLibrary
版本是27,如果需要不同的SupportLibrary
版本可以用exclude
將Glide
的SupportLibrary
從依賴中去掉,具體的在集成時說明
一、集成
在項目的build.gradle
文件中添加google()
倉庫
repositories {
google()
//or maven { url 'https://maven.google.com' }
jcenter()
}
在要使用的Glide
的module
中添加以下代碼
implementation('com.github.bumptech.glide:glide:4.8.0')
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
//如果使用了在Kotlin中使用了Glide註解,需要引入kapt依賴代替annotationProcessor依賴
//kapt 'com.github.bumptech.glide:compiler:4.8.0'
若使用了不是27的SupportLibrary
版本,使用以下代碼
implementation('com.github.bumptech.glide:glide:4.8.0') {
exclude group: "com.android.support"
}
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
使用了不同版本的SupportLibrary
可能導致一些運行時異常,
java.lang.NoSuchMethodError: No static method getFont(Landroid/content/Context;ILandroid/util/TypedValue;ILandroid/widget/TextView;)Landroid/graphics/Typeface; in class Landroid/support/v4/content/res/ResourcesCompat; or its super classes (declaration of 'android.support.v4.content.res.ResourcesCompat'
at android.support.v7.widget.TintTypedArray.getFont(TintTypedArray.java:119)
儘量避免使用@aar
,如果需要這麼做,需要添加transitive=true
確保有所需要的類。
dependencies {
implementation ("com.github.bumptech.glide:glide:4.8.0@aar") {
transitive = true
}
}
@aar
是Gradle
的限制符,默認會去除使用到得依賴。如果不添加transitive=true
的話將會導致運行時異常。
java.lang.NoClassDefFoundError: com.bumptech.glide.load.resource.gif.GifBitmapProvider
at com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder.<init>(ByteBufferGifDecoder.java:68)
at com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder.<init>(ByteBufferGifDecoder.java:54)
at com.bumptech.glide.Glide.<init>(Glide.java:327)
at com.bumptech.glide.GlideBuilder.build(GlideBuilder.java:445)
at com.bumptech.glide.Glide.initializeGlide(Glide.java:257)
at com.bumptech.glide.Glide.initializeGlide(Glide.java:212)
at com.bumptech.glide.Glide.checkAndInitializeGlide(Glide.java:176)
at com.bumptech.glide.Glide.get(Glide.java:160)
at com.bumptech.glide.Glide.getRetriever(Glide.java:612)
at com.bumptech.glide.Glide.with(Glide.java:684)
權限
這個不需要多說,如果加載網絡圖片必然需要網絡權限,這個還是根據具體的使用情況而定。
<uses-permission android:name="android.permission.INTERNET" />
Glide
添加了鏈接監聽(Connectivity Monitoring),通過網絡加載圖片時,Glide可以監聽鏈接狀態並在重新鏈接到網絡時重啓之前失敗的請求。使用鏈接監聽我們需要添加ACCESS_NETWORK_STATE
權限,Glide將自動監聽連接狀態,不需要額外的改動。
如果需要使用ExternalPreferredCacheDiskCacheFactory
將Glide的緩存存放到SD卡上,還需要添加READ_EXTERNAL_STORAGE
和WRITE_EXTERNAL_STORAGE
權限。
混淆
如果使用了混淆,需要添加以下的代碼到混淆的配置文件中
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
如果使用的target API低於27的話還需要添加
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
VideoDecoder
使用API 27
的一些藉口,可能導致混淆發出警告。
如果使用了DexGuard
,可能還需要添加
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
二、簡單使用
V4的基本用法還是沒變,一句簡單地鏈式調用就能講圖片加載到ImageView
上了
Glide.with(context)
.load(url)
.into(imageView);
這裏context
就是一個Context
對象,而url
是一個網絡圖片鏈接,imageView
則是需要顯示圖片的ImageView
。V4還可以手動取消加載,例如:
Glide.with(context).clear(imageView);
Glide.with()
中傳入的Activity
或Fragment
銷燬時,Glide
會自動取消加載並回收資源
這裏會提出clear
方法,在RecyclerView
中複用View
來加載圖片,每次需要調用Glide
重新進行加載操作或者使用clear
方法停止之前的請求,避免圖片顯示錯亂的問題。
可以看到with
方法還是很強大,支持Context
,Activity
,Fragment
,View
等,只是將原本支持傳入android.app.Fragment
的方法標記爲過期了。
load
方法也提供了強大的支持,Uri、文件、btye[]、網絡連接、資源id、bitmap、drawable都可以加載。
Generated API(生成API)
Glide V4使用註解處理器生成一個流式API,用於RequestBuilder
,RequestOptions
等相關的所有選項。Glide的文檔中有說明,其目的一是爲了更好地擴展自定義選項,其二是爲了方便打包常用選項組。
對
RequestOptions
的設置,Glide V4還提供了apply
方法設置單次請求的選項,以及applyDefaultRequestOptions
設置默認請求選項。
從Glide V3到Glide V4的升級,生成API是一個很大的改變,接下來詳細說明。
生成API目前只能在Application
模塊中使用,無法同時在各個Library
和Application
中定義各自的生成API,確保了調用API時請求的選項是一致的。官方也指出,在將來的版本中可能解除此限制。
使用Generated API僅僅需要兩步,第一步,在build.gradle
的依賴中添加annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
上面其實已經給出;第二步,我們需要創建一個class
繼承AppGlideModule
,併爲該類添加@GlideModule
註解。
package com.mrtrying.glidev4_example;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
GlideModule
public final class ExampleAppGlideModule extends AppGlideModule {}
AppGlideModule
雖然是抽象類,卻可以不用重寫任何方法。但必須使用@GlideModule
註解標記該類,否則沒法順利的生成GlideApp
的API
。
PS:第一次添加AppGlideModule
或者對AppGlideModule
做了某些修改時,我們需要重新構建項目重新生成API。如果AndroidStudio沒法自動完成構建,可以使用Build—->Rebuild Project
手動重新構建,必要時可能需要手動刪除module
的build
目錄再重新構建項目。
生成API的默認名爲GlideApp
,其包名與所在的module
的包名相同,而基本用法也於之前相同,只是將Glide
替換成了GlideApp
,就可以使用了
GlideApp.with(this).load(url).into(imageView);
Glide生成API還有
GlideExtension
、GlideOption
、GlideType
,暫時只對AppGlideModule
進行說明,其他的涉及到較深度的使用,放在後面說明。
佔位符
Glide提供了三種不同類型的佔位符:
- placeholer(佔位符)
- error(錯誤符)
- fallback(後備回調符)
GlideApp.with(this)
.load(url)
.placeholder(R.mipmap.placeholder)
.error(R.mipmap.error)
.fallback(R.mipmap.fallback)
.into(imageView);
先說API的參數,placeholder
、error
、fallback
相同可以傳入資源id或者Drawable對象。
接下來說說這三個佔位符的調用時機,用過V3的同學一定知道placeholer
佔位符是當資源正在請求是被展示的圖片或者資源文件,當請求成功時佔位符會被替換成請求到得資源;error
則是在請求永久性失敗是展示;fallback
是當請求的url(或者model)爲null
時展示,此佔位符的目的是設置null
是否是可接收的正常情況,而Glide將null
作爲錯誤處理。
三個佔位符對應了三種時機,其中還有一些狀態,無可避免的會出現一些複雜的情況需要說明:
- 設置了
placeholder
之後,如果請求失敗,但是沒有設置error
,那麼佔位符將繼續顯示;而請求的url/model爲null也會導致請求失敗,一樣的,沒有設置error
和fallback
的話,佔位符也將繼續顯示 - 如果請求的url/model爲null導致請求失敗,
fallback
會優先於error
調用,也就是說url爲null時設置了error
而沒有設置fallback
將繼續顯示error
;反之,設置了fallback
就會顯示fallback
佔位符這個功能確實很棒,有朋友不經要問了,如果我想加載網絡圖片怎麼辦?這個就十分抱歉了,Glide並不提供這樣的功能,只能支持資源文件或者是Drawable
對象。當然,你也可以提前下載要網絡圖片在以Drawable
形式加載,只要這樣的效率你可以接收(個人覺得沒有什麼必要)。
官方文檔在佔位符最後的FAQ中有說明,佔位符是在主線程中,從Android Resource加載的,而且Glide的Transformation
不會被應用到佔位符上。這都是希望在使用佔位符時能儘量少得佔用系統資源來考慮的,所以Glide並沒有提供他們認爲不太合理的API。
關於圖片的變換,比較常用到得是圓角和模糊,圓角的處理的話比較推薦RCLayout,雖然在背景的處理上會有一點鋸齒,不過還是很不錯的一個庫;而模糊的話推薦使用android自帶的
RenderScript
,相關方法在API 17
以上提供,也提供了相關的support
包。
transformation(變換)
Glide提供了transformation
得功能,在獲取到請求的圖片之後,能對圖片進行一些處理,例如:裁剪、模糊等;而transformation
的強大在於可以自定義,這樣一來transformation
不僅能處理bitmap
,同樣可以用於處理GIF動畫,還有自定義資源類型。
Glide在API上提供的了5個相關的方法可以直接使用
- circleCrop()
- centerCrop()
- centerInside()
- fitCenter()
- optionalFitCenter()
optionalFitCenter()
的具體作用沒有太清楚,效果與fitCenter()
相同,有知道的大佬請告知
使用方式基本和佔位符一致,在load()
方法後調用相關方法就能使用相應的transformation
效果了
GlideApp.with(this)
.load(url)
.centerCrop()
.into(imageView);
這是在生成API中的使用,在Glide普通的API也是可以使用的,不過沒有直接可以調用的方法,需要通過RequestOptions
來設置
RequestOptions options = new RequestOptions().centerCrop();
Glide.with(this)
.load(url)
.apply(options)
.into(imageView);
除了可以自己創建以外,Glide提供了靜態獲取RequestOptions
方法,可以直接使用
Glide.with(this)
.load(url)
.apply(RequestOptions.centerCropTransform())
.into(imageView);
針對
ImageView
可能自身也會設置scaleType
的情況,Glide在部分情況會自動應用FitCenter
或CenterCrop
,如果scaleType
是CENTER_CROP
,Glide
將會自動應用CenterCrop
變換。如果scaleType
爲FIT_CENTER
或CENTER_INSIDE
,Glide
會自動使用FitCenter
變換。
當然,設置了其他的變換也可以將其覆蓋;或者使用dontTransform()
方法就不會在執行任何變換。
這裏需要注意的是,Glide內置的這幾種transformation
,即使在使用多種變換,也只有最後一個transformation
會生效。這個不僅僅是對Glide內置的transformation
也包括transform()
設置的transformation
,都會替換掉之前的transformation
。
如果需要支持多種變換,需要使用transform()
設置MultiTransformation
類對象傳入,MultiTransformation
的構造器可以接收可變參數或者Transformation
的集合;或者在transforms()
方法中設置多個(不要以爲是一個方法,這是transforms()
,結尾有s
的)而這些個變化的應用順序就是傳入參數的順序。
GlideApp.with(this)
.load(url)
.transform(new MultiTransformation<>(new FitCenter(),new RoundedCorners(3)))
.into(imageView);
//或者
GlideApp.with(this)
.load(url)
.transforms(new FitCenter(),new RoundedCorners(3))
.into(imageView);
由於
Transformation
是沒有狀態的,我們可以再多個加載中複用同一個Transformation
對象。
自定義transformation
爲了方便使用,我們希望一些特殊的變換也能像上面一樣的使用,這種情況可以通過實現Transformation
來處理。
這裏直接以Glide內置實現的RoundedCorners
爲例
public final class RoundedCorners extends BitmapTransformation {
private static final String ID = "com.bumptech.glide.load.resource.bitmap.RoundedCorners";
private static final byte[] ID_BYTES = ID.getBytes(CHARSET);
private final int roundingRadius;
/**
* @param roundingRadius the corner radius (in device-specific pixels).
* @throws IllegalArgumentException if rounding radius is 0 or less.
*/
public RoundedCorners(int roundingRadius) {
Preconditions.checkArgument(roundingRadius > 0, "roundingRadius must be greater than 0.");
this.roundingRadius = roundingRadius;
}
@Override
protected Bitmap transform(
@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
return TransformationUtils.roundedCorners(pool, toTransform, roundingRadius);
}
@Override
public boolean equals(Object o) {
if (o instanceof RoundedCorners) {
RoundedCorners other = (RoundedCorners) o;
return roundingRadius == other.roundingRadius;
}
return false;
}
@Override
public int hashCode() {
return Util.hashCode(ID.hashCode(),
Util.hashCode(roundingRadius));
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update(ID_BYTES);
byte[] radiusData = ByteBuffer.allocate(4).putInt(roundingRadius).array();
messageDigest.update(radiusData);
}
}
可以看到RoundedCorners
是繼承自BitmapTransformation
,BitmapTransformation
已經幫我們處理了部分基礎邏輯,比如提取和回收原始的Bitmap
,而我們處理好Bitmap
的變換就可以了。
除了自身的構造函數意外還重寫了4個方法,其中transform
方法就是進行變換處理的方法,變換完成之後返回變換完成的Bitmap
。其餘的三個方法,updateDiskCacheKey
是必須實現的,而爲例保證內存和磁盤緩存正常,equals()
和hashCode()
也是必須重寫的。
RoundedCorners
使用完整報名路徑的限定名來作爲一個 ID,它可以構成 hashCode()
的基礎,並可用於更新 updateDiskCacheKey()
傳入的 MessageDigest
。如果你的 Transformation 需要參數而且它會影響到 Bitmap 被變換的方式,它們也必須被包含到這三個方法中,就像RoundedCorners
一樣。原來的ID保留,但roundingRadius
也包含到了這個三個方法中,updateDiskCacheKey
方法還演示了你可以如何使用 ByteBuffer
來包含基本參數到你的 updateDiskCacheKey
實現中。
官方文檔着重指出必須要重寫equals()
和hashCode()
方法,雖然不重寫不會出現編譯問題,但是這不代表能正常工作。
三、進階
Target
或許在使用into()
時有些印象,into()
方法可以直接傳入ImageView
也可以使用Target
。其實跟蹤一下Glide的源碼就會發現其實在傳入ImageView
時,最終也在ImageViewTargetFactory
的包裝下返回了ImageViewTarget
,而ViewTarget
的最上層的父類就是Target
。
into()
方法不僅用於啓動每一個請求,同時也制定了接收請求結果的Target
。
Target<Drawable> target = GlideApp.with(this)
.load(url)
.centerCrop()
.into(new Target<Drawable>() {
//Target的方法太多,這裏代碼省略...
});
如果重用同一個Target
對加載一個新的請求,那麼之前的的請求都會被取消並且釋放資源。我們也可以使用clear()
方法對不需要重新加載的請求進行相關資源的釋放。
GlideApp.with(this).clear(target);
上面的情況中直接使用ImageVIew
也是可以的。因爲ViewTarget
使用了setTag()
和getTag()
存儲了Request
,所以可以直接從View
的tag
取回之前一次加載的信息,也是是ImageVIew
的默認tag
被佔用的原因。
也就是說,在使用ImageVIew的情況中,即使使用同一個ImageView重新加載也是可以釋放之前的請求和資源的
Glide.with(this)
.load(url)
.into(imageView);
//加載新連接
Glide.with(this)
.load(newUrl)
.into(imageView);
只要繼承ViewTarget
或者重寫setRequest()
和getRequest()
並實現取回上一次加載的信息,重用的機制就可以得以保證。
Transitions(過渡)
Glide V3和V4不同,不會默認應用交叉淡入或任何其他的過渡效果,每個請求需要手動應用過渡。Glide提供了很多過渡動畫,我們可以手動應用於每一個請求上;內置過渡的運行方式是一致的,會根據加載圖像不同的情況來決定是否執行過渡。例如:如果Glide從內存緩存中加載出來,Glide的內置過渡將不會執行;而加載磁盤緩存、本地文件或者遠程連接時都會執行Glide的內置過渡。
可以通過transition()
方法設置TransitionOptions
Glide.with(this)
.load(url)
.transition(DrawableTransitionOptions.withCrossFade())
.into(imageView);
TransitionOptions
用於給一個特定的請求指定過渡,而不同的資源類型能決定使用什麼類型的過渡選項。Bitmap 和 Drawable可以對應使用使用 BitmapTransitionOptions
或 DrawableTransitionOptions
來指定類型特定的過渡動畫。對於 Bitmap
和 Drawable
之外的資源類型,可以使用 GenericTransitionOptions
。
如果需要自定義過渡動畫,我們需要通過DrawableTransitionOptions.with()
生成我們自己的TransitionOptions
,而with()
需要出入一個TransitionFactory
對象。TransitionFactory
是一個接口我們需要實現一個這樣的類
public class ExampleTransitionFactory implements TransitionFactory {
@Override
public Transition build(DataSource dataSource, boolean isFirstResource) {
return null;
}
}
通過DrawableTransitionOptions.with(new ExampleTransitionFactory())
我們就能調用transition()
方法來加載我們的定製的過渡動畫。transition()
方法支持動畫的資源id,Animator
和TransitionFactory
,可以通過這三種方式來實現動畫部分。
最後就是執行transition()
來加載我們自定義的動畫
GlideApp.with(this)
.load(url)
.transition(DrawableTransitionOptions.with(new ExampleTransitionFactory()).transition(R.anim.show))
.into(imageView);
這裏需要提醒的是,動畫對於性能的開銷不用多說,比圖片解碼本身還要耗時,在列表的快速滑動的情況下可能造成加載緩慢。在列表中考慮是否使用動畫,在一些希望圖片儘快加載出來的時候也需要做此考慮。