圖片載入之Glide使用
一、簡介
在泰國舉行的谷歌開發者論壇上,谷歌為我們介紹了一個名叫Glide
的圖片載入庫,作者是bumptech
。這個庫被廣泛的運用在Google
的開源專案中,包括2014
年Google I/O
大會上釋出的官方App
。
Glide
是一款由Bump Technologies
開發的圖片載入框架,使得我們可以在Android
平臺上以極度簡單的方式載入和展示圖片。Glide
預設使用HttpUrlConnection
進行網路請求,為了讓App
保持一致的網路請求形式,可以讓Glide
使用我們指定的網路請求形式請求網路資源。
二、依賴
1.jar
包
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(this).load(url).into(view);
Glide.with()
方法用於建立一個載入圖片的例項。with()
方法可以接收Context
、Activity
、Fragment
或者FragmentActivity
型別的引數,因此可供我們選擇的範圍非常廣。在Activity
、Fragment
或者FragmentActivity
中呼叫with()
方法時都可以直接傳this
,不在這些類中時可獲取當前應用程式的ApplicationContext
傳入with()
方法中。特別需要注意的是with()
方法中傳入的例項會決定Glide
載入圖片的生命週期,如果傳入的是Activity
Fragment
或者FragmentActivity
的例項,那麼當其被銷燬時圖片載入也會停止,如果傳入的是ApplicationContext
時只有當應用程式被殺掉的時候圖片載入才會停止。
Glide.with(Context context);// 繫結Context
Glide.with(Activity activity);// 繫結Activity
Glide.with(FragmentActivity activity);// 繫結FragmentActivity
Glide.with(Fragment fragment);// 繫結Fragment
load()
方法用於指定待載入的圖片資源。Glide
支援載入各種各樣的圖片資源,包括網路圖片、本地圖片、應用資源、二進位制流、Uri
物件等。
into()
方法用於圖片顯示的對應ImageView
。
Glide
支援載入gif
圖片,其內部會自動判斷圖片格式,並且可以正確的將它解析並顯示出來。
使用Glide
載入圖片不用擔心記憶體浪費,甚至是記憶體溢位的問題。因為Glide
不會直接將圖片的完整尺寸全部載入到記憶體中,而是用多少載入多少。Glide
會自動判斷ImageView
的大小,然後只將這麼大的圖片畫素載入到記憶體當中,幫助我們節省記憶體開支。
六、方法
1.指定圖片格式
如果呼叫了.asBitmap()
方法,則.load()
中的引數指向的可以是一個靜態圖片也可以是GIF
圖片,如果是一張GIF
圖片,則載入之後只會展示GIF
圖片的第一幀。
如果呼叫的.asGif()
方法,則.load()
方法中的引數指向的必須是一個GIF
圖片,如果是一張靜態圖片,則圖片載入完成之後展示的只是圖片佔位符(如果沒有設定圖片佔位符,則什麼也不展示)。
//顯示靜態圖片(若載入的是gif圖那麼就會顯示第一幀的圖片)
.asBitmap()
//顯示動態圖片(若載入的是靜態圖會載入失敗)
.asGif()
2.指定佔位圖顯示
//載入時顯示的圖片
.placeholder(R.drawable.image_load)
//載入失敗時顯示的圖片
.error(R.drawable.image_error)
3.設定快取
//禁止記憶體快取
.skipMemoryCache(true)
//禁止磁碟快取(Glide預設快取策略是:DiskCacheStrategy.RESULT)
.diskCacheStrategy(DiskCacheStrategy.NONE)
//快取引數
//ALL:快取源資源和轉換後的資源(即快取所有版本影象,預設行為)
//NONE:不作任何磁碟快取,然而預設的它將仍然使用記憶體快取
//SOURCE:僅快取源資源(原來的全解析度的影象)
//RESULT:快取轉換後的資源(最終的影象,即降低解析度後的或者是轉換後的)
4.設定載入尺寸
以下方法可以設定圖片載入之後展示的寬度值和高度值,前提是目標ImageView
的寬度和高度都設定為wrap_content
。
//載入圖片為100*100畫素的尺寸
.override(100, 100)
5.設定圖片縮放
如果呼叫了.centerCrop()
方法,則顯示圖片的時候短的一邊填充容器,長的一邊跟隨縮放;如果呼叫了.fitCenter()
方法,則顯示圖片的時候長的一邊填充容器,短的一邊跟隨縮放;這兩個方法可以都呼叫,如果都呼叫,則最終顯示的效果是後呼叫的方法展示的效果。
//它是一個裁剪技術,即縮放影象讓它填充到ImageView界限內並且裁剪額外的部分,ImageView可能會完全填充,但影象可能不會完整顯示
.centerCrop()
//它是一個裁剪技術,即縮放影象讓影象都測量出來等於或小於ImageView的邊界範圍,該影象將會完全顯示,但可能不會填滿整個ImageView
.fitCenter()
6.設定資源載入優先順序
.priority(Priority.HIGH)
7.設定圓角或圓形圖片
//圓角圖片
.transform(new GlideRoundTransform(this))
//圓形圖片
.transform(new GlideCircleTransform(this))
8.設定縮圖
設定以下方法後會先載入這張圖片的sizeMultiplier
倍的縮圖到目標ImageView
中,然後再慢慢載入完整的圖片,sizeMultiplier
值的範圍是0~1
。
//方法一:係數需在(0,1)之間,0.5f為原圖的1/2,這樣會先載入縮圖然後在載入全圖
.thumbnail(0.5f)
//方法二:自定義資源圖片為縮圖
DrawableRequestBuilder<Integer> thumbnailRequest = Glide
.with(context)
.load(R.drawable.image_example);
Glide.with(context)
.load(url)
.thumbnail(thumbnailRequest)
.into(view);
9.設定動畫
載入圖片時所展示的動畫,可以是Animator
型別的屬性動畫,也可以是int
型別的動畫資源。這個動畫只在第一次載入的時候會展示,以後都會從快取中獲取圖片,因此也就不會展示動畫了。
//設定載入動畫
.animate(R.anim.alpha_in)
//淡入淡出動畫,也是預設動畫,動畫預設的持續時間是300毫秒
.crossFade()
//移除所有動畫
.dontAnimate()
10.載入本地視訊(相當於一張縮圖)
//只能載入本地視訊(顯示的只是視訊的第一幀影象,相當於一張縮圖,不能播放視訊),網路視訊無法載入
String files = Environment.getExternalStorageDirectory().getAbsolutePath() + "/glide.avi";
Glide.with(this).load(files).into(view);
11.設定載入內容
//示例一
Glide.with(context).load(url).into(new SimpleTarget<GlideDrawable>() {
@Override
public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
//圖片載入完成
view.setImageDrawable(resource);
}
});
//示例二
Glide.with(context).load(url).asBitmap().into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation glideAnimation) {
//圖片載入完成
view.setImageBitmap(bitmap);
}
});
12.設定監聽請求介面
Glide.with(this).load(url).listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
//載入異常
return false;
}
@Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
//載入成功
//view.setImageDrawable(resource);
return false;
}
}).into(view);
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.圖片裁剪、模糊、濾鏡等處理
依賴:
compile 'jp.wasabeef:glide-transformations:2.0.2'
// If you want to use the GPU Filters
compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.4.1'
可使用GenericRequestBuilder
或其子類的transform()
或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";
}
}
18.GlidePalette
調色盤
七、GlideModule使用
GlideModule
是一個抽象方法,全域性改變Glide
行為的一種方式,通過全域性GlideModule
配置Glide
,用GlideBuilder
設定選項,用Glide
註冊ModelLoader
等。所有的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" />
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.GlideBuilder
設定選項
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
當中
6.1.1 新增依賴
dependencies {
//OkHttp 2.x
compile 'com.github.bumptech.glide:okhttp-integration:[email protected]'
compile 'com.squareup.okhttp:okhttp:2.7.5'
//OkHttp 3.x
compile 'com.github.bumptech.glide:okhttp3-integration:[email protected]'
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" />
6.1.2 建立OkHttp
整合庫的GlideModule
<meta-data
android:name="com.wiggins.glide.okhttp.OkHttpGlideModule"
android:value="GlideModule" />
6.1.3 混淆配置
-keep class com.wiggins.glide.okhttp.OkHttpGlideModule
#or
-keep public class * implements com.bumptech.glide.module.GlideModule
注意:
1.OkHttp 2.x
和OkHttp 3.x
需使用不同的整合庫;
2. Gradle
會自動將OkHttpGlideModule
合併到應用的manifest
檔案中;
3. 如果你沒有對所有的GlideModule
配置混淆規則(即沒有使用-keep public class * implements com.bumptech.glide.module.GlideModule
),則需要把OkHttp
的GlideModule
進行混淆配置:-keep class com.wiggins.glide.okhttp.OkHttpGlideModule
。
6.2 將Volley
整合到Glide
當中
6.2.1 新增依賴
dependencies {
compile 'com.github.bumptech.glide:volley-integration:[email protected]'
compile 'com.mcxiaoke.volley:library:1.0.8'
}
6.2.2 建立Volley
整合庫的GlideModule
<meta-data
android:name="com.wiggins.glide.volley.VolleyGlideModule"
android:value="GlideModule" />
6.2.3 混淆配置
-keep class com.wiggins.glide.volley.VolleyGlideModule
#or
-keep public class * implements com.bumptech.glide.module.GlideModule
7.使用ModelLoader
自定義資料來源
如果需要根據不同的要求請求不同尺寸不同質量的圖片,這時我們就可以使用自定義資料來源。
7.1 定義處理URL
介面
public interface IDataModel {
String buildDataModelUrl(int width, int height);
}
7.2 實現不同的處理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);
}
}
7.3 實現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() {
}
}
}
7.4 根據不同的要求採用不同的策略載入圖片
//載入jpg圖片
Glide.with(this).using(new MyDataLoader(this)).load(new JpgDataModel(imageUrl)).into(imageView);
7.5 如何跳過.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
及縮圖以等型別; - 生命週期整合,提供多種方式與生命週期繫結,可以更好的讓載入圖片請求的生命週期動態管理起來;
- 高效的快取策略
3.1 支援Memory
和Disk
圖片快取;
3.2 快取相應大小的圖片尺寸(Picasso
只會快取原始尺寸圖片,而Glide
會根據你ImageView
的大小來快取相應大小的圖片尺寸,因此Glide
會比Picasso
載入的速度要快);
3.3 記憶體開銷小(Picasso
預設的是ARGB_8888
格式,而Glide
預設的Bitmap
格式是RGB_565
格式,這個記憶體開銷大約可以減小一半)。
Android
關於圖片記憶體計算共有四種,分別是:
ALPHA_8
:每個畫素佔用1byte
記憶體;ARGB_4444
:每個畫素佔用2byte
記憶體;RGB_565
:每個畫素佔用2byte
記憶體;ARGB_8888
:每個畫素佔用4byte
記憶體(預設、色彩最細膩、顯示質量最高、佔用的記憶體也最大、8bit = 1byte
);
舉例:一個32
位的PNG = ARGB_8888 = 1204 x 1024
,那麼佔用空間是:1024 x 1024 x (32/8) = 4,194,304kb = 4M
左右,在解析圖片的時候為了避免OOM
和節省記憶體,最好使用ARGB_4444
模式(節省一半的記憶體空間)。
十、缺點
- 使用方法複雜:由於
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)
解決辦法:正確管理Background Threads
(非同步執行緒),當Activity
停止或銷燬時,停止所有相關的非同步執行緒及後續的UI
操作,或者載入前使用isFinishing()
或isDestroyed()
進行限制(不建議這種處理方式)。
專案地址 ☞ 傳送門