一、简介
在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫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
最新版本:http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22glide%22
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.图片裁剪、模糊、滤镜等处理
图片处理库:https://github.com/wasabeef/glide-transformations
依赖:
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
调色板
GlidePalette
:https://github.com/florent37/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: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" />
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:1.4.0@aar'
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()
进行限制(不建议这种处理方式)。
项目地址 ☞ 传送门