1.什么是Glide?android
Glide是一款由Bump Technologies开发的图片加载框架,使得咱们能够在Android平台上以极度简单的方式加载和展现图片。 git
2.依赖,网络权限:github
compile 'com.github.bumptech.glide:glide:3.7.0' //3.7的依赖canvas
<uses-permission android:name="android.permission.INTERNET" /> 网络权限缓存
3.基本用法网络
Glide.with(this).load(url).into(imageView);app
①.Glide在Activity,Fragment,Adapter中使用with参数的区别?框架
with()方法中传入的实例会决定Glide加载图片的生命周期,若是传入的是Activity或者Fragment的实例,那么当这个Activity或Fragment被销毁的时候,图片加载也会中止。若是传入的是ApplicationContext,那么只有当应用程序被杀掉的时候,图片加载才会中止。 ide
②. load()方法源码分析
这个方法用于指定待加载的图片资源。Glide支持加载各类各样的图片资源,包括网络图片、本地图片、应用资源、二进制流、Uri对象等等。所以load()方法也有不少个方法重载
//加载本地图片:
File file = new File(getExternalCacheDir()+"/image.jpg");
Glide.with(this).load(file).into(imageView);
// 加载应用资源
int resource = R.drawable.image;
Glide.with(this).load(resource).into(imageView);
// 加载二进制流
byte[] image = getImageBytes();
Glide.with(this).load(image).into(imageView);
// 加载Uri对象
Uri imageUri = getImageUri();
Glide.with(this).load(imageUri).into(imageView);
③.into()方法
into()方法不只仅是只能接收ImageView类型的参数
4.使用
Glide.with(this) .load(url)//加载地址 // .asGif()//只容许加载动态图片,若是加载静态图片会加载失败 .asBitmap()//只容许加载静态图片 .placeholder(R.mipmap.ic_launcher)//占位图 .error(R.mipmap.ic_launcher)//异常占位图 .diskCacheStrategy(DiskCacheStrategy.NONE)//禁用掉Glide的缓存功能。 .override(100, 100)//指定图片大小 .into(imageView);
5.源码分析
在没阅读Glide源码以前,咱们带着下面几个问题去阅读源码,但愿在阅读源码的过程当中能够解决:
with()方法
传入Application参数的状况:
若是在Glide.with()方法中传入的是一个Application对象,那么这里就会调用带有Context参数的get()方法重载,而后会在调用getApplicationManager()方法来获取一个RequestManager对象。其实这是最简单的一种状况,由于Application对象的生命周期即应用程序的生命周期,所以Glide并不须要作什么特殊的处理,它自动就是和应用程序的生命周期是同步的,若是应用程序关闭的话,Glide的加载也会同时终止
传入非Application参数的状况:
无论你在Glide.with()方法中传入的是Activity、FragmentActivity、v4包下的Fragment、仍是app包下的Fragment,最终的流程都是同样的,那就是会向当前的Activity当中添加一个隐藏的Fragment。具体添加的逻辑是在上述代码的第117行和第141行,分别对应的app包和v4包下的两种Fragment的状况。那么这里为何要添加一个隐藏的Fragment呢?由于Glide须要知道加载的生命周期。很简单的一个道理,若是你在某个Activity上正在加载着一张图片,结果图片还没加载出来,Activity就被用户关掉了,那么图片还应该继续加载吗?固然不该该。但是Glide并无办法知道Activity的生命周期,因而Glide就使用了添加隐藏Fragment的这种小技巧,由于Fragment的生命周期和Activity是同步的,若是Activity被销毁了,Fragment是能够监听到的,这样Glide就能够捕获这个事件并中止图片加载了。
若是咱们是在非主线程当中使用的Glide,那么无论你是传入的Activity仍是Fragment,都会被强制当成Application来处理。
总结:with()方法其实就是为了获得一个RequestManager对象而已,而后Glide会根据咱们传入with()方法的参数来肯定图片加载的生命周期
load()方法:
调用fromString()方法,再调用load()方法,而后把传入的图片URL地址传进去。而fromString()方法也极为简单,就是调用了loadGeneric()方法,而且指定参数为String.class,由于load()方法传入的是一个字符串参数,因能够看出大多数的操做是在fromString()中的loadGeneric()方法中进行的
loadGeneric()调用了Glide.buildStreamModelLoader()和Glide.buildFileDescriptorModelLoader()方法来得到ModelLoader对象。ModelLoader对象是用于加载图片的,而咱们给load()方法传入不一样类型的参数,这里也会获得不一样的ModelLoader对象,因为咱们刚才传入的参数是String.class,所以最终获得的是StreamStringLoader对象,它是实现了ModelLoader接口的。
loadGeneric()方法是要返回一个DrawableTypeRequest对象的,所以在loadGeneric()方法的最后又去new了一个DrawableTypeRequest对象,而后把刚才得到的ModelLoader对象,还有一大堆杂七杂八的东西都传了进去
DrawableTypeRequest
主要的就是它提供了asBitmap()和asGif()这两个方法 用于强制指定加载静态图片和动态图片
而从源码中能够看出,它们分别又建立了一个BitmapTypeRequest和GifTypeRequest,若是没有进行强制指定的话,那默认就是使用DrawableTypeRequest。
DrawableTypeRequest 里面没有load方法,于是它是父类里面的方法
into()方法:
Glide缓存分为俩种:
①.内存缓存
内存缓存的主要做用是防止应用重复将图片数据读取到内存当中
②.磁盘缓存
硬盘缓存的主要做用是防止应用重复从网络或其余地方重复下载和读取数据
缓存key:
Engine类里面的load()方法中的 fetcher.getId()方法得到了一个id字符串,这个字符串也就是咱们要加载的图片的惟一标识,好比说若是是一张网络上的图片的话,那么这个id就是这张图片的url地址
下一行,将这个id连同着signature、width、height等等10个参数一块儿传入到EngineKeyFactory的buildKey()方法当中,从而构建出了一个EngineKey对象,这个EngineKey也就是Glide中的缓存Key了。
可见,决定缓存Key的条件很是多,即便你用override()方法改变了一下图片的width或者height,也会生成一个彻底不一样的缓存Key。
有了缓存Key,接下来就能够开始进行缓存了,那么咱们先从内存缓存看起。
首先你要知道,默认状况下,Glide自动就是开启内存缓存的。也就是说,当咱们使用Glide加载了一张图片以后,这张图片就会被缓存到内存当中,只要在它还没从内存中被清除以前,下次使用Glide再加载这张图片都会直接从内存当中读取,而不用从新从网络或硬盘上读取了,这样无疑就能够大幅度提高图片的加载效率。比方说你在一个RecyclerView当中反复上下滑动,RecyclerView中只要是Glide加载过的图片均可以直接从内存当中迅速读取并展现出来,从而大大提高了用户体验。
而Glide最为人性化的是,你甚至不须要编写任何额外的代码就能自动享受到这个极为便利的内存缓存功能,由于Glide默认就已经将它开启了。
禁用缓存功能:
Glide.with(this) .load(url) .skipMemoryCache(true) .into(imageView);
Glide.with(this) .load(url) .diskCacheStrategy(DiskCacheStrategy.NONE) .into(imageView);
调用diskCacheStrategy()方法并传入DiskCacheStrategy.NONE,就能够禁用掉Glide的硬盘缓存功能了。
这个diskCacheStrategy()方法基本上就是Glide硬盘缓存功能的一切,它能够接收四种参数:
实现图片预加载 preload():
Glide.with(this) .load(url) .diskCacheStrategy(DiskCacheStrategy.SOURCE) .preload();
若是使用了preload()方法,最好要将diskCacheStrategy的缓存策略指定成DiskCacheStrategy.SOURCE。由于preload()方法默认是预加载的原始图片大小,而into()方法则默认会根据ImageView控件的大小来动态决定加载图片的大小。所以,若是不将diskCacheStrategy的缓存策略指定成DiskCacheStrategy.SOURCE的话,很容易会形成咱们在预加载完成以后再使用into()方法加载图片,却仍然仍是要从网络上去请求图片这种现象。
PreloadTarget的源码很是简单,obtain()方法中就是new了一个PreloadTarget的实例而已,而onResourceReady()方法中也没作什么事情,只是调用了Glide.clear()方法。
这里的Glide.clear()并非清空缓存的意思,而是表示加载已完成,释放资源的意思,所以不用在这里产生疑惑。
访问图片的缓存文件 downloadOnly()方法是定义在DrawableTypeRequest类:
有两个方法重载,一个接收图片的宽度和高度,另外一个接收一个泛型对象
这两个方法各自有各自的应用场景,其中downloadOnly(int width, int height)是用于在子线程中下载图片的,而downloadOnly(Y target)是用于在主线程中下载图片的。
downloadOnly(int width, int height)的用法。当调用了downloadOnly(int width, int height)方法后会当即返回一个FutureTarget对象,而后Glide会在后台开始下载图片文件。接下来咱们调用FutureTarget的get()方法就能够去获取下载好的图片文件了,若是此时图片尚未下载完,那么get()方法就会阻塞住,一直等到图片下载完成才会有值返回。
必须将硬盘缓存策略指定成DiskCacheStrategy.SOURCE或者DiskCacheStrategy.ALL,不然Glide将没法使用咱们刚才下载好的图片缓存文件。
String url = "http://172.16.54.8:8080/test/zy.jpg"; 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) { return false; } }) .into(simpleTarget);
onResourceReady()方法和onException()方法都有一个布尔值的返回值,返回false就表示这个事件没有被处理,还会继续向下传递,返回true就表示这个事件已经被处理掉了,从而不会再继续向下传递。举个简单点的例子,若是咱们在RequestListener的onResourceReady()方法中返回了true,那么就不会再回调Target的onResourceReady()方法了。
requestListener的onResourceReady()方法,只有当这个onResourceReady()方法返回false的时候,才会继续调用Target的onResourceReady()方法,这也就是listener()方法的实现原理
requestListener的onException()方法,只有在onException()方法返回false的状况下才会继续调用setErrorPlaceholder()方法。也就是说,若是咱们在onException()方法中返回了true,那么Glide请求中使用error(int resourceId)方法设置的异常占位图就失效了。
dontTransform()方法:表示让Glide在加载图片的过程当中不进行图片变换
Glide.with(this) .load(url) .dontTransform() .into(imageView);
使用dontTransform()方法存在着一个问题,就是调用这个方法以后,全部的图片变换操做就所有失效了,那若是我有一些图片变换操做是必需要执行的该怎么办呢?不用担忧,总归是有办法的,这种状况下咱们只须要借助override()方法强制将图片尺寸指定成原始大小就能够了,代码以下所示:
Glide.with(this) .load(url) .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) .into(imageView);
经过override()方法将图片的宽和高都指定成Target.SIZE_ORIGINAL,问题一样被解决了
添加图片变换的用法很是简单,咱们只须要调用transform()方法,并将想要执行的图片变换操做做为参数传入transform()方法便可,以下所示:
Glide.with(this) .load(url) .transform(...) .into(imageView);
至于具体要进行什么样的图片变换操做,这个一般都是须要咱们本身来写的。不过Glide已经内置了两种图片变换操做,咱们能够直接拿来使用,一个是CenterCrop,一个是FitCenter。
但这两种内置的图片变换操做其实都不须要使用transform()方法,Glide为了方便咱们使用直接提供了现成的API:
Glide.with(this) .load(url) .centerCrop() .into(imageView); Glide.with(this) .load(url) .fitCenter() .into(imageView);
public class CenterCrop extends BitmapTransformation { public CenterCrop(Context context) { super(context); } public CenterCrop(BitmapPool bitmapPool) { super(bitmapPool); } // Bitmap doesn't implement equals, so == and .equals are equivalent here. @SuppressWarnings("PMD.CompareObjectsWithEquals") @Override protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { final Bitmap toReuse = pool.get(outWidth, outHeight, toTransform.getConfig() != null ? toTransform.getConfig() : Bitmap.Config.ARGB_8888); Bitmap transformed = TransformationUtils.centerCrop(toReuse, toTransform, outWidth, outHeight); if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) { toReuse.recycle(); } return transformed; } @Override public String getId() { return "CenterCrop.com.bumptech.glide.load.resource.bitmap"; } }
CenterCrop是继承自BitmapTransformation的,这个是重中之重,由于整个图片变换功能都是创建在这个继承结构基础上的。
接下来CenterCrop中最重要的就是transform()方法,其余的方法咱们能够暂时忽略。transform()方法中有四个参数,每个都很重要,咱们来一一解读下。第一个参数pool,这个是Glide中的一个Bitmap缓存池,用于对Bitmap对象进行重用,不然每次图片变换都从新建立Bitmap对象将会很是消耗内存。第二个参数toTransform,这个是原始图片的Bitmap对象,咱们就是要对它来进行图片变换。第三和第四个参数比较简单,分别表明图片变换后的宽度和高度,其实也就是override()方法中传入的宽和高的值了
transform()方法的细节,首先第一行就从Bitmap缓存池中尝试获取一个可重用的Bitmap对象,而后把这个对象连同toTransform、outWidth、outHeight参数一块儿传入到了TransformationUtils.centerCrop()方法当中
public final class TransformationUtils { public static Bitmap centerCrop(Bitmap recycled, Bitmap toCrop, int width, int height) { if (toCrop == null) { return null; } else if (toCrop.getWidth() == width && toCrop.getHeight() == height) { return toCrop; } // From ImageView/Bitmap.createScaledBitmap. final float scale; float dx = 0, dy = 0; Matrix m = new Matrix(); if (toCrop.getWidth() * height > width * toCrop.getHeight()) { scale = (float) height / (float) toCrop.getHeight(); dx = (width - toCrop.getWidth() * scale) * 0.5f; } else { scale = (float) width / (float) toCrop.getWidth(); dy = (height - toCrop.getHeight() * scale) * 0.5f; } m.setScale(scale, scale); m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); final Bitmap result; if (recycled != null) { result = recycled; } else { result = Bitmap.createBitmap(width, height, getSafeConfig(toCrop)); } // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given. TransformationUtils.setAlpha(toCrop, result); Canvas canvas = new Canvas(result); Paint paint = new Paint(PAINT_FLAGS); canvas.drawBitmap(toCrop, m, paint); return result; } }
这段代码就是整个图片变换功能的核心代码了,先作了一些校验,若是原图为空,或者原图的尺寸和目标裁剪尺寸相同,那么就放弃裁剪,经过数学计算来算出画布的缩放的比例以及偏移值,判断缓存池中取出的Bitmap对象是否为空,若是不为空就能够直接使用,若是为空则要建立一个新的Bitmap对象,将原图Bitmap对象的alpha值复制到裁剪Bitmap对象上面,裁剪Bitmap对象进行绘制,并将最终的结果进行返回
获得了裁剪后的Bitmap对象,咱们再回到CenterCrop当中,你会看到,在最终返回这个Bitmap对象以前,还会尝试将复用的Bitmap对象从新放回到缓存池当中,以便下次继续使用
使用技巧:
1.Glide.with(context).resumeRequests()和 Glide.with(context).pauseRequests()
当列表在滑动的时候,调用pauseRequests()取消请求,滑动中止时,调用resumeRequests()恢复请求。这样是否是会好些呢?
2.Glide.clear()
当你想清除掉全部的图片加载请求时,这个方法能够帮助到你。
3.ListPreloader
若是你想让列表预加载的话,不妨试一下ListPreloader这个类。