Glide筆記
一、簡介
在泰國舉行的谷歌開發者論壇上,谷歌為我們介紹了一個名叫Glide的圖片加載庫,作者是bumptech。這個庫被廣泛的運用在Google的開源項目中,包括2014年Google I/O大會上發布的官方App。
Glide是一款由Bump Technologies開發的圖片加載框架,使得我們可以在Android平臺上以極度簡單的方式加載和展示圖片。Glide默認使用HttpUrlConnection進行網絡請求,為了讓App保持一致的網絡請求形式,可以讓Glide使用我們指定的網絡請求形式請求網絡資源。
二、依賴
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'
}
三、權限
<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()方法用于創建一個加載圖片的實例。with()方法可以接收Context、Activity、Fragment或者FragmentActivity類型的參數。特別需要注意的是with()方法中傳入的實例會決定Glide加載圖片的生命周期,如果傳入的是Activity、Fragment或者FragmentActivity的實例,那么當其被銷毀時圖片加載也會停止,如果傳入的是ApplicationContext時只有當應用程序被殺掉的時候圖片加載才會停止。
使用Glide加載圖片不用擔心內存浪費,甚至是內存溢出的問題。因為Glide不會直接將圖片的完整尺寸全部加載到內存中,而是用多少加載多少。Glide會自動判斷ImageView的大小,然后只將這么大的圖片像素加載到內存當中,幫助我們節省內存開支。
下面一個小的工具函數可以將資源id轉換為一個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、指定圖片格式
如果調用了.asBitmap()
方法,則.load()中的參數指向的可以是一個靜態圖片也可以是GIF圖片,如果是一張GIF圖片,則加載之后只會展示GIF圖片的第一幀。
如果調用的.asGif()
方法,則.load()方法中的參數指向的必須是一個GIF圖片,如果是一張靜態圖片,則圖片加載完成之后展示的只是出錯占位符(如果沒有設置出錯占位符,則什么也不展示)。
//顯示靜態圖片(若加載的是gif圖那么就會顯示第一幀的圖片)
.asBitmap()
//顯示動態圖片(若加載的是靜態圖會加載失敗)
.asGif()
2、指定占位圖顯示
//加載時顯示的圖片
.placeholder(R.drawable.image_load)
//加載失敗時顯示的圖片
.error(R.drawable.image_error)
3、設置緩存
Android應用中一個較好的圖片處理框架,會最小化網絡請求的消耗。Glide也是一樣,默認使用內存和磁盤緩存來避免不必要的網絡請求。然而,如果你的圖片變化的非常快,你需要禁止一些緩存。
比如你請求一個1000x1000像素的圖片,你的ImageView是500x500像素,Glide會保存兩個版本的圖片到緩存里。
//禁止內存緩存,但仍然會緩存到磁盤
.skipMemoryCache(true)
//禁止磁盤緩存(Glide默認緩存策略是:DiskCacheStrategy.RESULT)
.diskCacheStrategy(DiskCacheStrategy.NONE)
//緩存參數
//ALL:緩存源資源和轉換后的資源(即緩存所有版本圖像,默認行為)
//NONE:不作任何磁盤緩存,然而默認的它將仍然使用內存緩存
//SOURCE:僅緩存源資源(原來的全分辨率的圖像),上面例子里的1000x1000像素的圖片
//RESULT:緩存轉換后的資源(最終的圖像,即降低分辨率后的或者是轉換后的)
如果你有一個圖片你需要經常處理它,會生成各種不同的版本的圖片,緩存它的原始的分辨率圖片才有意義。我們
使用DiskCacheStrategy.SOURCE
去告訴Glide只緩存原始版本:
Glide.with(context).load("url").diskCacheStrategy(DiskCacheStrategy.SOURCE).into(imageView);
4、設置加載尺寸
Glide在緩存和內存里自動限制圖片的大小去適配ImageView的尺寸。用Glide時,如果圖片不需要自動適配ImageView,調用override(horizontalSize, verticalSize),
它會在將圖片顯示在ImageView之前調整圖片的大小。
//加載圖片為100*100像素的尺寸
.override(100, 100)
5、設置圖片縮放
如果調用了.centerCrop()
方法,則顯示圖片的時候短的一邊填充容器,長的一邊跟隨縮放;
如果調用了.fitCenter()
方法,則顯示圖片的時候長的一邊填充容器,短的一邊跟隨縮放;
這兩個方法可以都調用,如果都調用,則最終顯示的效果是后調用的方法展示的效果。
//它是一個裁剪技術,即縮放圖像讓它填充到ImageView界限內并且裁剪額外的部分,ImageView可能會完全填充,但圖像可能不會完整顯示
.centerCrop()
//它是一個裁剪技術,即縮放圖像讓圖像都測量出來等于或小于ImageView的邊界范圍,該圖像將會完全顯示,但可能不會填滿整個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、設置資源加載優先級
假設你正在創建一個信息展示界面,包含頂部的一個主要照片,還有底部的2個并不重要的小圖。對于用戶體驗,我們最好先加載主角照片,然后再加載底部不緊急的圖片。Glide里的.priority()方法和Priority的枚舉變量支持你的想法。
Priority枚舉變量,以遞增方式列出:
- Priority.LOW
- Priority.NORMAL
- Priority.HIGH
- Priority.IMMEDIATE
你應當明白優先級并不是非常嚴格的。Glide會將它們作為一個指導來最優化處理請求。但并不意味著所有的圖片都能夠按請求的順序加載。
.priority(Priority.HIGH)
7、設置圓角或圓形圖片
//圓角圖片
.transform(new GlideRoundTransform(this))
//圓形圖片
.transform(new GlideCircleTransform(this))
8、設置縮略圖
縮略圖的優點
縮略圖不同于前面提到的占位圖。占位圖應當是跟app綁定在一起的資源。縮略圖是一個動態的占位圖,可以從網絡加載。縮略圖也會被先加載,直到實際圖片請求加載完畢。如果因為某些原因,縮略圖獲得的時間晚于原始圖片,它并不會替代原始圖片,而是簡單地被忽略掉。
提示:另外一個非常棒的平滑圖片顯示的方法,通過加載圖片主色調的占位圖。
Glide提供了2個不同的方法產生縮略圖。
第一種:簡單的縮略圖
通過在加載的時候指定一個小的分辨率,產生一個縮略圖。這個方法在ListView和詳細視圖的組合中非常有用。如果你已經在ListView中用到了250x250像素的圖片,那么在在詳細視圖中會需要一個更大分辨率的圖片。然而從用戶的角度,我們已經看見了一個小版本的圖片,為什么需要好幾秒,同樣的圖片(高分辨率的)才能被再次加載出來呢?
在這種情況下,從顯示250x250像素版本的圖片平滑過渡到詳細視圖里查看大圖更有意義。Glide里的.thumbnail()方法讓這個變為可能。這里,.thumbnal()的參數是一個(0,1)之間浮點數:
Glide.with(context).load("url")
.thumbnail(0.1f)//系數需在(0,1)之間,這樣會先加載縮略圖然后加載全圖
.into(imageView);
這里傳遞一個0.1f作為參數,Glide會加載原始圖片大小的10%的圖片。如果原始圖片有1000x1000像素,縮略圖的分辨率為100x100像素。由于圖片將會比ImageView小,你需要確保縮放類型是否正確。
注意到你所有的請求設置都會影響到你的縮略圖。例如,如果你使用了一個變換讓你的圖片變為灰度圖,縮略圖也同樣將會是灰度圖。
第二種:高級縮略圖請求(原圖與縮略圖完全不同 )
.thumbnail()傳入一個浮點類型的參數,非常簡單有效,但并不是總是有意義。如果縮略圖需要從網絡加載同樣全分辨率圖片,可能根本都不快。這樣,Glide提供了另一個方法去加載和顯示縮略圖:傳遞一個新的Glide請求作為參數。
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 );
}
區別在于第一個縮略圖請求是完全獨立于第二個原始請求的。縮略圖可以來自不同資源或者圖片URL,你可以在它上面應用不同的變換。
9、設置動畫
加載圖片時所展示的動畫,可以是Animator類型的屬性動畫,也可以是int類型的動畫資源。這個動畫只在第一次加載的時候會展示,以后都會從緩存中獲取圖片,因此也就不會展示動畫了(圖片的改變時才會有用)。
//設置加載動畫
.animate(R.anim.alpha_in)
//實現ViewPropertyAnimation.Animator接口
.animate(ViewPropertyAnimation.Animator animator)
//淡入淡出動畫,也是默認動畫,動畫默認的持續時間是300毫秒。類似:.crossFade(int duration)
.crossFade()
//移除所有動畫
.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、加載本地視頻(相當于一張縮略圖)
//只能加載本地視頻(顯示的只是視頻的第一幀圖像,相當于一張縮略圖,不能播
//放視頻),網絡視頻無法加載。如果你想要從網絡URL播放視頻,參考VideoView
String files = Environment.getExternalStorageDirectory().getAbsolutePath() + "/glide.avi";
Glide.with(this).load(files).into(view);
11、定制view中使用SimpleTarget和ViewTarget
Glide中的回調:Target
假設我們并沒有ImageView作為圖片加載的目標。我們只需要Bitmap本身。Glide提供了一個用Target獲取Bitmap資源的方法。Target只是用來回調,它會在所有的加載和處理完畢時返回想要的結果。
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注意事項:
- 第一個是SimpleTarget對象的定義。java/Android可以允許你在.into()內匿名定義,但這會顯著增加在Glide處理完
圖片請求前Android垃圾回收清理匿名target對象的可能性。最終,會導致圖片被加載了,但是回調永遠不會被調用。
所以,請確保將你的回調定義為一個字段對象,防止被萬惡的Android垃圾回收給清理掉。 - 第二個關鍵部分是Glide的.with( context )。這個問題實際上是Glide一個特性問題:當你傳遞了一個context,例如
當前app的activity,當activity停止后,Glide會自動停止當前的請求。這種整合到app生命周期內是非常有用的,但也
是很難處理的。如果你的target是獨立于app的生命周期。這里的解決方案是使用application的context:.with(context.getApplicationContext())。當app自己停止運行的時候,Glide會只取消掉圖片的請求。
特定大小的Target
另外一個潛在問題是Target沒有一個明確的大小。如果你傳遞一個ImageView作為.into()的參數,Glide會使用ImageView的
大小來限制圖片的大小。例如如果要加載的圖片是1000x1000像素,但是ImageView的尺寸只有250x250像素,Glide會降低圖片到小尺寸,以節省處理時間和內存。顯然,由于target沒有具體大小,這對target并不起效。但是,如果你有個期望的具體大小,你可以增強回調。如果你知道圖片應當為多大,那么在你的回調定義里應當指明,以節省內存:
private SimpleTarget target2 = new SimpleTarget<Bitmap>(250, 250) {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
imageView2.setImageBitmap(bitmap);
}
};
ViewTarget
有很多原因導致我們不能直接使用ImageView。前面已經介紹了如何獲取Bitmap。現在,我們將更深入學習。假設你有個自定義的View。由于沒有已知的方法在哪里設置圖片,Glide并不支持加載圖片到定制的View內。然而用ViewTarget會讓這個更簡單。
讓我們看一個簡單的定制View,它繼承于FrameLayout,內部使用了一個ImageView:
public class FutureStudioView extends FrameLayout {
...
public void setImage(Drawable drawable) {
iv = (ImageView) findViewById(R.id.custom_view_image);
iv.setImageDrawable(drawable);
}
}
由于我們定制的View并不是繼承自ImageView,這里不能使用常規的.into()方法。因此,我們只能創建一個ViewTarget,用來傳遞給.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的回調方法中,我們在定制view上使用我們創建的setImage(Drawable drawable)方法設置圖片。同時,確保你注意到我們已經在ViewTarget的構造方法里傳遞了我們的定制view:new ViewTarget<FutureStudioView, GlideDrawable>(customView)。
12、設置監聽請求接口
首先,創建一個listener作為一個字段對象,避免被垃圾回收:
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方法中,你可以抓取問題,并決定你需要做什么,比如記錄日志。如果Glide應當處理這個后果,比如顯示一個出錯占位圖,在onException方法中返回false是很重要的。
Glide
.with( context )
.load(UsageExampleListViewAdapter.eatFoodyImages[0])
.listener( requestListener )
.error( R.drawable.cupcake )
.into( imageViewPlaceholder );
只有在listener的onException方法里返回false,R.drawable.cupcake才會顯示出來。
13、設置取消或恢復請求
以下兩個方法是為了保證用戶界面的滑動流暢而設計的。當在ListView中加載圖片的時候,如果用戶滑動ListView的時候繼續加載圖片,就很有可能造成滑動不流暢、卡頓的現象,這是由于Activity需要同時處理滑動事件以及Glide加載圖片。Glide為我們提供了這兩個方法,讓我們可以在ListView等滑動控件滑動的過程中控制Glide停止加載或繼續加載,可以有效的保證界面操作的流暢。
//當列表在滑動的時候可以調用pauseRequests()取消請求
Glide.with(context).pauseRequests();
//當列表滑動停止時可以調用resumeRequests()恢復請求
Glide.with(context).resumeRequests();
// ListView滑動時觸發的事件
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:
// 當ListView處于滑動狀態時,停止加載圖片,保證操作界面流暢
Glide.with(MainActivity.this).pauseRequests();
break;
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
// 當ListView處于靜止狀態時,繼續加載圖片
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、清除內存緩存
//可以在UI主線程中進行
Glide.get(this).clearMemory();
16、清除磁盤緩存
//需要在子線程中執行
Glide.get(this).clearDiskCache();
17、圖片裁剪、模糊、濾鏡等處理
變換
在圖片顯示出之前可以對圖片進行變換處理。例如,如果你的app需要顯示一張灰度圖,但只能獲取到一個原始全色彩的版本,你可以使用一個變換去將圖片從有明艷色彩的版本轉換成慘淡的黑白版。不要誤會我們,變換不僅限于顏色。你可以改變圖片的很多屬性:大小、邊框、色彩、像素點,等等!在之前介紹用Glide調整圖片大小時,已經介紹了自帶的兩個
變換fitCenter和 centerCrop。這兩個方案都有一個顯著的特征,他們有他們自己的Glide轉換方法,所以,這篇文章不再介紹了。
實現自己的變換
為了實現你自己自定義的變換,你需要創建一個新的類去實現變換接口。這個方法需要實現的內容還是相當復雜的,你需要深入探索Glide的內部結構才能讓其工作好。如果你只是想要常規的圖片(不包括Gif和視頻)變換,我們建議只要處理抽象的BitmapTransformation類。它簡化了相當多的實現,能覆蓋95%的使用范圍。
所以,讓我們先看一下BitmapTransformation實現的一個例子。用Renderscript去模糊圖片。我們可以用之前用過的代碼去實現一個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
}
}
現在,我們用前面文章的代碼,借助Renderscript來實現圖片的模糊處理。
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()方法為這個變換描述了一個獨有的識別。Glide使用那個關鍵字作為緩存系統的一部分。防止出現異常問題,確保其唯一。
應用一個簡單的變換
Glide有兩個不同的方式進行變換。第一個是傳遞一個你的類的實例作為.transform()的參數。不管是圖片還是gif,都可以進行變換。另一個則是使用.bitmapTransform(),
它只接受bitmap的變換。由于我們的實現都是基于bitmap,我們可以使用第一個:
Glide.with(context)
.load(eatFoodyImages[0])
.transform(new BlurTransformation(context))
//.bitmapTransform(new BlurTransformation(context)) // this would work too!
.into(imageView1);
這足夠讓Glide從網絡下載的圖片自動實現模糊算法。非常有用!
實現多重變換
通常,Glide的流接口(fluent interface)允許方法被連接在一起,然而變換并不是這樣的。確保你只調用.transform()或者.bitmapTransform()一次,不然,之前的設置將會被覆蓋!然而,你可以通過傳遞多個轉換對象當作參數到.transform()(或者.bitmapTransform())中來進行多重變換:
Glide.with(context)
.load(eatFoodyImages[1])
.transform(new GreyscaleTransformation(context), new BlurTransformation(context))
.into(imageView2);
在這段代碼中,我們先對圖片進行了灰度變換,然后模糊處理。Glide會為你自動進行兩個轉換。牛逼吧!
提示:當你使用變換的時候,你不能使用.centerCrop()或者.fitCenter()。
Glide的變換集合
如果你已經對你的app里要用什么變換有了想法,在花點時間看看下面的庫吧:Glide-transformations(https://github.com/wasabeef/glide-transformations)。它提供了許多變換的集合。值得去看一下你的idea是否已經被實現了。
這個庫有2個不同版本。擴展庫包括更多的變換,并且是用手機的GPU進行計算。需要一個額外的依賴,所以這兩個版本的設置還有點不一樣。你應當看看支持的變換的列表,再決定你需要用哪個版本。
Glide變換的設置
設置是很簡單的!對于基本版,你可以在你的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文件后,你已經可以進行使用變換集合了。使用方式與使用自定義變換一樣。假如我們要用glide變換集合去模糊圖片:
Glide
.with( context )
.load( eatFoodyImages[2] )
.bitmapTransform( new jp.wasabeef.glide.transformations.BlurTransformation( context, 25 ) )
.into( imageView3 );
你也可以像上面一樣應用一組變換。一個單獨的變換或者一組變換,.bitmapTransform()都可以接受!
示例:圓角處理
Glide.with(mContext)
.load(R.drawable.image_example)
.bitmapTransform(new RoundedCornersTransformation(mContext, 30, 0, RoundedCornersTransformation.CornerType.BOTTOM))
.into(imageView);
可實現Transformation接口,進行更靈活的圖片處理,如進行簡單地圓角處理。
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 + ")";
}
}
自定義圖片處理時為了避免創建大量Bitmap以及減少GC,可以考慮重用Bitmap,這就需要使用BitmapPool,例如從Bitmap池中取一個Bitmap,用這個Bitmap生成一個Canvas,然后在這個Canvas上畫初始的Bitmap并使用Matrix、Paint或者Shader處理這張圖片。為了有效并正確重用Bitmap需要遵循以下三條準則:
- 永遠不要把transform()傳給你的原始resource或原始Bitmap給recycle()了,更不要放回BitmapPool,因為這些都自動完成了。值得注意的是,任何從BitmapPool取出的用于自定義圖片變換的輔助Bitmap,如果不經過transform()方法返回,就必須主動放回BitmapPool或者調用recycle()回收。
- 如果你從BitmapPool拿出多個Bitmap或不使用你從BitmapPool拿出的一個Bitmap,一定要返回extras給BitmapPool。
- 如果你的圖片處理沒有替換原始resource(例如由于一張圖片已經匹配了你想要的尺寸,你需要提前返回),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()方法會返回null,就需要我們自己創建Bitmap了
if (result == null) {
// 如果想讓Bitmap支持透明度,就需要使用ARGB_8888
result = Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.ARGB_8888);
}
//創建最終Bitmap的Canvas.
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setAlpha(128);
// 將原始Bitmap處理后畫到最終Bitmap中
canvas.drawBitmap(toTransform, 0, 0, paint);
// 由于我們的圖片處理替換了原始Bitmap,就return我們新的Bitmap就行。
// Glide會自動幫我們回收原始Bitmap。
return result;
}
@Override
public String getId() {
// Return some id that uniquely identifies your transformation.
return "com.wiggins.glide.MyTransformation";
}
}
七、GlideModule使用
GlideModule是一個接口,全局改變Glide行為的一種方式,通過全局GlideModule配置Glide(GlideModule#applyOptions
),用GlideBuilder設置選項,用Glide注冊ModelLoader(GlideModule#registerComponents
)等。你需要創建Glide的實例,來訪問GlideBuilder。可以通過創建一個公共的類,實現GlideModule的接口來定制Glide。所有的GlideModule實現類必須是public的,并且只擁有一個空的構造器,以便在Glide延遲初始化時,可以通過反射將它們實例化。
1、自定義一個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注冊
<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、多個GlideModule沖突問題
GlideModule不能指定調用順序,所以應該避免不同的GlideModule之間有沖突的選項設置,可以考慮將所有的設置都放到一個GlideModule里面,或者排除掉某個manifest文件的某個Module。
<meta-data
android:name="com.wiggins.glide.MyGlideModule"
tools:node="remove" />
5、更改Glide配置
已經知道如何使用Glide module去自定義Glide。現在我們看一下接口的第一個方法:applyOptions(Context context, GlideBuilder builder)
。這個方法將GlideBuilder的對象當作參數,并且是void返回類型,所以你在這個方法里能調用GlideBuilder可以用的方法。
.setMemoryCache(MemoryCache memoryCache)
.setBitmapPool(BitmapPool bitmapPool)
.setDiskCache(DiskCache.Factory diskCacheFactory)
.setDiskCacheService(ExecutorService service)
.setResizeService(ExecutorService service)
.setDecodeFormat(DecodeFormat decodeFormat)
顯而易見,GlideBuilder對象可以讓你訪問到Glide的核心部分。使用文中的方法,你可以改變磁盤緩存、內存緩存等等。
5.1 設置Glide內存緩存大小
MemoryCache用來把resources緩存在內存里,以便能馬上能拿出來顯示。默認情況下Glide使用LruResourceCache,我們可以通過它的構造器設置最大緩存內存大小。
//獲取系統分配給應用的總內存大小
int maxMemory = (int) Runtime.getRuntime().maxMemory();
//設置圖片內存緩存占用八分之一
int memoryCacheSize = maxMemory / 8;
//設置內存緩存大小
builder.setMemoryCache(new LruResourceCache(memoryCacheSize));
獲取默認的使用內存
//MemoryCache和BitmapPool的默認大小由MemorySizeCalculator類決定,MemorySizeCalculator會根據給定屏幕大小可用內存算出合適的緩存大小,
這也是推薦的緩存大小,我們可以根據這個推薦大小做出調整
MemorySizeCalculator calculator = new MemorySizeCalculator(context);
int defaultMemoryCacheSize = calculator.getMemoryCacheSize();
int defaultBitmapPoolSize = calculator.getBitmapPoolSize();
5.2 設置Glide磁盤緩存大小
//方式一
//指定的是數據的緩存地址
File cacheDir = context.getExternalCacheDir();
//最多可以緩存多少字節的數據
int diskCacheSize = 1024 * 1024 * 30;
//設置磁盤緩存大小
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 設置圖片解碼格式
默認格式RGB_565相對于ARGB_8888的4字節/像素可以節省一半的內存,但是圖片質量就沒那么高了,而且不支持透明度。
builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
5.4 設置BitmapPool緩存內存大小
Bitmap池用來允許不同尺寸的Bitmap被重用,這可以顯著地減少因為圖片解碼像素數組分配內存而引發的垃圾回收。默認情況下Glide使用LruBitmapPool作為Bitmap池,LruBitmapPool采用Lru算法保存最近使用的尺寸的Bitmap,我們可以通過它的構造器設置最大緩存內存大小。
builder.setBitmapPool(new LruBitmapPool(memoryCacheSize));
5.5 設置用來檢索cache中沒有的Resource的ExecutorService
//為了使縮略圖請求正確工作,實現類必須把請求根據Priority優先級排好序
builder.setDiskCacheService(ExecutorService service);
builder.setResizeService(ExecutorService service);
6、集成網絡框架
Glide包含一些小的、可選的集成庫,目前Glide集成庫當中包含了訪問網絡操作的Volley和OkHttp,也可以通過Glide的ModelLoader接口自己寫網絡請求。
6.1 將OkHttp集成到Glide當中
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'
}
結尾的@aar可以將庫中的AndroidManifest.xml文件一起導出,Gradle自動合并必要的GlideModule到AndroidManifest.xml,然后使用所集成的網絡連接,所以不用再
將以下文本添加到項目的AndroidManifest.xml文件中:
<meta-data
android:name="com.bumptech.glide.integration.okhttp.OkHttpGlideModule"
android:value="GlideModule" />
b)創建OkHttp集成庫的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需使用不同的集成庫;
b.Gradle會自動將OkHttpGlideModule合并到應用的manifest文件中;
c.如果你沒有對所有的GlideModule配置混淆規則(即沒有使用-keep public class * implements com.bumptech.glide.module.GlideModule
),則需要把OkHttp的GlideModule進行混淆配置:-keep class com.wiggins.glide.okhttp.OkHttpGlideModule
。
6.2 將Volley集成到Glide當中
a)添加依賴
dependencies {
compile 'com.github.bumptech.glide:volley-integration:1.4.0@aar'
compile 'com.mcxiaoke.volley:library:1.0.8'
}
b)創建Volley集成庫的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自定義數據源
7.1、替換Glide組件
替換Glide組件功能需要在自定義模塊的registerComponents()方法中加入具體的替換邏輯。相比于更改Glide配置,替換Glide組件這個功能的難度就明顯大了不少。Glide中的組件非常繁多,也非常復雜,但其實大多數情況下并不需要我們去做什么替換。不過,有一個組件卻有著比較大的替換需求,那就是Glide的HTTP通訊組件。
替換Glide組件功能需要在自定義模塊的GlideModule#registerComponents(Context context, Glide glide)
方法中加入具體的替換邏輯,需要在方法中調用Glide#register(Class<T> modelClass, Class<Y> resourceClass, ModelLoaderFactory<T, Y> factory)
,其中modelClass表示 數據模型的類型,一般為GlideUrl
,Glide.with(context).load("url")底層就是將轉化為了GlideUrl;resourceClass表示URL所指向的資源的類型,一般為InputStream。
默認情況下,Glide使用的是基于原生HttpURLConnection進行訂制的HTTP通訊組件,但是現在大多數的Android開發者都更喜歡使用OkHttp,因此將Glide中的HTTP通訊組件修改成OkHttp的這個需求比較常見,那么今天我們也會以這個功能來作為例子進行講解。
Model:數據模型,一般為URL字符串
Resource:URL所指向的網絡資源
它主要和三個接口有關:
ModelLoader:數據模型Loader,將任意復雜的數據模型轉化為具體的可被DataFetcher使用的數據類型。需要返回一個從url拉取數據的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要解碼的數據
public interface DataFetcher<T> {
T loadData(Priority var1) throws Exception; //重要方法,返回給glide的數據
void cleanup();
String getId();
void cancel();
}
默認地,Glide內部使用標準的HTTPUrlConnection去下載圖片。Glide也提供兩個集成庫。這三個方法優點是在安全設置上都是相當嚴格的。唯一的不足之處是當你從一個使用HTTPS,還是self-signed的服務器下載圖片時,Glide并不會下載或者顯示圖片,因為self-signed認證會被認為存在安全問題。
首先創建一個跳過SSL認證的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;//刪除這行,多謝下面評論的幾位小伙伴指出空指針問題,并提供解決方案。
}
}
};
// 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);
}
}
}
創建的OkHttpClient關閉了所有的SSL認證檢查。
集成到 Glide
Glide的OkHTTP集成庫做的都是一樣的工作,所以我們可以跟隨他們的步驟。首先,我們需要在GlideModule里聲明我們的定制。你應該想到,我們需要在registerComponents()方法里做適配。我們可以調用.register()方法去交換Glide基礎構成。Glide使用一個ModelLoader去鏈接到數據模型創建一個具體的數據類型。我們的例子中,我們需要創建一個ModelLoader,它連接到一個URL,通過GlideUrl類響應并轉化為輸入流。Glide需要能夠創建我們的新ModelLoader的實例,所以我們在.register()方法中傳入一個工廠:
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自定義數據源
如果需要根據不同的要求請求不同尺寸不同質量的圖片,這時我們就可以使用自定義數據源。
a)定義處理URL接口
public interface IDataModel {
String buildDataModelUrl(int width, int height);
}
b)實現不同的處理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)實現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)根據不同的要求采用不同的策略加載圖片
//加載jpg圖片
Glide.with(this).using(new MyDataLoader(this)).load(new JpgDataModel(imageUrl)).into(imageView);
e)如何跳過.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);
八、特點
使用簡單;
可配置度及自適應程度高;
支持常見圖片格式如jpg、png、gif、webp等;
支持多種數據源如網絡、本地、資源、Uri等;
高效緩存策略(支持Memory和Disk圖片緩存,默認Bitmap格式采用RGB_565內存使用至少減少一半);
生命周期集成(根據Context/Activity/Fragment/FragmentActivity生命周期自動管理請求);
高效處理Bitmap(使用BitmapPool使Bitmap復用,主動調用recycle回收需要回收的Bitmap,減小系統回收壓力)。
九、優點
多樣化媒體加載,支持Gif、WebP、Video及縮略圖以等類型;
生命周期集成,提供多種方式與生命周期綁定,可以更好的讓加載圖片請求的生命周期動態管理起來;
高效的緩存策略
- 支持Memory和Disk圖片緩存;
- 緩存相應大小的圖片尺寸(Picasso只會緩存原始尺寸圖片,而Glide會根據你ImageView的大小來緩存相應大小的圖片尺寸,因此Glide會比Picasso加載的速度要快);
- 內存開銷小(Picasso默認的是ARGB_8888格式,而Glide默認的Bitmap格式是RGB_565格式,這個內存開銷大約可以減小一半)。
十、缺點
使用方法復雜:由于Glide功能強大,所以使用的方法非常多,其源碼也相對的復雜;
包較大:Glide(v3.7.0)的大小約465kb。
十一、使用場景
需要更多的內容表現形式(如Gif、縮略圖等);
更高的性能要求(緩存、加載速度等)。
十二、特別說明
1.ImageView的setTag問題
問題描述:如果使用Glide的into(imageView)為ImageView設置圖片的同時使用ImageView的setTag(final Object tag)方法,將會導致java.lang.IllegalArgumentException: You must not call setTag() on a view Glide is targeting異常。因為Glide的ViewTarget中通過view.setTag(tag)和view.getTag()標記請求的,由于Android 4.0之前Tag存儲在靜態map里,如果Glide使用setTag(int key, final Object tag)方法標記請求則可能會導致內存泄露,所以Glide默認使用view.setTag(tag)標記請求,你就不能重復調用了。
解決辦法:如果你需要為ImageView設置Tag,必須使用setTag(int key, final Object tag)及getTag(int key)方法,其中key必須是合法的資源id以確保key
的唯一性,典型做法就是在資源文件中聲明type="id"的item資源。
2.placeholder()導致的圖片變形問題
問題描述:使用.placeholder()方法在某些情況下會導致圖片顯示的時候出現圖片變形的情況。這是因為Glide默認開啟的crossFade動畫導致的TransitionDrawable繪制異常,具體描述可以查看https://github.com/bumptech/glide/issues/363。根本原因就是你的placeholder圖片和你要加載顯示的圖片寬高比不一樣,而Android的
TransitionDrawable無法很好地處理不同寬高比的過渡問題,這是Android也是Glide的Bug。
解決辦法:使用.dontAnimate()方法禁用過渡動畫,或者使用animate()方法自己寫動畫,再或者自己修復TransitionDrawable的問題。
3.ImageView的資源回收問題
問題描述:默認情況下Glide會根據with()使用的Activity或Fragment的生命周期自動調整資源請求以及資源回收。但是如果有很占內存的Fragment或Activity不銷毀而僅僅是隱藏視圖,那么這些圖片資源就沒辦法及時回收,即使是GC的時候。
解決辦法:可以考慮使用WeakReference,如:
final WeakReference<ImageView> imageViewWeakReference = new WeakReference<>(imageView);
ImageView target = imageViewWeakReference.get();
if (target != null) {
Glide.with(context).load(uri).into(target);
}
4.由于Bitmap復用導致的在某些設備上圖片錯亂的問題
問題描述: Glide默認使用BitmapPool的方式對應用中用到的Bitmap進行復用,以減少頻繁的內存申請和內存回收,而且默認使用的Bitmap模式為RGB565以減少內存開銷。但在某些設備上(通常在Galaxy系列5.X設備上很容易復現)某些情況下會出現圖片加載錯亂的問題,具體詳見https://github.com/bumptech/glide/issues/601。原因初步確定是OpenGL紋理渲染異常。
解決辦法:GlideModule使用PREFER_ARGB_8888(Glide4.X已經默認使用該模式了),雖然內存占用比RGB565更多一點,但可以更好地處理有透明度Bitmap的復用問
題,或者禁用Bitmap復用setBitmapPool(new BitmapPoolAdapter())來修復這個問題(不推薦這種處理方式)。
5.異步線程完成后加載圖片的崩潰問題
問題描述:通常情況下異步線程會被約束在Activity生命周期內,所以異步線程完成后使用Glide加載圖片是沒有問題的。但如果你的異步線程在Activity銷毀時沒
有取消掉,那么異步線程完成后Glide就無法為一個已銷毀的Activity加載圖片資源,拋出的異常如下(在with()方法中就進行判斷并拋出異常):
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(異步線程),當Activity停止或銷毀時,停止所有相關的異步線程及后續的UI操作,或者加載前使用isFinishing()
或isDestroyed()進行限制(不建議這種處理方式)。