在图片加载库烂大街的今天,选择一个适合自己使用的图片加载库已经成为了每一个Android开发者的必经之路。现在市面上知名的图片加载库有UIL,Picasso,Volley ImageLoader,Fresco以及我们今天的主角Glide。它们各有千秋,不能评定谁一定比谁好,只能说哪一个更适合你。
我的理解
下面我来谈一下个人对这些图片加载库的理解,如有错误,还望指教。
Universal Image Loader:一个强大的图片加载库,包含各种各样的配置,最老牌,使用也最广泛。
Picasso: Square出品,必属精品。和OkHttp搭配起来更配呦!
Volley ImageLoader:Google官方出品,可惜不能加载本地图片~
Fresco:Facebook出的,天生骄傲!不是一般的强大。
Glide:Google推荐的图片加载库,专注于流畅的滚动。
更多详情请看stackoverflow上这个问题。
初试Glide
下面进入今天的主题,相信之前很多同学都看到过这篇介绍Glide的文章,中文版在这里。文中从各个方面介绍和比较了Glide与Picasso,总体来说二者极为相似,有着近乎相同的API的使用风格。但Glide在缓存策略和加载GIF方面略胜一筹。最后作者也极力推荐了这个库。
而且据说在Google新出的Photos应用中,到处可见Glide的踪迹。看到这里,你是不是已经迫不及待的想试一试这个库呢?就在你下定决心尝试一记的时候,你又听说Yelp app(据说是美国的大众点评)也在使用这个吊炸天的库。你的心中激动万分,发四一定要使用这个库。说干就干,打开Android Studio,在builde.gradle里面添加上
compile 'com.github.bumptech.glide:glide:3.6.1'
然后全局搜索图片加载的地方,全部换成了下面的代码:
Glide.with(mContext)
.load(url)
.placeholder(R.drawable.loading_spinner)
.crossFade()
.into(myImageView);
在经过漫长的编译过程之后,再次打开APP,看到有着渐现效果的图片呈现在你的面前,你不禁叫道:“wocao,真TM帅!为什么我以前没有发现呢?”。
不过在你使用了几天之后你会发现一些问题:
为什么 有的图片第一次加载的时候只显示占位图,第二次才显示正常的图片呢?
为什么 我总会得到类似You cannot start a load for a destroyed activity这样的异常呢?
为什么 我不能给加载的图片setTag()呢?
为什么?为什么?这么NB的库竟然会有这么多的问题。没错,这就是我今天要讲的重点。怎么避免上面的问题发生。
一些解决方案
1.如果你刚好使用了这个圆形Imageview库或者其他的一些自定义的圆形Imageview,而你又刚好设置了占位的话,那么,你就会遇到第一个问题。如何解决呢?
方案一: 不设置占位;
方案二:使用Glide的Transformation API自定义圆形Bitmap的转换。这里是一个已有的例子;
方案三:使用下面的代码加载图片:
Glide.with(mContext)
.load(url)
.placeholder(R.drawable.loading_spinner)
.into(new SimpleTarget<Bitmap>(width, height) {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
// setImageBitmap(bitmap) on CircleImageView
}
});
感谢aeecc0d15a40指出该方法在listview上复用有问题的bug,如果在listview中加载CircleImageView,请不要使用该方法。
方案四:不使用Glide的默认动画:
Glide.with(mContext)
.load(url)
.dontAnimate()
.placeholder(R.drawable.loading_spinner)
.into(circleImageview);
2.至于第二个问题,请记住一句话:不要再非主线程里面使用Glide加载图片,如果真的使用了,请把context参数换成getApplicationContext。更多的细节请参考这个issue。
3.为什么不能设置Tag,是因为你使用的姿势不对哦。如何为ImageView设置Tag呢?且听我细细道来。
方案一:使用setTag(int,object)方法设置tag,具体用法如下:
Java代码是酱紫的:
Glide.with(context).load(urls.get(i).getUrl()).fitCenter().into(imageViewHolder.image);
imageViewHolder.image.setTag(R.id.image_tag, i);
imageViewHolder.image.setOnClickListener(new View.OnClickListener() {
@Override
int position = (int) v.getTag(R.id.image_tag);
Toast.makeText(context, urls.get(position).getWho(), Toast.LENGTH_SHORT).show();
}
});
同时在values文件夹下新建ids.xml,添加
<item name="image_tag" type="id"/>
大功告成!
方案二:从Glide的3.6.0之后,新添加了全局设置的方法。具体方法如下:
先实现GlideMoudle接口,全局设置ViewTaget的tagId:
public class MyGlideMoudle implements GlideModule{
@Override
public void applyOptions(Context context, GlideBuilder builder) {
ViewTarget.setTagId(R.id.glide_tag_id);
}
@Override
public void registerComponents(Context context, Glide glide) {
}
}
同样,也需要在ids.xml下添加id
<item name="glide_tag_id" type="id"/>
最后在AndroidManifest.xml文件里面添加
<meta-data
android:name="com.yourpackagename.MyGlideMoudle"
android:value="GlideModule" />
又可以愉快的玩耍了,嘻嘻`(∩_∩)′。
方案三:写一个继承自ImageViewTaget的类,复写它的get/setRequest方法。
Glide.with(context).load(urls.get(i).getUrl()).fitCenter().into(new ImageViewTarget<GlideDrawable>(imageViewHolder.image) {
@Override
protected void setResource(GlideDrawable resource) {
imageViewHolder.image.setImageDrawable(resource);
}
@Override
public void setRequest(Request request) {
imageViewHolder.image.setTag(i);
imageViewHolder.image.setTag(R.id.glide_tag_id,request);
}
@Override
public Request getRequest() {
return (Request) imageViewHolder.image.getTag(R.id.glide_tag_id);
}
});
imageViewHolder.image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = (int) v.getTag();
Toast.makeText(context, urls.get(position).getWho(), Toast.LENGTH_SHORT).show();
}
});
一些使用技巧
1.Glide.with(context).resumeRequests()和 Glide.with(context).pauseRequests()
当列表在滑动的时候,调用pauseRequests()取消请求,滑动停止时,调用resumeRequests()恢复请求。这样是不是会好些呢?
2.Glide.clear()
当你想清除掉所有的图片加载请求时,这个方法可以帮助到你。
3.ListPreloader
如果你想让列表预加载的话,不妨试一下ListPreloader这个类。
一些基于Glide的优秀库
一个基于Glide的transformation库,拥有裁剪,着色,模糊,滤镜等多种转换效果,赞的不行不行的~~
一个可以在Glide加载时很方便使用Palette的库。
网友评论
http://api.res.mojing.com//forum//20161223//14BDFAA1820B3CE0589EDFF9D90694CF.gif
会报Bad position的错误
代码如下: Glide.with(mContext).load(video.thumb).asBitmap().centerCrop().dontAnimate().into(holder.video_bg);
.load(new File(photo.getPath()))
.asBitmap()
.centerCrop()
.dontAnimate()
.placeholder(R.drawable.__picker_ic_photo_black_48dp)
.error(R.drawable.__picker_ic_broken_image_black_48dp)
// .diskCacheStrategy(DiskCacheStrategy.ALL)
// .crossFade(100)
.transform(new MyTransformation(context))
.into(holder.ivPhoto);
只显示占位图就加上dontAnimate()
.load(new File(photo.getPath()))
.asBitmap()
.thumbnail(0.1f)
// .override(imageSize, imageSize)
.placeholder(R.drawable.__picker_ic_photo_black_48dp)
.error(R.drawable.__picker_ic_broken_image_black_48dp)
.into(new SimpleTarget<Bitmap>(450 , 450) {
@Override
public void onResourceReady(Bitmap arg0, GlideAnimation<? super Bitmap> arg1) {
holder.ivPhoto.setImageBitmap(arg0);
}
});
.load(uri)
.asBitmap()
.placeholder(R.drawable.ic_launcher)
.into(new SimpleTarget<Bitmap>(450,450) {
@Override
public void onResourceReady(Bitmap resource,
GlideAnimation<? super Bitmap> glideAnimation) {
}
});
添加 asBitmap方法
Glide.with(this)
.load(uri)
.placeholder(R.drawable.ic_launcher)
.into(new SimpleTarget<Bitmap>(450 , 450) {
@Override
public void onResourceReady(Bitmap arg0, GlideAnimation<? super Bitmap> arg1) {
iv_studentHeadImg.setImageBitmap(arg0);
}
});
into那里会报错,请问是什么原因
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.activity_ppt_show_item, parent, false);
viewHolder = new ViewHolder();
viewHolder.img = (ImageView) convertView.findViewById(R.id.pptImg);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
String path = list.get(position).get("VISIT_IMG_PATH").toString().replace("\\", "/");
Glide.with(mContext).load(Urls.BASIC_URL2 + path).fitCenter().into(viewHolder.img);
return convertView;
}
```java
public class MyGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDiskCache(new ExternalCacheDiskCacheFactory(context));
}
@Override
public void registerComponents(Context context, Glide glide) {
}
}
```
使用ExternalCacheDiskCacheFactory可以缓存在sdcard,还可以自定义目录和大小。
详情请看:https://github.com/bumptech/glide/wiki/Configuration#location
最后不要忘记在manifest文件里面声明MyGlideModule
```xml
<meta-data
android:name="yourpackage.MyGlideModule"
android:value="GlideModule"/>
```
```
File b = new File(Environment.getExternalStorageDirectory(), "DCIM/Camera/IMG_20151028_192555.jpg");
File c = new File(Environment.getExternalStorageDirectory() + "/DCIM/Camera/IMG_20151028_192555.jpg");
Glide.with(MainActivity.this).load(b).error(R.drawable.empty_pic).placeholder(R.drawable.empty_pic).into(image2);
```
Glide.with(MainActivity.this).load(b).error(R.drawable.empty_pic).placeholder(R.drawable.empty_pic).listener(new LoggingListener<String, GlideDrawable>()).into(image2);
public class LoggingListener<T, R> implements RequestListener<T, R> {
@Override public boolean onException(Exception e, Object model, Target target, boolean isFirstResource) {
android.util.Log.d("GLIDE", String.format(Locale.ROOT,
"onException(%s, %s, %s, %s)", e, model, target, isFirstResource), e);
return false;
}
@Override public boolean onResourceReady(Object resource, Object model, Target target, boolean isFromMemoryCache, boolean isFirstResource) {
android.util.Log.d("GLIDE", String.format(Locale.ROOT,
"onResourceReady(%s, %s, %s, %s, %s)", resource, model, target, isFromMemoryCache, isFirstResource));
return false;
}
}
更多debug的方式请参考https://github.com/bumptech/glide/wiki/Debugging-and-Error-Handling,找到加载失败的原因才能对症下药
Glide.with(mContext)
.load(url)
.placeholder(R.drawable.loading_spinner)
.into(new SimpleTarget<Bitmap>(width, height) {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
// setImageBitmap(bitmap) on CircleImageView
}
};
发现ListView复用了之前的图片,并没有显示占位图片.,导致滚动图片闪烁的问题,不知道你有没有发现这个问题?
使用了ViewHolder
Glide.with(context)
.load(url)
.asBitmap()
.animate(R.anim.fade_in)//淡入动画效果
.placeholder(placeholderResid)
.into(imageView);
恩,我后来这样子使用,解决了圆形Imageview库冲突的问题,也不会有复用的问题,具体为什么,我也不知道~~
issue 里有提到,但是并没有真正解决我的问题:
https://github.com/bumptech/glide/issues/381
https://github.com/bumptech/glide/issues/484
https://github.com/bumptech/glide/issues/209
https://github.com/bumptech/glide/issues/464
其中[issue484](https://github.com/bumptech/glide/issues/484) 正是我碰到的问题
作者是这样回答的:You're probably loading a lot of images at once on one screen and there's no more memory for the next one.
If this is not the case, then you have a memory leak somewhere.
In any case, reproduce the above exception in your app and then take a heap dump and check what's using so much memory.
You should also note that non-top activities in the active app are not destroyed to reclaim memory.
至今未解