目录
概述
原理浅析
with:创建RequestManager
load:创建RequestBuilder
into:创建Request
重要类:
1.Glide:
2.GlideBuilder:
3.RequestManagerRetriever:
4.RequestManager:
5.RequestBuilder:
6.Request:
7.SingleRequest:
8.Target:
9.AppGlideModule
10.MemorySizeCalculator
缓存机制
1.Glide一级缓存:ActiveResources
2.Glide二级缓存:MemoryCache
3.Glide三级缓存:DiskLruCache
4.缓存加载
5.高级技巧
基本用法
导入库:
加载图片:
1.占位图 / 错误图 / 后备回调图
2.指定加载图片大小
3.指定加载图片格式
4.加载Gif图片
5.加载视频文件
6.RequestOptions
7.TransitionOptions
8.缩略图
9.重用 / 取消加载
10.RecyclerView中使用Glide
11.RecyclerView中使用Glide预加载
回调监听
1.into(Target target)方法:
2.preload()方法:
3.downloadOnly()方法:(待完善……!)
4.listener()方法:
图片变换
1.图片变换的基本用法
2.图片变换的自定义:
3.图片变换开源库(重点推荐):
自定义配置
1.AppGlideModule(用于Application)
2.LibraryGlideModule(用于Library)
3.变更Glide配置
4.替换Glide组件
5.依赖 Glide 注解解析器
6.添加混淆
7.Glide v4禁用清单解析:
8.依赖冲突
Picasso:使用简单,代码简洁,依赖包最小;功能简单,性能较差,没有实现本地缓存。
Glide: 支持Gif WebP Video,生命周期继承,高效缓存策略(支持Memory和Disk缓存;支持多种规格图片缓存;内存开销小,默认RGB_565);方法多,比Picasso复杂,比Fresco简单,性能优于Picasso,劣于Fresco。
Fresco(Facebook): 大大减少OOM,底层使用 C++技术解决图片缓存问题,性能优,几乎全部功能都能在 xml 上定制;用法复杂,依赖包(2M ~ 3M)
Picasso And Glide都可以实现一行代码解决加载图片!引入第三方Lib够用即可(个人愚见),综合考虑性能、易用性、包大小等方面,个人推荐Glide。
Glide内存消耗:
Glide默认Bitmap格式是RGB_565,Picasso默认Bitmap格式是ARGB_8888,Glide加载的图片质量要差于Picasso,而内存开销要小一半。
更改Glide默认格式为ARGB_8888,更改之后,Picasso的内存开销仍然远大于Glide
Glide.with(this)
.load("http://url/cover.jpg")
.apply(new RequestOptions().format(DecodeFormat.PREFER_ARGB_8888))
.into(imageView);
更改之后的内存消耗对比图:Picasso的内存开销仍然远大于Glide,原因在于Picasso是加载了全尺寸的图片到内存,然后让GPU来实时重绘大小。而Glide加载的大小和ImageView的大小是一致的(可以自动计算出任意情况下的ImageView大小),因此更小。
Glide#with() 方法会将 RequestManager 的创建委托给 RequestManagerRetriever,RequestManagerRetriever 为单例类,调用 get(Context) 创建 RequestManager。
Glide.with()方法可以接收Context、Activity、Fragment或者当前应用程序的ApplicationContext类型参数。注意with()方法中传入的实例会决定Glide加载图片的生命周期,如果传入的是Activity或者Fragment的实例,那么当这个Activity或Fragment被销毁的时候,图片加载也会停止。如果传入的是ApplicationContext,那么只有当应用程序被杀掉的时候,图片加载才会停止。
一个Activity / Fragment对应一个RequestManager(单例,生命周期);子线程发起或者Application发起,对应全局RequestManager(单例,生命周期)。
load() 方法返回一个RequestBuilder用于指定待加载的图片资源。Glide支持加载各种各样的图片资源,包括网络图片、本地图片、应用资源、二进制流、Uri对象等等。因此load()方法也有很多个方法重载:
RequestBuilder load(@Nullable Bitmap bitmap);
RequestBuilder load(@Nullable Drawable drawable);
RequestBuilder load(@Nullable String string);
// 加载Uri对象
RequestBuilder load(@Nullable Uri uri);
// 加载本地图片
RequestBuilder load(@Nullable File file);
// 加载应用资源
RequestBuilder load(@RawRes @DrawableRes @Nullable Integer resourceId);
// 加载网络URL
RequestBuilder load(@Nullable URL url);
// 加载二进制流
RequestBuilder load(@Nullable byte[] model);
RequestBuilder load(@Nullable Object model);
RequestBuilder downloadOnly();
RequestBuilder download(@Nullable Object model);
返回一个Target接口类型,该方法最终创建Request并启动,同时也指定了接收请求结果的 Target,into(ImageView) 作为辅助方法,接受一个 ImageView 参数并为其请求的资源类型包装了一个合适的 ImageViewTarget。
Glide关键三步:with,load,into
Glide.with(this)
.load("http://url/cover.jpg")
.into(imageView);
单例类,提供配置Encoder、Decoder、ModelLoader、Pool入口,提供创建 RequestManager 的接口(Glide#with() 方法)
用于创建 Glide 实例的类,其中包含了很多个 get/set 方法,例如设置 BitmapPool、MemoryCache(内存缓存)、DiskCacheFactory(磁盘缓存)等,最终通过这些设置调用 build 方法构建 Glide。
根据with方法传入的参数类型,具体实施创建RequestManager操作。
(1)RequestManager fragmentGet(Context context, android.app.FragmentManager fm);
对应参数类型为Activity或者android.app.Fragment
(2)RequestManager supportFragmentGet(Context context, android.support.v4.app.FragmentManager fm);
对应参数类型为android.support.v4.app.Fragment
(3)RequestManager getApplicationManager(Context context);
子线程中调用with方法,或者传入参数为ApplicationContext
创建 RequestBuilder(通过load方法);通过生命周期管理请求的启动结束等。
构建请求,例如设置 RequestOption、缩略图、加载失败占位图等等。RequestManager 中 load 重载方法,对应 RequestBuilder 中的重载 load 方法,一般来说 load 方法之后就是调用 into 方法设置 ImageView 或者 Target,into 方法中最后会创建 Request,并启动。
接口类,定义了对请求的开始、结束、状态获取、回收等操作,Request中不仅包含基本的信息,还负责管理请求。
Request实现类,负责执行请求并将结果反映到 Target 上。
当我们使用 Glide 加载图片时,会先根据 Target 类型创建不同的 Target,然后 RequestBuilder 将这个 target 当做参数创建 Request 对象,Request 与 Target 就是这样关联起来的。这里就会先创建一个包含 Target 的 SingleRequest 对象。考虑到性能问题,可能会连续创建很多个 SingleRequest 对象,所以使用了对象池来做缓存。
我们经常在 Activity#onCreate 方法中直接使用 Glide 方法,但此时的图片大小还未确定,所以调用 Request#begin 时并不会直接发起请求,而是等待 ImageView 初始化完成,对于 ViewTarget 以及其子类来说,会注册View 的 OnPreDrawListener 事件,等待 View 初始化完成后就调用 SingleRequest#onSizeReady 方法,这个方法里就会开始加载图片了。
onSizeReady 方法并不会去直接加载图片,而是调用了 Engine#load 方法加载,这个方法差不多有二十个参数,所以 onSizeReady 方法算是用来构建参数列表并且调用 Engine#load 方法的。
clear 方法用于停止并清除请求,主要就是从 Engine 中移除掉这个任务以及回调接口。
目标类,代表一个可被 Glide 加载并且具有生命周期的资源,Target 负责展示占位符,加载资源到View,并为每个请求决定合适的尺寸。在调用RequestBuilder#into 方法时会根据传入参数创建对应类型的 Target 实现类。
Glide 默认提供了用于放在 ImageView 上的 ImageViewTarget(以及其各种子类)、放在 AppWidget 上的 AppWidgetTarget、用于同步加载图片的 FutureTarget(只有一个实现类:RequestFutureTarget)等等,
(1)ImageViewTarget(继承自ViewTarget —> BaseTarget)
负责把图片资源加载到ImageView上,图片数据加载完成后会回调其中的 onResourceReady 方法,第一步是将图片设置给 ImageView,第二部是判断是否需要使用动画,需要的话就执行动画。
(2)RequestFutureTarget
调用 RequestBuilder#submit 将会返回一个 FutureTarget,调用 get 方法即可获取到加载的资源对象。
try {
Drawable drawable = Glide.with(this)
.load("http://url/cover.jpg")
.apply(new RequestOptions().format(DecodeFormat.PREFER_ARGB_8888))
.override(Target.SIZE_ORIGINAL)
.submit()
.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
(3)AppWidgetTarget:设置图片到RemoteView上。
(4)NotificationTarget:设置图片到Notification的RemoteView上。
继承自LibraryGlideModule(实现了RegistersComponents接口) 同时实现了AppliesOptions接口。用于延迟设置Glide相关参数。通过这个设置可以保证在 Glide 单例类初始时,所有请求发起之前应用到 Glide。
(1)RegistersComponents接口:用于管理注册 ModelLoader、Encoder、Decoder等组件的,Glide默认使用HttpUrlConnection,可以改为使用OkHttp
(2)AppliesOptions接口 :通过GlideBuilder参数,设置线程池、设置 BitmapPool/ArrayPoll等。
用来计算 BitmapPool 、ArrayPool 以及 MemoryCache 大小。
Glide的缓存设计可以说是非常先进的,考虑的场景也很周全。在缓存这一功能上,Glide又将它分成了两个模块,一个是内存缓存,一个是硬盘缓存。内存缓存的主要作用是防止应用重复将图片数据读取到内存当中,而硬盘缓存的主要作用是防止应用重复从网络或其他地方重复下载和读取数据,二者相互结合。
Glide在实例化时,会初始化三个与缓存相关的类对象以及一个用于计算缓存大小的类对象:
// 根据当前机器参数计算需要设置的缓存大小
if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
// Bitmap池
if (bitmapPool == null) {
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
// 创建内存缓存
if (memoryCache == null) {
memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}
// 创建硬盘缓存
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
弱引用存储当前正在活动的资源,即正在使用的资源。生命周期短,没有大小限制,内部基于Map
从Memory、Disk、File、network获取的图片资源,使用时(例如,在页面显示)存储到ActiveResources,使用完将缓存图片从activeResources中移除,然后再将它put到LruResourceCache当中。这样也就实现了正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存的功能。
内存缓存实现类为 LruResourceCache,继承了 LruCache,LruCache基于LinkedHashMap实现LRU存储策略。LinkedHashMap是Java提供的一个很好的用来实现 LRU 算法的数据结构,其基于 HashMap 实现,同时又将 HashMap 中的 Entity 串成了一个双向链表。
LruResourceCache 中提供了一个 ResourceRemovedListener 接口,当有资源从 MemoryCache 中被移除时会回调其中的方法,Engine 中接收到这个消息后就会进行 Bitmap 的回收操作。
Glide默认开启内存缓存,只要在它还没从内存中被清除之前,下次使用Glide再加载这张图片都会直接从内存当中读取,而不用重新从网络或硬盘上读取了,这样无疑就可以大幅度提升图片的加载效率。比方说你在一个RecyclerView当中反复上下滑动,RecyclerView中只要是Glide加载过的图片都可以直接从内存当中迅速读取并展示出来,从而大大提升了用户体验。
Glide.with(this)
.load(url)
// 关闭内存缓存
.skipMemoryCache(true)
.into(imageView);
磁盘缓存路径默认为 Context#getCacheDir() 下面的 image_manager_disk_cache 文件夹,默认缓存大小为 250MB。
磁盘缓存实现类DiskLruCacheWrapper实现了 DiskCache 接口:
File get(Key key);
void put(Key key, Writer writer);
void delete(Key key);
void clear();
调用diskCacheStrategy()方法并传入DiskCacheStrategy.NONE,就可以禁用掉Glide的硬盘缓存功能了。
Glide.with(this)
.load(url)
// 关闭硬盘缓存
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(imageView);
diskCacheStrategy()方法基本上就是Glide硬盘缓存功能的一切,它可以接收四种参数:
(1)DiskCacheStrategy.NONE: 表示不缓存任何内容。
(2)DiskCacheStrategy.DATA: 表示只缓存原始图片。
(3)DiskCacheStrategy. RESOURCE: 表示只缓存转换之后的图片。
(3)DiskCacheStrategy. AUTOMATIC:表示根据数据源自动选择磁盘缓存策略(默认选择)。尝试对本地和远程图片使用最佳的策略。当你加载远程数据(比如,从URL下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过的原始数据;对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图。
(4)DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
当我们使用Glide去加载一张图片的时候,Glide默认并不会将原始图片展示出来,而是会对图片进行压缩和转换。而Glide默认情况下在硬盘缓存的就是转换过后的图片,我们通过调用diskCacheStrategy()方法则可以改变这一默认行为。
Glide使用的是自己编写的DiskLruCache工具类,而非Google提供的现成的工具类DiskLruCache。但是基本的实现原理都是差不多的。
Picasso和Glide在磁盘缓存策略上有很大的不同。Picasso缓存的是全尺寸的,而Glide缓存的是跟ImageView尺寸相同的。
将ImageView调整成不同大小,但不管大小如何Picasso只缓存一个全尺寸的。Glide则不同,它会为每种大小的ImageView缓存 一次。尽管一张图片已经缓存了一次,但是假如你要在另外一个地方再次以不同尺寸显示,需要重新下载,调整成新尺寸的大小,然后将这个尺寸的也缓存起来。
for example:假如在第一个页面有一个200x200的ImageView,在第二个页面有一个100x100的ImageView,这两个ImageView本来是要显示同一张图片,却需要下载两次。
默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:
(1)活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?
(2)内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?
(3)资源类型(Resource)- 该图片是否之前曾被解码、转换并写入过磁盘缓存?
(4)数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?
前两步检查图片是否在内存中,如果是则直接返回图片;后两步则检查图片是否在磁盘上,以便快速但异步地返回图片。如果四个步骤都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)。
场景描述:
如果应用服务器给访问的图片URL加密,打个比方:在图片URL后面加上用户token参数,作为新的图片开放访问URL,而token作为一个验证身份的参数并不是一成不变的,很有可能时时刻刻都在变化。而如果token变了,那么图片的url也就跟着变了,图片url变了,缓存Key也就跟着变了。结果明明是同一张图片,就因为token不断在改变,导致Glide的缓存功能完全失效了。
解决方案:重写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;
}
}
在load()方法中传入这个自定义的MyGlideUrl对象,而不能再像之前那样直接传入url字符串了。不然的话Glide在内部还是会使用原始的GlideUrl类,而不是我们自定义的MyGlideUrl类。
Glide.with(this)
.load(new MyGlideUrl(url))
.into(imageView);
Glide是一款由Bump Technologies开发的图片加载框架。GitHub:https://github.com/bumptech/glide 中文文档https://muyangmin.github.io/glide-docs-cn/
implementation 'com.github.bumptech.glide:glide:4.11.0'
// OkHttp3集成库
implementation "com.github.bumptech.glide:volley-integration:4.11.0"
// Volley集成库
implementation 'com.github.bumptech.glide:okhttp3-integration:4.11.0'
// RecyclerViewProvider集成库
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
// Excludes the support library because it's already included by Glide.
transitive = false
}
implementation 'com.github.bumptech.glide:annotations:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
使用aar进行依赖:
implementation ("com.github.bumptech.glide:glide:4.11.0@aar") {
transitive = true
}
混淆代码:
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep class com.bumptech.glide.GeneratedAppGlideModuleImpl
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
Kotlin依赖:
dependencies {
kapt 'com.github.bumptech.glide:compiler:4.11.0'
}
Glide.with(this)
.load(url)
.into(imageView);
占位图(placeholder):就是指在图片的加载过程中,我们先显示一张临时的图片,等图片加载出来了再替换成要加载的图片。
错误图(error):在请求永久性失败时展示;在请求的url 为null,且并没有设置 fallback Drawable 时展示。
后备回调图(fallback):在请求的url为 null 时展示。设计 fallback Drawable 的主要目的是允许用户传入 null 的情况。例如,一个 null 的个人资料 url 可能暗示这个用户没有设置头像,应该使用默认头像。默认情况下Glide将 null 作为错误处理,fallback允许用户在输入null情况下把fallback Drawable设置为默认图。
直接设置:
Glide.with(this)
.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.fallback(R.drawable.fallback)
.into(imageView);
RequestOptions:
Glide.with(this)
.load(url)
.apply(new RequestOptions()
.placeholder(R.mipmap.img_placeholder)
.error(R.mipmap.img_error)
.fallback(R.drawable.img_fallback)
.fitCenter())
.into(imageView);
在刚才的三步走之间插入了一个placeholder()方法,然后将占位图片的资源id传入到这个方法中。另外,这个占位图的用法其实也演示了Glide当中绝大多数API的用法,其实就是在load()和into()方法之间串接任意想添加的功能就可以了。
在加载错误后发起一个新的加载请求:
Glide.with(this)
.load(url)
.error(Glide.with(this).load(error_url))
.into(view);
注意事项:
(1)占位图 / 错误图 / 后备回调图不是异步加载的
(2)占位图 / 错误图 / 后备回调图无法应用变换(解决方案是另外自定义View或者本地加入相应资源)
(3)不同的View上最好不要塞入同一个Drawable,应该使用资源ID或者Drawable.newDrawable进行资源拷贝
场景:一张图片的尺寸是1000*1000像素,但是我们界面上的ImageView可能只有200*200像素,这个时候如果你不对图片进行任何压缩就直接读取到内存中,这就属于内存浪费了,因为程序中根本就用不到这么高像素的图片。
Glide会自动判断ImageView的大小,然后只将这么大的图片像素加载到内存当中,帮助我们节省内存开支。
如果需要强制指定加载的图片大小,可以使用如下方法:
Glide.with(this)
.load(url)
.override(100, 100)
.into(imageView);
override()方法指定了一个图片的尺寸,也就是说,Glide现在只会将图片加载成100*100像素的尺寸,而不会管你的ImageView的大小是多少了。
Glide默认加载图片采用Drawable格式,用户可指定加载成Gif,Bitmap,Drawable,File等格式。
asGif:判断图片是否是gif格式,如果不是,则加载error图片;
asBitmap:对于gif / 视频文件,加载第一帧;
Glide.with(this)
.asGif() // asBitmap() asDrawable asFile()
.load(url)
.into(imageView);
(1)默认格式加载
Glide.with(this)
.load(url)
.into(imageView);
(2)gif格式加载:判断图片是否是gif格式,如果不是,则加载error图片
Glide.with(this)
.asGif()
.load(url)
.into(imageView);
(3)Bitmap格式加载:加载第一帧
Glide.with(this)
.asBitmap()
.load(url)
.into(imageView);
加载视频的第一帧,缓存源文件到本地
String videoUrl = "http://gmp4.dwstatic.com/1901/6445176136.mp4";
Glide.with(this)
.load(videoUrl)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(imgView);
RequestOptions(请求选项)可以用于设置占位图 / 错误图 / 后备回调图,图片变换策略,缓存策略,编码配置等 ……
apply() 方法可以被调用多次,即实现多个RequestOptions组合效果。如果 不同RequestOptions 对象存在相互冲突的设置,以最后一个apply 的RequestOptions为准。
Glide.with(this)
.load(url)
.apply(new RequestOptions()
.placeholder(R.mipmap.img_placeholder)
.error(R.mipmap.img_error)
.fallback(R.drawable.img_fallback)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.format(DecodeFormat.PREFER_ARGB_8888)
.fitCenter())
.into(imageView);
正常情况下,Glide先加载placeholder(占位图),等异步加载完资源图片后替换placeholder,替换的视觉效果突然且生硬,为了避免这种视觉上的突然性,可以使用TransitionOptions做淡入变换处理。
TransitionOptions 用于给一个特定的请求指定过渡。根据加载资源的类型,对应不同的TransitionOptions:BitmapTransitionOptions / DrawableTransitionOptions。对于 Bitmap 和 Drawable 之外的资源类型,可以使用 GenericTransitionOptions。
Android中的动画代价是比较大的,交叉淡入和其他涉及 alpha 变化的动画显得尤其昂贵。动画通常比图片解码本身还要耗时。请在使用 Glide 向 ListView , GridView, 或 RecyclerView 加载图片时考虑避免使用动画,尤其是大多数情况下,希望图片被尽快缓存和加载的时候。作为替代方案,请考虑预加载,这样当用户滑动到具体的 item 的时候,图片已经在内存中了。
Glide.with(this)
.load(url)
.transition(BitmapTransitionOptions.withCrossFade(200))
.into(view);
Glide 的默认交叉淡入效果使用了 TransitionDrawable 。它提供两种动画模式,由 setCrossFadeEnabled() 控制。当交叉淡入被禁用时,正在过渡的图片会在原先显示的图像上面淡入。当交叉淡入被启用时,原先显示的图片会从不透明过渡到透明,而正在过渡的图片则会从透明变为不透明。
Glide4 中,默认禁用了交叉淡入。当占位符比实际加载的图片要大,或者图片部分为透明时,禁用交叉淡入会导致动画完成后占位符在图片后面仍然可见。如果你在加载透明图片时使用了占位符,你可以启用交叉淡入,具体办法是调整 DrawableCrossFadeFactory 里的参数并将结果传到 transition() 中:
DrawableCrossFadeFactory factory = new DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true).build();
GlideApp.with(this)
.load(url)
.transition(DrawableTransitionOptions.withCrossFade(factory))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.color.placeholder)
.into(imageView);
Glide不支持多个请求间交叉淡入,解决方案:ViewSwitcher。
如果要改变你的 transition 的默认行为,以更好地控制它在不同的加载源(内存缓存,磁盘缓存,或uri)下是否被应用,可以检查一下 TransitionFactory 中传递给 build() 方法的那个 DataSource 。
缩略图请求与主图请求并行启动,可以起到快速加载,减少等待的效果。
Glide.with(this)
.load(url)
.thumbnail(Glide.with(this)
.load(thumbnailUrl))
.into(imageView);
强制在加载主图的时候,加载一个固定大小的缩略图:
Glide.with(this)
.load(url)
.thumbnail(Glide.with(this)
.load(url)
.override(100, 100))
.into(imageView);
按原图的百分比加载一个缩略图:
Glide.with(this)
.load(url)
.thumbnail(0.5f)
.into(imageView);
重用:
Target target = Glide.with(this)
.load(url)
.into(view);
// 开启一个新的请求newUrl,目标target之前的所有请求将被取消,资源被释放
Glide.with(this)
.load(newUrl)
.into(target);
取消加载:
Glide.with(this).clear(target);
Glide.with(this).clear(view);
首先,Glide 已经自动处理了 View 的复用和请求的取消。如果 url 为 null,Glide 会清空 View 的内容,或者显示 placeholder Drawable 或 fallback Drawable 的内容。
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
String url = urls.get(position);
// Glide 已经自动处理了 View 的复用和请求的取消
Glide.with(fragment)
.load(url)
.into(holder.imageView);
}
特殊场景:对于任何可复用的 View 或 Target ,如果它们在之前的位置上,用 Glide 进行过加载操作,然后在新的位置上需要设置特殊的图片,需要调用clear终止之前的加载行为;否则,就会出现这种情况,你已经为一个 view 设置好了一个 Drawable,但该 view 在之前的位置上使用 Glide 进行过加载图片的操作,Glide 加载完毕后可能会将这个 view 改回成原来的内容。
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (isImagePosition(position)) {
String url = urls.get(position);
Glide.with(fragment)
.load(url)
.into(holder.imageView);
} else {
Glide.with(fragment).clear(holder.imageView);
holder.imageView.setImageDrawable(specialDrawable);
}
}
用户滑动 RecyclerView 时自动加载稍微超前一些的图片,并配合使用正确的图片尺寸和高效率的磁盘缓存策略,达到显著减少用户滑动图片列表时看到的加载指示器的数量。
(1)创建一个 PreloadSizeProvider
(2)创建一个 PreloadModelProvider
(3)创建一个 RecyclerViewPreloader 并将你前两步创建的 PreloadSizeProvider 和 PreloadModelProvider 赋值进去
(4)将你的 RecyclerViewPreloader 添加到你的 RecyclerView 做为一个 scroll listener。
具体代码示例:https://github.com/bumptech/glide/tree/master/integration/recyclerview
into()方法提供了一个接收Target参数的重载。即使我们传入的参数是ImageView,Glide也会在内部自动构建一个Target对象。只要能够掌握自定义Target技术的话,就可以更加随心所欲地控制Glide的回调了,目的是为了将Target作用在任意View上。
Target的继承结构还是相当复杂的,实现Target接口的子类非常多。大多数都是Glide已经实现好的具备完整功能的Target子类,如果我们要进行自定义的话,通常只需要自定义CustomViewTarget。
public class TargetView extends LinearLayout {
private CustomViewTarget viewTarget;
public TargetView(Context context, AttributeSet attrs) {
super(context, attrs);
viewTarget = new CustomViewTarget(this) {
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) { }
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition super Drawable> transition) {
TargetView mLayout = getView();
mLayout.setImageAsBackground(resource);
}
@Override
protected void onResourceCleared(@Nullable Drawable placeholder) { }
};
}
public CustomViewTarget getTarget() {
return viewTarget;
}
public void setImageAsBackground(Drawable resource) {
setBackground(resource);
}
}
在TargetView的构造函数中,我们创建了一个CustomViewTarget的实例,并将TargetView当前的实例this传了进去。CustomViewTarget中需要指定两个泛型,一个是View的类型,一个图片的类型(Drawable或Bitmap)。然后在onResourceReady()方法中,我们就可以通过getView()方法获取到TargetView的实例,并调用它的任意接口了。比如说这里我们调用了setImageAsBackground()方法来将加载出来的图片作为TargetView布局的背景图。
实际Activity中调用TargetView中的Target:
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(targetView.getTarget());
Glide专门给我们提供了预加载的接口,也就是preload()方法,需要时直接使用就可以了。preload()方法有两个方法重载,一个不带参数,表示将会加载图片的原始尺寸,另一个可以通过参数指定加载图片的宽和高。preload()方法的用法也非常简单,直接使用它来替换into()方法即可,如下所示:
Glide.with(this)
.load("http://baidu.com")
.diskCacheStrategy(DiskCacheStrategy. SOURCE)
.preload();
如果使用了preload()方法,最好要将diskCacheStrategy的缓存策略指定成DiskCacheStrategy.SOURCE。因为preload()方法默认是预加载的原始图片大小,而into()方法则默认会根据ImageView控件的大小来动态决定加载图片的大小。因此,如果不将diskCacheStrategy的缓存策略指定成DiskCacheStrategy.SOURCE的话,很容易会造成我们在预加载完成之后再使用into()方法加载图片,却仍然还是要从网络上去请求图片这种现象。
Glide.with(this)
.load("http://baidu.com")
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView);
这里仍然需要将硬盘缓存策略指定成DiskCacheStrategy.SOURCE,以保证Glide一定会去读取刚才预加载的图片缓存。
listener()方法的作用非常普遍,它可以用来监听Glide加载图片的状态。举个例子,比如说刚才使用了preload()方法来对图片进行预加载,但是怎样确定预加载有没有完成呢?还有如果Glide加载图片失败了,该怎样调试错误的原因呢?都在listener()方法当中。
listener()是结合into()方法一起使用的,当然也可以结合preload()方法一起使用。最基本的用法如下所示:
public void loadImage(ImageView view) {
String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";
Glide.with(this)
.load(url)
.listener(new RequestListener() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) {
return false;
}
})
.into(view);
我们在into()方法之前串接了一个listener()方法,然后实现了一个RequestListener的实例。其中RequestListener需要实现两个方法,一个onResourceReady()方法,一个onLoadFailed()方法。从方法名上就可以看出来了,当图片加载完成的时候就会回调onResourceReady()方法,而当图片加载失败的时候就会回调onLoadFailed()方法,onLoadFailed()方法中会将失败的Exception参数传进来,这样我们就可以定位具体失败的原因了。
onResourceReady()方法和onLoadFailed()方法都有一个布尔值的返回值,返回false就表示这个事件没有被处理,还会继续向下传递,返回true就表示这个事件已经被处理掉了,从而不会再继续向下传递。举个简单点的例子,如果我们在RequestListener的onResourceReady()方法中返回了true,那么就不会再回调Target的onResourceReady()方法了。
问题场景:
在1920*1080手机屏幕上给一个宽高设置为wrap_content的ImageView加载一张540*258的图片,结果图片放大了,why?ImageView有scaleType这个属性,在没有指定scaleType属性的情况下,ImageView默认的scaleType是FIT_CENTER,接下来看一下into()方法:
public Target into(ImageView view) {
Util.assertMainThread();
if (view == null) {
throw new IllegalArgumentException("You must pass in a non null View");
}
if (!isTransformationSet && view.getScaleType() != null) {
switch (view.getScaleType()) {
case CENTER_CROP:
applyCenterCrop();
break;
case FIT_CENTER:
case FIT_START:
case FIT_END:
applyFitCenter();
break;
//$CASES-OMITTED$
default:
// Do nothing.
}
}
return into(glide.buildImageViewTarget(view, transcodeClass));
}
如果ImageView的scaleType是CENTER_CROP,则会去调用applyCenterCrop()方法,如果scaleType是FIT_CENTER、FIT_START或FIT_END,则会去调用applyFitCenter()方法。这里的applyCenterCrop()和applyFitCenter()方法其实就是向Glide的加载流程中添加了一个图片变换操作。由于ImageView默认的scaleType是FIT_CENTER,因此会自动添加一个FitCenter的图片变换,而在这个图片变换过程中做了某些操作,导致图片充满了全屏。
解决方案:
Glide提供了专门的API来添加和取消图片变换,想要解决这个问题只需要使用如下代码即可:
Glide.with(this)
.load("http://baidu.com")
.dontTransform()
.into(imageView);
dontTransform()方法,表示让Glide在加载图片的过程中不进行图片变换,这样刚才调用的applyCenterCrop()、applyFitCenter()就统统无效了。
但是使用dontTransform()方法存在着一个问题,就是调用这个方法之后,所有的图片变换操作就全部失效了,那如果有一些图片变换操作是必须要执行的该怎么办呢?这种情况下只需要借助override()方法强制将图片尺寸指定成原始大小就可以了,代码如下所示:
Glide.with(this)
.load(url)
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.into(imageView);
通过override()方法将图片的宽和高都指定成Target.SIZE_ORIGINAL,问题同样被解决了。程序的最终运行结果和上图是完全一样的。
Glide从加载了原始图片到最终展示给用户之前,又进行了一些变换处理,从而能够实现一些更加丰富的图片效果,如图片圆角化、圆形化、模糊化等等。添加图片变换的用法非常简单,我们只需要调用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);
centerCrop()和fitCenter()方法其实也只是对transform()方法进行了一层封装而已,它们背后的源码仍然还是借助transform()方法来实现的。
Glide定制好了一个图片变换的框架,大致的流程是我们可以获取到原始的图片,然后对图片进行变换,再将变换完成后的图片返回给Glide,最终由Glide将图片显示出来。理论上,在对图片进行变换这个步骤中我们可以进行任何的操作,你想对图片怎么样都可以。包括圆角化、圆形化、黑白化、模糊化等等,甚至你将原图片完全替换成另外一张图都是可以的。
DEMO:对图片进行圆形化变换:
自定义一个类让它继承自BitmapTransformation ,然后重写transform()方法,并在这里去实现具体的图片变换逻辑就可以了:
public class CenterCrop extends BitmapTransformation {
@Override
protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
int diameter = Math.min(toTransform.getWidth(), toTransform.getHeight());
final Bitmap toReuse = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888);
final Bitmap result;
result = toReuse;
int dx = (toTransform.getWidth() - diameter) / 2;
int dy = (toTransform.getHeight() - diameter) / 2;
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
BitmapShader shader = new BitmapShader(toTransform, BitmapShader.TileMode.CLAMP,
BitmapShader.TileMode.CLAMP);
if (dx != 0 || dy != 0) {
Matrix matrix = new Matrix();
matrix.setTranslate(-dx, -dy);
shader.setLocalMatrix(matrix);
}
paint.setShader(shader);
paint.setAntiAlias(true);
float radius = diameter / 2f;
canvas.drawCircle(radius, radius, radius, paint);
pool.put(toReuse);
toReuse.recycle();
return result;
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
}
}
继承BitmapTransformation有一个限制,就是只能对静态图进行图片变换。
首先先算出原图宽度和高度中较小的值,因为对图片进行圆形化变换肯定要以较小的那个值作为直径来进行裁剪。之后从Bitmap缓存池中尝试获取一个Bitmap对象来进行重用,如果没有可重用的Bitmap对象的话就创建一个。再具体进行圆形化变换的部分,这里算出了画布的偏移值,并且根据刚才得到的直径算出半径来进行画圆。最后,尝试将复用的Bitmap对象重新放回到缓存池当中,并将圆形化变换后的Bitmap对象进行返回。
这样,一个自定义图片变换的功能就写好了。使用方法非常简单,就是把这个自定义图片变换的实例传入到transform()方法中即可,如下所示:
Glide.with(this)
.load("http://baidu.com")
.transform(new CenterCrop())
.into(imageView);
针对Glide的图片变换,推荐开源库:glide-transformations:https://github.com/wasabeef/glide-transformations
实现了很多通用的图片变换效果,如裁剪变换、颜色变换、模糊变换等等,使得我们可以非常轻松地进行各种各样的图片变换。
implementation 'jp.wasabeef:glide-transformations:4.3.0'
// If you want to use the GPU Filters
implementation 'jp.co.cyberagent.android:gpuimage:2.1.0'
(1)模糊化处理:glide-transformations库中的BlurTransformation类,代码如下所示:
Glide.with(this).load(R.drawable.demo)
.apply(RequestOptions.bitmapTransform(new BlurTransformation(25, 3)))
.into(imageView);
注意这里我们调用的是bitmapTransform()方法而不是transform()方法,因为glide-transformations库都是专门针对静态图片变换来进行设计的。
(2)图片黑白化:glide-transformations库中的GrayscaleTransformation类,代码如下所示:
Glide.with(this).load(R.drawable.demo)
.apply(RequestOptions.bitmapTransform(new GrayscaleTransformation ()))
.into(imageView);
(3)同时执行模糊化和圆角的变换:
Glide.with(this).load(R.drawable.demo)
.apply(RequestOptions.bitmapTransform(new MultiTransformation(new BlurTransformation(25),
new RoundedCornersTransformation(128, 0, RoundedCornersTransformation.CornerType.BOTTOM))))
.into(imageView);
自定义模块功能可以将更改Glide配置,替换Glide组件等操作独立出来,使得我们能轻松地对Glide的各种配置进行自定义,并且又和Glide的图片加载逻辑没有任何交集,这也是一种低耦合编程方式的体现。
(1)自定义AppGlideModule
(2)自定义一个或多个 LibraryGlideModule
(3)给上述两种实现添加 @GlideModule 注解
(4)添加对 Glide 的注解解析器的依赖。
(5)在 proguard 中,添加对 AppGlideModules 的 keep
自定义一个我们自己的模块类,并让它继承抽象父类AppGlideModule:
class MyGlideModule extends AppGlideModule {
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
}
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
super.registerComponents(context, glide, registry);
}
}
我们重写了applyOptions() 和registerComponents() 方法,这两个方法分别就是用来更改Glide配置以及替换Glide组件的。只需要在这两个方法中加入具体的逻辑,就能实现更改Glide配置或者替换Glide组件的功能了。
AppGlideModule.applyOptions():
(1)设置内存缓存
默认配置是LruResourceCache
方案一:使用MemorySizeCalculator:这个类主要关注设备的内存类型,设备 RAM 大小,以及屏幕分辨率。
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context)
.setMemoryCacheScreens(2)
.build();
builder.setMemoryCache(new LruResourceCache(calculator.getMemoryCacheSize()));
}
方案二:直接设置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setMemoryCache(new LruResourceCache(MEMORY_CACHE_SIZE));
}
(2)设置Bitmap池
Glide 使用 LruBitmapPool 作为默认的 BitmapPool,LruBitmapPool 是一个内存中的固定大小的 BitmapPool,使用 LRU 算法清理。默认大小基于设备的分辨率和密度,具体的计算通过 Glide 的 MemorySizeCalculator 来完成,与 Glide 的 MemoryCache 的大小检测方法相似。
方案一:使用MemorySizeCalculator:这个类主要关注设备的内存类型,设备 RAM 大小,以及屏幕分辨率。
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
MemorySizeCalculator calculator = new MemorySizeCalculator.Builder(context)
.setBitmapPoolScreens(2)
.build();
builder.setBitmapPool(new LruBitmapPool(calculator.getMemoryCacheSize()));
}
方案二:直接设置
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setBitmapPool(new LruBitmapPool(BITMAP_CACHE_SIZE));
}
(3)设置磁盘缓存
Glide 使用 DiskLruCacheWrapper 作为默认的磁盘缓存。DiskLruCacheWrapper 是一个使用 LRU 算法的固定大小的磁盘缓存。默认磁盘大小为 250 MB。
Glide默认的硬盘缓存策略使用的是InternalCacheDiskCacheFactory,这种缓存会将所有Glide加载的图片都存储到当前应用的私有目录下。这是一种非常安全的做法,但同时这种做法也造成了一些不便,因为私有目录下即使是开发者自己也是无法查看的,如果想要去验证一下图片到底有没有成功缓存下来,这就有点不太好办了。
Glide 提供ExternalPreferredCacheDiskCacheFactory可以将所有Glide加载的图片都缓存到外部磁盘上。
另外,InternalCacheDiskCacheFactory和ExternalPreferredCacheDiskCacheFactory的默认硬盘缓存大小都是250M。也就是说,如果你的应用缓存的图片总大小超出了250M,那么Glide就会按照DiskLruCache算法的原则来清理缓存的图片。ExternalPreferredCacheDiskCacheFactory默认缓存路径是在sdcard/Android/包名/cache/image_manager_disk_cache目录当中,其中journal文件是DiskLruCache算法的日志文件,这个文件必不可少,且只会有一个。
所以说,也是可以对这个默认的缓存大小进行修改的,而且修改方式非常简单,只需要向ExternalPreferredCacheDiskCacheFactory或者InternalCacheDiskCacheFactory再传入一个参数就可以了,现在我们就将Glide硬盘缓存的大小调整成了500M。
public static final int DISK_CACHE_SIZE = 500 * 1024 * 1024;
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context, DISK_CACHE_SIZE));
}
(4)设置默认Options
builder.setDefaultRequestOptions(
new RequestOptions()
.format(DecodeFormat.PREFER_RGB_565));
如果某个具体请求通过apply RequestOptions对该请求设置请求选项,会覆盖默认的Options。
(5)设置Log Level
builder.setLogLevel(Log.ERROR);
AppGlideModule. registerComponents ()。
组件类型:
ModelLoader:用于加载自定义的 Model(Url, Uri,任意的 POJO ) 和 Data(InputStreams, FileDescriptors)。
ResourceDecoder:用于对新的 Resources(Drawables, Bitmaps) 或新的 Data 类型(InputStreams, FileDescriptors)进行解码。
Encoder:用于向 Glide 的磁盘缓存写 Data (InputStreams, FileDesciptors)。
ResourceTranscoder:用于在不同的资源类型之间做转换,例如,从 BitmapResource 转换为 DrawableResource 。
ResourceEncoder:用于向 Glide 的磁盘缓存写 Resources(BitmapResource, DrawableResource)。
组件通过 Registry 类来注册。例如,添加一个 ModelLoader,使其能从自定义的Model对象中创建一个InputStream:
数据加载:
1)模型(Model) -> 数据(Data) (由模型加载器 ModelLoader 处理)
2)数据(Data) -> 资源(Resource) (由资源解析器 ResourceDecoder 处理)
3)资源(Resource) -> 转码后的资源(Transcoded Resource) (可选;由资源转码器 ResourceTranscoder 处理)
编码器(Encoder)可以在步骤2之前往Glide的磁盘缓存中写入数据;资源编码器(ResourceEncoder)可以在步骤3之前往Glide的磁盘缓存写入资源。
替换组件:
默认情况下,Glide使用的是基于原生HttpURLConnection进行订制的HTTP通讯组件,但是现在大多数的Android开发者都更喜欢使用OkHttp:
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
super.registerComponents(context, glide, registry);
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
OkHttp3 集成库:implementation 'com.github.bumptech.glide:okhttp3-integration:4.11.0'
Volley 集成库:implementation "com.github.bumptech.glide:volley-integration:4.11.0"
implementation 'com.github.bumptech.glide:annotations:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep class com.bumptech.glide.GeneratedAppGlideModuleImpl
Glide v3版本以前,如果想要让自定义GlideModule生效,还得在AndroidManifest.xml文件当中加入如下配置才行:
在
Glide v4 的 AppGlideModule 和 LibraryGlideModule,你可以完全禁用清单解析。这样可以改善 Glide 的初始启动时间,并避免尝试解析元数据时的一些潜在问题。
@Override
public boolean isManifestParsingEnabled() {
// 如果依赖中使用了Glide的 annotation processor,则需要在AppGlideModule实现类里返回false
return false;
}
AppGlideModule 实现是一个信号,它会让 Glide 的注解解析器生成一个单一的所有已发现的 LibraryGlideModules 的联合类。对于一个特定的应用,只能存在一个 AppGlideModule。避免在Library中使用AppGlideModule,否则将会阻止依赖该Library的任何App对Glide进行配置诸如 Glide 缓存大小和位置之类的选项。如果两个程序Library都包含 AppGlideModule,App将无法在同时依赖两个Library的情况下通过编译。
使用@Excludes(排除)解决一个AppGlideModule包含多个LibraryGlideModule情况下,LibraryGlideModule之间组件冲突的问题。
@Excludes({com.example.DemoLibraryGlideModule1, com.example.DemoLibraryGlideModule2})
@GlideModule
public class MyGlideModule extends AppGlideModule {