技术解读
Sketch:把「一切皆 URI」做到底的图片加载库
项目背景与痛点
Android 图片加载这块地,过去十年基本是 Glide、Picasso、Fresco 三分天下。但那套生态有一个共同前提:图片最终服务于 View。当 Compose 成为一等公民、再往前一步跨到 Compose Multiplatform 时,这套前提开始松动。
裂缝出现在三个地方。其一,Glide 依赖注解处理器生成 API,kapt 或 KSP 带来的构建成本在模块化项目里并不便宜;其二,Compose 没有 View 的 attach/detach 生命周期,缓存与请求的挂载点得重新设计;其三,资源形态远比 http:// 与 file:// 复杂,asset、content、data URI、apk 内资源、视频帧、SVG 都是不同格式,很多库只覆盖其中一部分,剩下的让业务自己兜底。
Sketch 的解法很直接:把加载过程抽象成一条统一的数据管道——凡是能产出字节流的东西都是 URI,凡是能解析字节流的东西都是 Decoder。这个思路不新,但 panpf 把它执行得足够彻底,并且在 3.x 重写内核后,把它搬到了 Compose Multiplatform 上。
核心技术揭秘
Sketch 采用清晰的三层结构:用户层(sketch-view、sketch-compose)、核心层(sketch-core)、扩展层(sketch-http、sketch-okhttp、sketch-gif、sketch-svg、sketch-video)。核心层本身不做任何具体的事,它只是一个调度器,把活派发给一组可替换的接口实现:
- Fetcher:负责从某个 URI 形态拉取原始数据流
- Decoder:负责把数据流解析成可显示对象,并支持「只读元信息」的轻量探测
- Keyer:负责生成内存/磁盘缓存键
- Cache:内存缓存与磁盘缓存的具体策略
- Interceptor:请求链路上的横切逻辑
一次加载的完整数据流大致如下:
这里有几个值得注意的设计细节。
其一是「先探测、后解码」的两段式解码器。 Decoder 被要求能低成本地读出宽高、格式、Exif 方向,而不真正解码像素。这一步非常关键:缩略图采样率必须基于原始尺寸和目标尺寸计算,而 Exif 方向会交换宽高,如果不先读方向再算采样,竖拍照片的采样结果就会算错,直接导致内存翻倍。
其二是请求合并。 同一个 MemoryCacheKey 的并发请求不会各自跑一遍完整链路,而是共享同一个执行结果。这在列表快速滚动时能省掉大量重复的 IO 与解码,本质上是把「缓存」的概念从结果层延伸到了「进行中的任务」层。
其三是跨平台抽象。 Compose Multiplatform 上不存在 Android 的 Bitmap,iOS 侧对应的是 Skia Image,平台差异被收进 expect/actual 的解码器与缓存实现里。这也是 Sketch 相对其他库真正的技术门槛所在——它不是把 Android 库「移植」到 CMP,而是重新划了一遍平台边界。
功能亮点与差异
Sketch 的杀手锏是资源形态的覆盖面。http、https、file、content、asset、data:image/...、android.resource、apk 内路径,全部走同一套 ImageRequest,业务侧不必为每种来源写一套加载逻辑。相比之下,Coil 的 Fetcher 体系虽然也可扩展,但内置覆盖面要窄一些。
其次是被拆成可选依赖的格式支持:GIF(含播放控制与帧缓存)、SVG(矢量解码,不是栅格化兜底)、视频帧缩略图(MediaMetadataRetriever 路径)、Exif 自动校正。按需引入,不用的模块不增加包体。
第三是请求级别的细粒度配置。尺寸、缩放、占位图、错误图、内存缓存策略、磁盘缓存策略、变换、进度监听、生命周期绑定,都能在单次请求上覆盖全局默认值。这比 Glide 的全局配置加少量 RequestOption 更灵活,代价是 API 表面积明显更大。
第四是没有编译期代码生成。不需要 kapt、不需要 KSP,构建链干净,这对大型多模块工程是实打实的收益。
应用场景与落地建议
适合的场景很明确:多图长列表(RecyclerView 复用或 LazyColumn)、以缩略图和视频封面为主的媒体类应用、需要动态下发 SVG 图标的配置化界面,以及已经在用或准备用 Compose Multiplatform 的跨端项目。
生产环境有几处必须注意。GIF 是内存风险点,务必显式控制帧缓存与播放生命周期,避免在列表里同时播放多个动图。磁盘缓存目录要按业务规划配置,默认策略未必符合多账号或多环境隔离的需求。生命周期绑定别偷懒,SketchImageView 与 Compose 侧的自动绑定要确认生效,否则页面退出后请求仍在跑,回调打到已经销毁的目标上。大图务必走采样,不要指望库替你兜住原图解码的内存峰值。最后,变换(Transformation)的顺序会影响中间 Bitmap 的分配次数,链式变换越多,越应该考虑合并成一次。
综合评价
优点:资源形态覆盖是同类里最全的一档;组件抽象干净,Fetcher/Decoder/Keyer 边界清晰,可替换性强;Compose Multiplatform 支持在开源库里属于领先位置;无注解处理器,构建友好;中文文档完善,作者长期维护。
缺点:API 表面积大,ImageRequest 的属性多到需要反复查文档,新手的学习曲线比 Coil 陡不少;社区规模与第三方生态远不及 Glide,遇到冷门问题可参考的答案少;CMP 端部分能力受平台限制(视频缩略图这类强平台依赖的功能在非 Android 目标上无法对齐);GIF 与 SVG 需要额外依赖,包体优势要靠裁剪才能体现。
一句话总结:只做 Android 单端,Glide 或 Coil 完全够用;一旦涉及 Compose Multiplatform 加多种资源形态,Sketch 是目前少数能把这件事从头到尾讲通的库。它不适合追求「三行代码接完就走」的项目,但对愿意理解其管道模型的团队,回报是可控性和一致性。
Sketch Image Loader
评论