glide和picasso
Glide和Picasso都是图片加载框架,用法相似:
//Glide的用法
Glide.with(context).load(url).into(imageView);
//Picasso的用法
Picasso.with(context).load(url).into(imageView);
不同的地方,也是Glide的优点:
- with()方法里面的参数可以是Context、Activity、Fragment,并且后面两个参数类型可以使这个图片和后面连个参数的生命周期绑定。
- Bitmap的默认格式是RGB_565,所以是Glide的默认格式,而Picasso的默认格式是ARGB_8888,这个格式的图片质量高于前者,但是内存开销较大。
- Picasso和Glide在磁盘缓存策略上有很大的不同,Picasso缓存的是全尺寸的,而Glide缓存的是跟ImageView尺寸相同的,也就是说如果将ImageView调整成不同大小,Picasso都只缓存一个全尺寸的,而Glide则不同,它会为每种大小的ImageView缓存一次。所以如果一张图片已经缓存了一次,但是假如要在另外一个地方再次以不同尺寸显示,需要重新下载,调整成新尺寸的大小,然后将这个尺寸的也缓存起来(不过可以在里面设置,使得每次加载不同大小的都放进内存,下次在加载的时候就可以直接在内存里面取得)。
- 特别的,Glide还能加载GIF动态图,可以和生命周期绑定,使动态图随生命周期的变化而变化。
两者缺点:
-
Picasso每次加载的都是全尺寸图片,导致内存开销大,当然这个和他的格式为ARGB_8888有关,不过就算是Glide也是ARGB_8888内存开销也比Picasso小得多
/** ** 将Glide中Bitmap的格式改为ARGB_8888 **/ //第一步:创建一个新的GlideModule将Bitmap格式转换到ARGB_8888 public class GlideConfiguration implements GlideModule { @Override public void applyOptions(Context context, GlideBuilder builder) { // Apply options to the builder here. builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888); } @Override public void registerComponents(Context context, Glide glide) { // register ModelLoaders here. } } //第二步:同时在AndroidManifest.xml中将GlideModule定义为meta-data <meta-data android:name="com.inthecheesefactory.lab.glidepicasso.GlideConfiguration" android:value="GlideModule"/>
-
Glide由于在每次加载不同大小的图片时,都是重新下载,而为了解决这种性能问题,选择将每次下载的图片都放入磁盘,但是也就造成了占用更大的空间。
缓存Glide加载的图片到本地
Glide提供了一个downloadOnly() 接口来获取缓存的图片文件,但是前提必须要设置diskCacheStrategy方法的缓存策略为DiskCacheStrategy.ALL或者DiskCacheStrategy.SOURCE,还有downloadOnly()方法需要在线程里进行。
private class getImageCacheAsyncTask extends AsyncTask<String, Void, File> {
private final Context context;
public getImageCacheAsyncTask(Context context) {
this.context = context;
}
@Override
protected File doInBackground(String... params) {
String imgUrl = params[0];
try {
return Glide.with(context)
.load(imgUrl)
.downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.get();
} catch (Exception ex) {
return null;
}
}
@Override
protected void onPostExecute(File result) {
if (result == null) {
return;
}
//此path就是对应文件的缓存路径
String path = result.getPath();
Log.e("path", path);
Bitmap bmp= BitmapFactory.decodeFile(path);
img.setImageBitmap(bmp);
}
}
Glide的缓存机制
一般在做图片的缓存的时候都要考虑到内存泄漏的问题,使用内存缓存技术可以很好的解决这个问题,它可以让组件快速地重新加载和处理图片,内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除,当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片,BitmapWorkerTask 还要把新加载的图片的键值对放到缓存中。
在Glide框架里面的缓存:
Glide缓存分为 内存缓存 和 硬盘缓存。
这两个缓存模块的作用各不相同,内存缓存 的主要作用是防止应用重复将图片数据读取到内存当中,而 硬盘缓存 的主要作用是防止应用重复从网络或其他地方重复下载和读取数据。
-
内存缓存使用弱引用和LruCache结合完成的,弱引用来缓存的是正在使用中的图片。图片封装类Resources内部有个计数器判断是该图片否正在使用。
- 读:是先从lruCache取,取不到再从弱引用中取;
- 存:内存缓存取不到,从网络拉取回来先放在弱引用里,渲染图片,图片对象Resources使用计数加一;
- 渲染完图片,图片对象Resources使用计数减一,如果计数为0,图片缓存从弱引用中删除,放入lruCache缓存。
-
磁盘缓存
-
读:先找处理后(result)的图片,没有的话再找原图。
-
存:先存原图,再存处理后的图。
//磁盘缓存的四个参数 DiskCacheStrategy.NONE: 表示不缓存任何内容。 DiskCacheStrategy.SOURCE: 表示只缓存原始图片。 DiskCacheStrategy.RESULT: 表示只缓存转换过后的图片(默认选项)。 DiskCacheStrategy.ALL : 表示既缓存原始图的图片。
-
Glide的缓存Key(key唯一标识图片)的生成代码在Engine类的load()方法中,在里面调用了fetcher.getId()
方法获得了一个id字符串,这个字符串也就是我们要加载的图片的唯一标识。接下来将这个id连同着signature、width、height等等10个参数一起传入到EngineKeyFactory的buildKey()方法当中,从而构建出了一个EngineKey对象,这个EngineKey也就是Glide中的缓存Key了。EngineKey类的源码,其实主要就是重写了equals()和hashCode()方法,保证只有传入EngineKey的所有参数都相同的情况下才认为是同一个EngineKey对象。
关于Glide使用的问题:为了对图片资源进行保护,会在图片url地址的基础之上再加上一个token参数,token作为一个验证身份的参数并不是一成不变的,很有可能时时刻刻都在变化。而如果token变了,那么图片的url也就跟着变了,图片url变了,缓存Key也就跟着变了。结果就造成了,明明是同一张图片,就因为token不断在改变,导致Glide的缓存功能完全失效了。
解决:重写getCacheKey()方法,在里面加入了一段逻辑用于将图片url地址中token参数的这一部分移除掉。这样getCacheKey()方法得到的就是一个没有token参数的url地址,从而不管token怎么变化,最终Glide的缓存Key都是固定不变的了
//加载自定义的类
Glide.with(this)
.load(new MyGlideUrl(url))
.into(imageView);
//继承GlideUrl重写getCacheKey()方法
public class MyGlideUrl extends GlideUrl {
private String mUrl;
public MyGlideUrl(String url) {
super(url);
mUrl = url;
}
@Override
public String getCacheKey() {
return mUrl.replace(findTokenParam(), "");
}
private String findTokenParam() {
String tokenParam = "";
int tokenKeyIndex = mUrl.indexOf("?token=") >= 0 ? mUrl.indexOf("?token=") : mUrl.indexOf("&token=");
if (tokenKeyIndex != -1) {
int nextAndIndex = mUrl.indexOf("&", tokenKeyIndex + 1);
if (nextAndIndex != -1) {
tokenParam = mUrl.substring(tokenKeyIndex + 1, nextAndIndex + 1);
} else {
tokenParam = mUrl.substring(tokenKeyIndex);
}
}
return tokenParam;
}
}
Glide如何确定图片加载完毕
使用 RequestListener
接口,例如:
Glide.with(fragment)
.load(url)
.listener(new RequestListener() {
@Override
boolean onLoadFailed(@Nullable GlideException e, Object model,
Target<R> target, boolean isFirstResource) {
// Log errors here.
}
@Override
boolean onResourceReady(R resource, Object model, Target<R> target,
DataSource dataSource, boolean isFirstResource) {
// Log successes here or use DataSource to keep track of cache hits and misses.
}
})
.into(imageView);
引入另一个话题:在展示高分辨率图片的时候,最好先将图片进行压缩,压缩后的图片大小应该和用来展示它的控件大小相近。在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响。
//压缩的代码,压缩为100*100
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
通过设置BitmapFactory.Options中inSampleSize的值就可以实现。比如我们有一张2048*1536像素的图片,将inSampleSize的值设置为4,就可以把这张图片压缩成512*384像素
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {
// 源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
- BitmapFactory这个类提供了多个解析方法
(decodeByteArray, decodeFile, decodeResource等)
用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法- SD卡中的图片可以使用decodeFile方法
- 网络上的图片可以使用decodeStream方法
- 资源文件中的图片可以使用decodeResource方法
- 这些方法会尝试为已经构建的bitmap分配内存,这时就会很容易导致OOM出现。
- 为此每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。
- 虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;