6、图片加载框架深度解析:Glide源码分析(缓存、生命周期、请求管理)、Fresco的Ashmem机制、WebP与SVG格式选择
图片加载,几乎是每个Android应用都绕不开的坎儿。我见过太多应用因为图片处理不当,导致OOM、列表卡顿、流量浪费。说实话,选对图片框架,你的性能优化就成功了一半。
今天咱们就深入聊聊三个主流方案:Glide、Fresco,以及图片格式的选择。我会结合自己踩过的坑,把它们的核心机制掰开揉碎了讲清楚。
6.1 Glide:最流行的图片加载库,你真的用对了吗?
Glide是目前Android圈使用最广的图片加载库。为什么?因为它对Android生态的适配做得最好——生命周期感知、缓存策略灵活、请求管理自动。但很多人只是把它当个"加载图片的工具",根本没发挥出它的全部实力。
6.1.1 缓存机制:三级缓存与LRU算法
Glide的缓存分为三级:活动缓存(Active Resources)、内存缓存(Memory Cache)、磁盘缓存(Disk Cache)。
活动缓存:正在被使用的图片,用弱引用持有。我遇到过一个问题:列表快速滑动时,图片频繁进出活动缓存,导致GC频繁。后来发现是没用好skipMemoryCache(false),让高频复用的图片留在了活动缓存里。
内存缓存:采用LRU算法,默认大小为应用最大堆内存的1/8。你可以通过setMemoryCacheSize()调整。我个人习惯在低端机上把缓存调小一点,避免内存压力。
磁盘缓存:默认250MB,位置在cache/glide目录。注意,Glide的磁盘缓存是根据图片的原始尺寸和变换参数来生成缓存key的。也就是说,同一张图,你加载100x100和200x200,会缓存两份。
核心要点:Glide的缓存key由EngineKey生成,包含:model(URL)、signature、width、height、transformations等。所以,如果你对同一张图做了不同的变换,会生成不同的缓存。
6.1.2 生命周期管理:与Activity/Fragment绑定
Glide最让我欣赏的一点,就是它能自动感知生命周期。你不需要手动在onDestroy里取消请求,它会自动帮你做。
原理其实不复杂:Glide会创建一个RequestManagerFragment(或SupportRequestManagerFragment),挂到当前Activity/Fragment上。这个Fragment会监听生命周期回调,当宿主销毁时,自动取消所有请求并释放资源。
我曾经在项目里遇到过一个问题:在ViewPager中预加载图片,但页面切换后,之前的请求还在跑。后来发现是因为没有使用with(fragment),而是用了with(context)。记住:尽量用with(fragment)或with(view),别用with(context),否则生命周期绑定会失效。
6.1.3 请求管理:优先级、缩略图与预加载
Glide的请求管理非常灵活。我常用的几个技巧:
- 优先级:
.priority(Priority.HIGH),让关键图片优先加载。比如列表中的首屏图片设为HIGH,非首屏设为LOW。 - 缩略图:
.thumbnail(0.1f),先加载10%质量的缩略图,再加载原图。用户体验提升明显。 - 预加载:
Glide.with(this).load(url).preload(),提前把图片加载到缓存里。我一般在用户滑动到下一页之前,预加载下一页的图片。
// 缩略图示例
Glide.with(fragment)
.load(url)
.thumbnail(0.1f) // 先加载10%质量的缩略图
.into(imageView);
// 预加载示例
Glide.with(this)
.load(url)
.preload(); // 预加载到缓存,不显示
6.2 Fresco的Ashmem机制:为什么它能扛住超大图?
Fresco是Facebook开源的图片加载库。它的最大亮点,就是使用了Ashmem(Anonymous Shared Memory)来管理图片内存。
传统方式加载图片,Bitmap是存在Java堆里的。Java堆受GC管理,而且有内存碎片问题。加载一张大图,很容易触发GC,甚至OOM。
Fresco的做法是:把图片数据放到Ashmem中。Ashmem是Linux内核提供的一种共享内存机制,它不占用Java堆内存,也不受GC影响。图片解码后,数据直接存在Ashmem里,Java层只保留一个引用。
个人经验:我在一个图片社交应用里,用Fresco替换了原来的Glide。之前加载20张高清图就OOM,换成Fresco后,能同时加载50张以上。原因就是Ashmem绕过了Java堆的限制。
但Fresco也有缺点:它强制使用自定义的SimpleDraweeView,不能直接用ImageView。如果你项目里大量使用第三方库(比如PhotoView),集成起来会比较麻烦。
另外,Fresco的包体积比Glide大不少。如果你的应用对包体积敏感,需要权衡一下。
6.3 WebP与SVG格式选择:别再只用PNG了
图片格式的选择,直接影响加载速度和内存占用。我见过太多项目,所有图片都用PNG,结果包体积大、加载慢。
6.3.1 WebP:Google的亲儿子
WebP是Google推出的图片格式,支持有损和无损压缩。相比PNG,体积能减少25%-35%;相比JPEG,体积能减少15%-25%。
Android从4.0(API 14)开始原生支持WebP解码。但要注意:带透明通道的WebP,需要Android 4.2.1+(API 18)。
| 格式 | 压缩率 | 透明度 | 兼容性 | 推荐场景 |
|---|---|---|---|---|
| PNG | 低 | 支持 | 全版本 | 需要透明背景的小图标 |
| JPEG | 中 | 不支持 | 全版本 | 照片、复杂场景 |
| WebP | 高 | 支持(API 18+) | API 14+ | 大部分场景,推荐替代PNG/JPEG |
| SVG | 极高(矢量) | 支持 | 需库支持 | 图标、简单图形、自适应尺寸 |
我个人习惯:新项目全部用WebP。用cwebp命令行工具批量转换,或者用在线工具。注意,WebP的解码速度比PNG稍慢,但现代手机基本感觉不到差异。
6.3.2 SVG:矢量图的优势与陷阱
SVG是矢量图,放大不失真,文件体积极小。但Android原生不支持SVG,需要借助库(如AndroidSVG或Picasso的扩展)。
SVG最大的问题是:复杂图形的渲染开销很大。你想想看,一个简单的图标,SVG可能只有几百字节。但如果是一个复杂的插画,SVG可能有几千行路径,渲染时CPU/GPU开销反而比位图还大。
避坑指南:我曾经在一个启动页里用SVG加载了一个复杂的品牌Logo,结果低端机上渲染了200多毫秒,导致启动白屏。后来换成了WebP,问题解决。SVG只适合简单图形(图标、线条图),别用在复杂场景。
另外,SVG的解析库会增加包体积。如果你只是用几个图标,不如直接用WebP或者字体图标。
6.4 核心知识体系:一张图看懂
下面这张图,是我梳理的图片加载框架核心知识体系。你可以看到,Glide、Fresco、格式选择,这三者其实是互补的:
6.5 实战建议:怎么选?
说了这么多,到底怎么选?我给出一个简单的决策路径:
- 大部分应用:用Glide。生态好、文档全、坑少。配合WebP格式,性能足够。
- 图片密集型应用(如社交、电商):考虑Fresco。它的Ashmem机制在加载大量高清图时优势明显。
- 包体积敏感:用WebP替代所有PNG/JPEG。SVG只用于简单图标。
- 混合使用:Glide + Fresco?不推荐。两个框架的内存管理会打架。选一个就好。
个人习惯:我现在的新项目,默认用Glide + WebP。如果遇到超大图(比如用户上传的4K照片),单独用Fresco的DraweeView加载。这样既保证了通用性,又解决了极端场景。
嗯,图片加载这块,其实还有很多细节可以聊。比如Glide的RequestBuilder链式调用、Fresco的ImagePipeline配置、WebP的编码参数优化……这些咱们后面章节再展开。今天先把核心机制理清楚,你心里有个谱就行。
公众号:蓝海资料掘金营,微信deep3321