
目前市場上的圖片異步請求框架有很多,比如Volley,Fresco,Glide,Picasso等,之前項(xiàng)目中用的一直都似乎Volley的ImageLoader,但是使用起來并不方便,再加上最近面試都會問到圖片異步加載框架,Glide,Picasso等,所以決定學(xué)習(xí)一下新的圖片異步加載框架,這邊我選擇Glide,首先是因?yàn)镚lide使用起來真的很方便,一行代碼就能解決(當(dāng)然,Picasso使用起來跟Glide幾乎沒什么差異,也很好用,這邊推薦一篇博客,主要講解Glide,Picasso的差異,然后就是Glide是Google官方推薦的,運(yùn)用在很多Google的開源項(xiàng)目中,沒理由不學(xué)了。
一、Glide基本使用
-
基本配置
我這邊使用的是3.7.0的版本,同時(shí)可以下載Glide的源碼,生成對應(yīng)的jar進(jìn)行導(dǎo)入,這兩種方案都是可行的。
compile 'com.github.bumptech.glide:glide:3.7.0' -
基本使用
Glide的使用是一樣簡單的,通常一行代碼就可以解決我們?nèi)粘5拈_發(fā)需求,Glide使用的也是目前非常流行的鏈?zhǔn)秸{(diào)用,使用起來特別舒暢,再想想Volley的ImageLoader,感覺整個(gè)世界又美好了好多,哈哈,廢話多了。Glide有個(gè)牛逼的特性就是他能根據(jù)Activity,F(xiàn)ragment的生命周期來判斷是請求加載圖片還是取消或暫停。這個(gè)特性根據(jù)with中傳入的值而定。
Glide.with(Context/Activity/Fragment).load(url/resId/...).into(ImageView); -
基本拓展
基礎(chǔ)上述的基本使用外,Glide理所當(dāng)然也能做一下個(gè)性化的配置,例如設(shè)置占位符,設(shè)置錯(cuò)誤圖等等,這些都是基本的。所有的設(shè)置只需要添加到load與into中間就行。當(dāng)然,可以配置的選項(xiàng)不僅僅這些,可以自己去探索看看。
Glide.with(context) .load("url") .placeholder(R.mipmap.placeholder) //設(shè)置占位符 .error(R.mipmap.error) //設(shè)置圖片加載失敗后的提示 .override(200, 200) //設(shè)置需要加載圖片的大小 .crossFade() //圖片顯示動(dòng)畫,逐漸顯示 .diskCacheStrategy(DiskCacheStrategy.NONE) //緩存策略,DiskCacheStrategy.NONE為不緩存 .into(mImageView); -
硬盤緩存策略
Glide的硬盤緩存通過diskCacheStrategy方法設(shè)定,它有四種硬盤緩存方案,主要是基于原圖與當(dāng)前使用的尺寸的圖片進(jìn)行區(qū)分,Glide加載圖片的適合會根據(jù)我們override傳入的數(shù)據(jù)加載對應(yīng)大小的圖片,再特定情況下將該尺寸圖片緩存在硬盤,如果沒有設(shè)置override,他會根據(jù)ImageView的大小來計(jì)算需要加載多大的圖片:
- DiskCacheStrategy.ALL 緩存所有,包括原始圖,以及當(dāng)前緩存尺寸的圖。
- DiskCacheStrategy.NONE 不做任何緩存,每次都從服務(wù)器下載。
- DiskCacheStrategy.SOURCE 只緩存原始圖。
- DiskCacheStrategy.RESULT 默認(rèn)選項(xiàng),只緩存當(dāng)前需要顯示的尺寸的圖,不緩存原始圖。
-
內(nèi)存緩存策略
Glide的內(nèi)存緩存是默認(rèn)開啟的,它內(nèi)部是使用一個(gè)LruCache算法與弱引用機(jī)制相結(jié)合的方式實(shí)現(xiàn)內(nèi)存緩存的,
private final MemoryCache cache; private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
activeResources緩存的是正在被使用的圖片,cache中緩存的是網(wǎng)絡(luò)請求后緩存下來的圖片,如果從緩存中取數(shù)據(jù),先從cache中取,取到圖片的畫,將該圖片從cache中刪除,再放入activeResources中,然后返回該圖片,如果沒取到,就從activeResources中取,取到就返回,取不到就從硬盤,或者網(wǎng)絡(luò)獲取,通過cache,activeResources結(jié)合的方式進(jìn)行內(nèi)存緩存,有效的避免了正在使用的圖片被LruCache算法回收的問題。
雖然Glide默認(rèn)開啟了內(nèi)存緩存,當(dāng)然我們也有方法關(guān)閉內(nèi)存緩存,通過如下方法可以實(shí)現(xiàn):
Glide.with(context)
.load(url)
.skipMemoryCache(true) //關(guān)閉內(nèi)存緩存
.into(mImageView);
?
二、圖片預(yù)加載-preload
? 再某些特定的情境下,我們肯定會遇到圖片提前下載到本地,然后需要使用的適合可以很快加載出來,這個(gè)適合就要使用到Glide的預(yù)加載了。對于這個(gè)功能,我們可以假想一下,對于目前的認(rèn)知,我們會怎么去實(shí)現(xiàn)?下面這行代碼能都實(shí)現(xiàn)?純粹一個(gè)Glide圖片加載請求,不給他顯示的ImageView,當(dāng)然這么些肯定是會有問題的,哈哈。
Glide.with(Context/Activity/Fragment).load(url/resId/...).into((ImageView)null);
? 帶著這個(gè)疑問,我們來看一下Glide給我們提供的preload()方法。這個(gè)方法就是提供給我們預(yù)加載圖片使用的,發(fā)起一個(gè)圖片加載的請求,這個(gè)請求結(jié)果會進(jìn)行硬盤,內(nèi)存緩存等操作,但是不對結(jié)果做任何操作。下面我們來看看它的源碼。
在看源碼前,我們需要了解一個(gè)事情,into方法中并不是只能傳遞一個(gè)ImageView,into()方法真正應(yīng)該傳入的是一個(gè)Target對象,into(ImageView)內(nèi)部其實(shí)就是將ImageView封裝到了一個(gè)XXXImageViewTarget,而Target是一個(gè)接口,定義了圖片加載的相關(guān)方法。
public interface Target<R> extends LifecycleListener {
...
//圖片加載啟動(dòng)前回調(diào)
void onLoadStarted(Drawable placeholder);
//圖片加載失敗回調(diào)
void onLoadFailed(Exception e, Drawable errorDrawable);
//圖片加載成功回調(diào)
void onResourceReady(R resource, GlideAnimation<? super R> glideAnimation);
...
}
了解了這一知識點(diǎn)后,我們就來看一下preload的源碼
public Target<TranscodeType> preload() {
return preload(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
}
public Target<TranscodeType> preload(int width, int height) {
final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(width, height);
return into(target);
}
不難發(fā)現(xiàn),preload其實(shí)就是創(chuàng)建了一個(gè)PreloadTarget,然后作為參數(shù)傳遞到into方法中,下面再追蹤一下PreoadTarget的源碼,重點(diǎn)看一下onResourceReady方法的重寫。
public final class PreloadTarget<Z> extends SimpleTarget<Z> {
/**
* Returns a PreloadTarget.
*
* @param width The width in pixels of the desired resource.
* @param height The height in pixels of the desired resource.
* @param <Z> The type of the desired resource.
*/
public static <Z> PreloadTarget<Z> obtain(int width, int height) {
return new PreloadTarget<Z>(width, height);
}
private PreloadTarget(int width, int height) {
super(width, height);
}
@Override
public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
Glide.clear(this);
}
}
PreloadTarget的源碼其實(shí)異常簡單,只有簡單幾行,onResourceReady方法中僅僅只是調(diào)用了Glide.clear(),該方法也僅僅是對一下資源的回收罷了,因此,Glide提供的preload方法與我們預(yù)想的差不多,就是在Glide請求成功后不做任何操作。
三、圖片下載-downloadOnly
有了上面的知識做鋪墊,我們不難發(fā)現(xiàn),Glide能做的不僅僅是將圖片顯示到ImageView等控件中,我們可以在拿到圖片資源后,做我們想做的一切,使用很簡單,只需要實(shí)現(xiàn)Target就行,同時(shí)Glide也實(shí)現(xiàn)了一些Target供我們使用,比如SimpleTarget,顧名思義,就是使用起來很簡單的target,只需要實(shí)現(xiàn)onResourceReady方法就行,比如,我們可以以這種方式實(shí)現(xiàn)圖片下載功能。
Glide.with(this)
.load("http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg")
.into(new SimpleTarget<GlideDrawable>() {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
//resource及是下載的圖片資源,我們只需要將圖片資源寫入文件,即可實(shí)現(xiàn)文件的下載
}
});
當(dāng)然,Glide這么強(qiáng)大的庫也給我們提供了下載文件的簡單使用,一共兩個(gè),一個(gè)需要在子線程中執(zhí)行,一個(gè)在主線程中執(zhí)行。
-
downloadOnly(width, height)
該方法需要在子線程中執(zhí)行,因?yàn)閮?nèi)部會有一個(gè)阻塞線程的耗時(shí)操作。該方法會返回一個(gè)Target,下載的圖片信息就可以通過返回的Target獲取到。Target.SIZE_ORIGINAL是告訴Glide下載圖片原始尺寸。
new Thread(new Runnable() { @Override public void run() { try { String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg"; final Context context = getApplicationContext(); FutureTarget<File> target = Glide.with(context) .load(url) .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); final File imageFile = target.get(); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(context, imageFile.getPath(), Toast.LENGTH_LONG).show(); } }); } catch (Exception e) { e.printStackTrace(); } } }).start(); -
downloadOnly(Target)
該方法與preload類似,只是這邊需要我們自己去實(shí)現(xiàn)這個(gè)Target
Glide.with(this) .load("http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg") .downloadOnly(new Target<File>() { ... @Override public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) { Log.e(TAG, "圖片存儲路徑 :" + resource.getAbsolutePath()); } ... });
四、Glide中的Transform
transform,顧名思義,就是對圖片進(jìn)行變換用的,我們經(jīng)常會在使用Glide的過程中使用centerCrop()和fitCenter()方法,Glide會根據(jù)目標(biāo)ImageView的scaleType,來決定是調(diào)用centerCrop()還是fitCenter(),由于ImageView默認(rèn)的ScaleType是FIT_CENTER,所以Glide默認(rèn)會為Glide添加fitCenter的轉(zhuǎn)換,當(dāng)然,如果我們不需要transform的時(shí)候,我們可以調(diào)用dontTransform()來取消轉(zhuǎn)換。
if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
下面我們來看看dontTransform, centerCrop, fitCenter三者的具體效果,(圖片為一張200x200的網(wǎng)絡(luò)圖片)

dontTransform():以ScaleType=FIT_CENTER的模式顯示圖片
fitCenter():拉伸圖片,橫向平鋪,截取橫向中間段,參考Glide源碼中的com.bumptech.glide.load.resource.bitmap.FitCenter
centerCrop():拉伸圖片,縱向平鋪,截取縱向中間段,參考com.bumptech.glide.load.resource.bitmap.CenterCrop
五、Transform實(shí)現(xiàn)圓角圖片
有了上面關(guān)于transform的知識,我們知道我們可以在transform中對圖形做任何我們想做的操作,舉例一個(gè)非常常見的需求,就是圓角圖片或者原型頭像,以及帶邊框等等,我們完全可以使用transform來實(shí)現(xiàn),下面直接貼出源碼,可以直接使用。
public class RoundTransform extends BitmapTransformation{
private static final int DEFAULT_BORDER_COLOR = Color.WHITE;
private static final int DEFAULT_BORDER_WIDTH = 10;
private static final int DEFAULT_BORDER_RADIUS = 0;
//類型:圓角
public static final int TYPE_ROUND = 0;
//類型:圓形
public static final int TYPE_CIRCLE = 1;
private int mType = TYPE_ROUND;
private int mBorderColor = DEFAULT_BORDER_COLOR;
private int mBorderWidth = DEFAULT_BORDER_WIDTH;
private int mBorderRadius = DEFAULT_BORDER_RADIUS;
//繪制邊線的畫筆
private Paint mBorderPaint;
public RoundTransform(Context context) {
super(context);
init();
}
public RoundTransform(Context context, int type, int borderWidth, int borderColor){
super(context);
this.mType = type;
this.mBorderWidth = borderWidth;
this.mBorderColor = borderColor;
init();
}
public RoundTransform(Context context, int type, int borderWidth, int borderColor, int borderRadius){
super(context);
this.mType = type;
this.mBorderWidth = borderWidth;
this.mBorderColor = borderColor;
this.mBorderRadius = borderRadius;
init();
}
private void init(){
if(mBorderWidth > 0) {
mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBorderPaint.setDither(true);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setStrokeWidth(mBorderWidth);
}
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
switch (mType){
case TYPE_CIRCLE:
return transform4Circle(pool, toTransform);
case TYPE_ROUND:
return transform4Round(pool, toTransform);
}
return null;
}
/**
* 圓形變換
* @param pool
* @param toTransform
* @return
*/
private Bitmap transform4Circle(BitmapPool pool, Bitmap toTransform) {
if(toTransform == null){
return null;
}
int squareWidth = Math.min(toTransform.getWidth(), toTransform.getHeight());
int startX = (toTransform.getWidth() - squareWidth) / 2;
int startY = (toTransform.getHeight() - squareWidth) / 2;
//截取中間方塊
Bitmap squareBitmap = Bitmap.createBitmap(toTransform, startX, startY, squareWidth, squareWidth);
Bitmap result = pool.get(squareWidth, squareWidth, Bitmap.Config.ARGB_8888);
if(result == null){
result = Bitmap.createBitmap(squareWidth, squareWidth, Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(result);
Paint shaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
shaderPaint.setDither(true);
shaderPaint.setShader(new BitmapShader(squareBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
canvas.drawCircle(squareWidth / 2, squareWidth / 2, squareWidth / 2, shaderPaint);
//繪制邊框
if(mBorderPaint != null){
canvas.drawCircle(squareWidth / 2, squareWidth / 2, squareWidth / 2 - mBorderWidth / 2, mBorderPaint);
}
if(result != null){
pool.put(result);
}
squareBitmap.recycle();
return result;
}
/**
* 圓角變換
* @param pool
* @param toTransform
* @return
*/
private Bitmap transform4Round(BitmapPool pool, Bitmap toTransform) {
if(toTransform == null){
return null;
}
// border 要居中即要壓在圖片上
final int border = (int) (mBorderWidth / 2);
final int width = (int) (toTransform.getWidth() - mBorderWidth);
final int height = (int) (toTransform.getHeight() - mBorderWidth);
Bitmap result = pool.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.setShader(new BitmapShader(toTransform, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
paint.setAntiAlias(true);
RectF rectF = new RectF(border, border, width - border, height - border);
canvas.drawRoundRect(rectF, mBorderRadius, mBorderRadius, paint);
if (mBorderPaint != null) {
canvas.drawRoundRect(rectF, mBorderRadius, mBorderRadius, mBorderPaint);
}
if(result != null){
pool.put(result);
}
return result;
}
@Override
public String getId() {
return getClass().getName();
}
}
如果需要更多的變換需求,可以參考一下https://github.com/wasabeef/glide-transformations,他幫我們實(shí)現(xiàn)了很多轉(zhuǎn)換,比如高斯模糊之類的。導(dǎo)入進(jìn)來直接用即可,很方便。
六、自定義模塊
Glide使用如此簡單,只需要一行代碼,那我們肯定會問,難道它沒用提供給我們配置設(shè)置的方法嘛?我想設(shè)置圖片解碼格式(默認(rèn)使用RGB_565),我想設(shè)置緩存方案,我想設(shè)置緩存路徑,我想...等下,難道Glide這么牛逼的框架沒想到這些嘛?怎么可能,他能做的遠(yuǎn)比我們想的多。
Glide提供了自定義模塊來提供我們設(shè)置配置的接口,我們需要實(shí)現(xiàn)GlideModule
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {}
@Override
public void registerComponents(Context context, Glide glide) {}
}
然后在AndroidManifest.xml中配置
<application>
...
<meta-data
android:name="com.yunzhou.libcommon.glide.CustomGlideModule"
android:value="GlideModule" />
...
</application>
這邊需要注意的一點(diǎn)是,自定義模塊并不是只能有一個(gè),可以多個(gè),我們可以看下源碼,他是通過反射機(jī)制實(shí)現(xiàn)的,獲取的是value=‘GlideModule’的一個(gè)List<GlideModule>
Context applicationContext = context.getApplicationContext();
List<GlideModule> modules = new ManifestParser(applicationContext).parse();
public List<GlideModule> parse() {
List<GlideModule> modules = new ArrayList<GlideModule>();
try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
context.getPackageName(), PackageManager.GET_META_DATA);
if (appInfo.metaData != null) {
for (String key : appInfo.metaData.keySet()) {
if (GLIDE_MODULE_VALUE.equals(appInfo.metaData.get(key))) {
modules.add(parseModule(key));
}
}
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException("Unable to find metadata to parse GlideModules", e);
}
return modules;
}
下面說一下如何使用GlideModule實(shí)現(xiàn)自定義的配置,主要還是兩個(gè)方法applyOptions和registerComponents
-
applyOptions
applyOptions主要做的是修改初始化配置,GlideBuilder提供了6項(xiàng)配置的修改
-
setDiskCache
配置硬盤緩存策略,默認(rèn)使用InternalCacheDiskCacheFactory
-
setDecodeFormat
配置圖片加載解碼模式,默認(rèn)為RGB_565
-
setBitmapPool
配置圖片緩存池,默認(rèn)為LruBitmapPool
-
setDiskCacheService
配置讀取硬盤緩存的異步執(zhí)行器,默認(rèn)是FifoPriorityThreadPoolExecutor
-
setMemoryCache
配置內(nèi)存緩存策略,默認(rèn)是LruResourceCache
-
setResizeService
配置讀取非緩存圖片的異步執(zhí)行器,默認(rèn)是FifoPriorityThreadPoolExecutor
?
這里面我們講一下硬盤緩存策略,它默認(rèn)使用的是InternalCacheDiskCacheFactory,閱讀源碼可以發(fā)現(xiàn)它是存儲在內(nèi)部存儲區(qū)域的getCacheDir() + "/image_manager_disk_cache", 代碼很簡單,僅僅兩個(gè)構(gòu)造函數(shù)而已,能夠供我們配置的也就是緩存的目錄的文件名,以及緩存大小,并不能自定義緩存路徑;
public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory { public InternalCacheDiskCacheFactory(Context context) { this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE); } public InternalCacheDiskCacheFactory(Context context, int diskCacheSize) { this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, diskCacheSize); } public InternalCacheDiskCacheFactory(final Context context, final String diskCacheName, int diskCacheSize) { super(new CacheDirectoryGetter() { @Override public File getCacheDirectory() { File cacheDirectory = context.getCacheDir(); if (cacheDirectory == null) { return null; } if (diskCacheName != null) { return new File(cacheDirectory, diskCacheName); } return cacheDirectory; } }, diskCacheSize); } }如果我們想使用外部緩存怎么辦?
Glide為我們提供了InternalCacheDiskCacheFactory來為我們實(shí)現(xiàn)內(nèi)部緩存,那它同時(shí)也通過了ExternalCacheDiskCacheFactory來實(shí)現(xiàn)外部緩存,我們通過如下代碼就可以實(shí)現(xiàn)使用外部存儲區(qū)域
builder.setDiskCache(new ExternalCacheDiskCacheFactory(context));但是,它與內(nèi)部緩存一樣,我們并不能夠修改外部存儲路徑。
如何修改存儲路徑?
Glide并沒有提供我們直接修改存儲邏輯的方法,但這也不帶邊不能修改,這邊提供兩種方案
1.修改InternalCacheDiskCacheFactory/ExternalCacheDiskCacheFactory源碼,設(shè)置成我們需要的路徑
2.新建類繼承DiskLruCacheFactory,讓這個(gè)新建的類提供自定義存儲路徑的接口,具體的核心邏輯可參考InternalCacheDiskCacheFactory/ExternalCacheDiskCacheFactory的實(shí)現(xiàn),聰明的你一定可以解決的!
-
-
registerComponents
registerComponents中主要是替換Glide中的一些組件,比如大家都知道的,Glide可以修改Http網(wǎng)絡(luò)請求的引擎,Glide默認(rèn)使用的是HttpUrlConnection,我們可以修改它使用Volley,或者當(dāng)下比較火的OkHttp等等。這邊我們就講一下使用OkHttp進(jìn)行網(wǎng)絡(luò)請求的實(shí)現(xiàn),使用Volley也是一樣的,比較簡單,關(guān)鍵還是Glide封裝的比較牛逼。
修改網(wǎng)絡(luò)請求有兩種方式,第一種是使用Glide為我們實(shí)現(xiàn)好的模塊,我們導(dǎo)入就行,比如OkHttp
dependencies { compile 'com.github.bumptech.glide:glide:3.7.0' compile 'com.squareup.okhttp3:okhttp:3.9.0' compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0@aar' }第二種方法,就需要手動(dòng)擼代碼了,主要是重新實(shí)現(xiàn)ModelLoader和DataFetcher兩個(gè)接口,其中DataFetcher的實(shí)現(xiàn)是真正進(jìn)行網(wǎng)絡(luò)請求的,下面看下源碼,不難理解
public class OKHttpFetcher implements DataFetcher<InputStream> { private final OkHttpClient mOkHttpClient; private final GlideUrl mUrl; private InputStream mStream; private ResponseBody mResponseBody; private volatile boolean isCancelled; public OKHttpFetcher(OkHttpClient client, GlideUrl url){ this.mOkHttpClient = client; this.mUrl = url; } @Override public InputStream loadData(Priority priority) throws Exception { Request.Builder requestBuilder = new Request.Builder() .url(mUrl.toStringUrl()); for(Map.Entry<String, String> headerEntry : mUrl.getHeaders().entrySet()){ String key = headerEntry.getKey(); requestBuilder.addHeader(key, headerEntry.getValue()); } requestBuilder.addHeader("httpEngine", "OkHttp"); Request request = requestBuilder.build(); if(isCancelled){ return null; } Response response = mOkHttpClient.newCall(request).execute(); mResponseBody = response.body(); if(!response.isSuccessful() || mResponseBody == null){ throw new IOException("Request failed with code: " + response.code()); } mStream = ContentLengthInputStream.obtain(mResponseBody.byteStream(), mResponseBody.contentLength()); return mStream; } @Override public void cleanup() { try { if(mStream != null){ mStream.close(); } if(mResponseBody != null){ mResponseBody.close(); } }catch (IOException e){ e.printStackTrace(); } } @Override public String getId() { return mUrl.getCacheKey(); } @Override public void cancel() { isCancelled = true; } }public class OKHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> { private OkHttpClient okHttpClient; public OKHttpGlideUrlLoader(OkHttpClient client){ this.okHttpClient = client; } @Override public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) { return new OKHttpFetcher(okHttpClient, model); } public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream>{ private OkHttpClient client; public Factory(){} private synchronized OkHttpClient getOkHttpClient(){ if(client == null){ client = new OkHttpClient(); } return client; } @Override public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) { return new OKHttpGlideUrlLoader(getOkHttpClient()); } @Override public void teardown() { } } }準(zhǔn)備工作就緒后,在registerComponents中實(shí)現(xiàn)注冊就可以了
//設(shè)置網(wǎng)絡(luò)引擎為OKHttp glide.register(GlideUrl.class, InputStream.class, new OKHttpGlideUrlLoader.Factory());
七、使用Glide時(shí)遇到的問題
1.使用七牛云上的圖片,可能出于安全考慮,圖片路徑后面需要添加token,導(dǎo)致每次都會觸發(fā)網(wǎng)絡(luò)請求
這個(gè)問題的原因跟Glide的緩存key有關(guān),Glide調(diào)用load的時(shí)候會將傳入的url封裝成一個(gè)GlideUrl對象,GlideUrl中的getCacheKey()方法返回的內(nèi)容是緩存key的組成部分,因此圖片路徑后面添加token,同一張圖片每一次請求token都不一樣,會導(dǎo)致key每次都不一樣,所以Glide每次都會去下載,因?yàn)樗诰彺嬷姓也坏綄?yīng)key的內(nèi)容。
?解決方案就是重寫GlideUrl的getCacheKey(),在這個(gè)方法中將圖片路徑后面的token過濾掉,然后調(diào)用load方法的時(shí)候傳入的不再是單獨(dú)的字符串,而是我們重寫的GlideUrl對象。
2.在RecycleView的Item中加載圖片,ImageView大小設(shè)置的wrap_content,來回滑動(dòng)過程中會出現(xiàn)同一張圖片忽大忽 小的問題。
這個(gè)問題出現(xiàn)的原因有兩個(gè),一個(gè)是RecycleView的View重用機(jī)制,另一個(gè)是Glide加載圖片時(shí)根據(jù)ImageView的大小加載適當(dāng)?shù)膱D片。這兩者共同作用導(dǎo)致了一種情況的發(fā)生。說真的我也沒有特別好的解決方案,本身RecycleView中每一項(xiàng)圖片大小都不能確定,來去很大,這種需求就比較雞肋了,可以找交互懟一波,如果真的要實(shí)現(xiàn)的話就是我們加載圖片的時(shí)候 每次都計(jì)算圖片大小,然后用override設(shè)定圖片需要顯示圖片的區(qū)域大小。