glide性能优化实战
前言
项目使用glide加载图片之前也只是会基本api,这次项目有非常多的图片需要展示,而且设备是一个android12的版本,但是性能不太理想,分给APP的资源不太多,所以需要优化现有图片加载逻辑,读者可以根据自己的项目自行选择优化项。
关于Glide的简单介绍
Glide是一个快速高效的Android图片加载库,注重于平滑的滚动。Glide提供了易用的API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。
Glide 支持拉取,解码和展示视频快照,图片,和GIF动画。Glide的Api是如此的灵活,开发者甚至可以插入和替换成自己喜爱的任何网络栈。默认情况下,Glide使用的是一个定制化的基于HttpUrlConnection的栈,但同时也提供了与Google Volley和Square OkHttp快速集成的工具库。
虽然Glide 的主要目标是让任何形式的图片列表的滚动尽可能地变得更快、更平滑,但实际上,Glide几乎能满足你对远程图片的拉取/缩放/显示的一切需求。
API
Glide 使用简明的流式语法API,这是一个非常棒的设计,因为它允许你在大部分情况下一行代码搞定需求:
kotlin
Glide.with(context)
.load(url)
.into(imageView);
性能
Glide 充分考虑了Android图片加载性能的两个关键方面:
- 图片解码速度
- 解码图片带来的资源压力
为了让用户拥有良好的App使用体验,图片不仅要快速加载,而且还不能因为过多的主线程I/O或频繁的垃圾回收导致页面的闪烁和抖动现象。
Glide使用了多个步骤来确保在Android上加载图片尽可能的快速和平滑:
- 自动、智能地下采样(downsampling)和缓存(caching),以最小化存储开销和解码次数;
- 积极的资源重用,例如字节数组和Bitmap,以最小化昂贵的垃圾回收和堆碎片影响;
- 深度的生命周期集成,以确保仅优先处理活跃的Fragment和Activity的请求,并有利于应用在必要时释放资源以避免在后台时被杀掉。
关于Glide的初步优化
上面都是官网的介绍,下面我们进行初步的一个优化。
缓存策略
首先是图片的缓存策略。几个可配置的缓存策略属性在DiskCacheStrategy类中都有介绍
-
NONE 不使用缓存,每次都重新加载
-
DATA 在解码之前,将检索到的数据直接写入磁盘缓存。
-
RESOURCE 在解码后,再将检索到的数据写入磁盘缓存。
-
AUTOMATIC 尝试根据DataFetcher和EncodeStrategy中的ResourceEncoder(如果ResourceEncoder可用的话)的数据源智能地选择策略。
-
ALL 如果是远程数据使用DATA和RESOURCE缓存,如果是本地数据使用RESOURCE缓冲。
项目加载的都是本地图片,所以在加载速度和缓存大小的权衡下,使用了RESOURCE
kotlin
...
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
...
图片裁剪
因为缓存大小跟图片大小是有直接关系的,所以加载前最好将其裁剪为和控件大小一致,避免过大的图片造成ANR和Out Of Memory
kotlin
...
.override(your size)
...
移除加载动画
Glide为了保证图片加载的过度流畅性,不显得突兀,是有默认的动画和变换的,但这也会消耗一定的性能,所以我们将它关闭
kotlin
...
.dontAnimate()
.dontTransform()
...
其他
(可选)如果需要在加载过程中对图片进行处理,可以使用RequestListener
初步结果
都加上过后大概是这个样子
kotlin
Glide.with(context)
.load(url)
.override(your size)
.dontAnimate()
.dontTransform()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(imageView);
可以从Profiler中看到加载一个list的图片时,内存和CPU都有下降。
关于Glide的AppGlideModule定制
在新版Glide中,可以通过自定义AppGlideModule来全局定义glide的设置
kotlin
import android.content.Context
import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.bitmap_recycle.LruArrayPool
import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool
import com.bumptech.glide.load.engine.cache.DiskLruCacheWrapper
import com.bumptech.glide.load.engine.cache.LruResourceCache
import com.bumptech.glide.load.engine.cache.MemorySizeCalculator
import com.bumptech.glide.load.engine.executor.GlideExecutor
import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.request.RequestOptions
import com.fawvw.hmi.media.common.utils.LogUtil
import java.io.File
@GlideModule
class MyAppGlideModule : AppGlideModule() {
private val TAG = "MyAppGlideModule"
//图片文件缓存 10M
private var IMAGE_CACHE_COUNT = 10 * 1024 * 1024
private val SOURCE_EXECUTOR_NAME = "source"
private val ANIMATION_EXECUTOR_NAME = "animation"
override fun applyOptions(context: Context, builder: GlideBuilder) {
super.applyOptions(context, builder)
val calculator = MemorySizeCalculator.Builder(context).build()
val defaultMemoryCacheSize = calculator.memoryCacheSize
val defaultBitmapPoolSize = calculator.bitmapPoolSize
val defaultArrayPoolSize = calculator.arrayPoolSizeInBytes
LogUtil.d(
TAG,
"defaultMemoryCacheSize: $defaultMemoryCacheSize, defaultBitmapPoolSize: $defaultBitmapPoolSize, defaultArrayPoolSize: $defaultArrayPoolSize"
)
builder.setDefaultRequestOptions(
RequestOptions().format(DecodeFormat.PREFER_RGB_565)
)
val cacheLocation = File(context.externalCacheDir, "GlideCache")
if (!cacheLocation.exists()) {
cacheLocation.mkdirs()
}
//设置glide文件缓存为10M
builder.setDiskCache {
DiskLruCacheWrapper.create(cacheLocation, IMAGE_CACHE_COUNT.toLong())
}
//设置glide内存,bitmap池,数组池砍
builder.setMemoryCache(LruResourceCache((defaultMemoryCacheSize / 4).toLong()))
builder.setBitmapPool(LruBitmapPool((defaultBitmapPoolSize / 4).toLong()))
builder.setArrayPool(LruArrayPool(defaultArrayPoolSize / 4))
//设置Source线程最大数量为1
builder.setSourceExecutor(
GlideExecutor.newSourceBuilder()
.setThreadCount(1)
.setName(SOURCE_EXECUTOR_NAME)
.build()
)
//设置Animation线程最大数量为1
builder.setAnimationExecutor(
GlideExecutor.newAnimationBuilder()
.setThreadCount(1)
.setName(ANIMATION_EXECUTOR_NAME)
.build()
)
}
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
super.registerComponents(context, glide, registry)
}
}
编译过后,就可以调用GlideApp了,用于将Glide替换掉,注意别忘了添加@GlideModule注解。
总结
在进行了上面的两个定制过后,CPU和内存都有明显的下降,但是有些配置确实会导致加载速度变慢和用户体验变差,需要自己权衡。