作者:新宿

背景

去年,闲鱼技术团队新一代图片库 PowerImage 在通过一系列灰度、问题修复、代码调优后,已全量稳固利用于闲鱼。

绝对于上一代 IFImage,PowerImage 通过进一步的演进,适应了更多的业务场景与最新的 flutter 个性,解决了一系列痛点:比方,因为齐全摈弃了原生的 ImageCache,在与原生图片混用的场景下,会让一些低频的图片反而占用了缓存;比方,咱们在模拟器上无奈展现图片;比方咱们在相册中,须要在图片库之外再搭建图片通道。

详情可浏览:《Flutter 图片库高燃新退场》

PowerImage相干链接:

GitHub:(✅star)

https://github.com/alibaba/po...

Flutter pub:(✅like)

https://pub.dev/packages/powe...

简介

PowerImage 是一个充分利用 native 原生图片库能力、高扩展性的flutter图片库。咱们奇妙地将外接纹理与 ffi 计划组合,以更贴近原生的设计,解决了一系列业务痛点。

能力特点

  • 反对加载 ui.Image 能力。在基于外接纹理的计划中,应用方无奈拿到真正的 ui.Image 去应用,这导致图片库在这种非凡的应用场景下无能为力。
  • 反对图片预加载能力。正如原生precacheImage一样。这在某些对图片展现速度要求较高的场景下十分有用。
  • 新增纹理缓存,与原生图片库缓存买通!对立图片缓存,防止原生图片混用带来的内存问题。
  • 反对模拟器。在 flutter-1.23.0-18.1.pre之前的版本,模拟器无奈展现 Texture Widget。
  • 欠缺自定义图片类型通道。解决业务自定义图片获取诉求。
  • 欠缺的异样捕捉与收集。
  • 反对动图。(来自淘特的PR)

Flutter 原生计划

在介绍新计划开始之前,先简略回顾一下 flutter 原生图片计划。

原生 Image Widget 先通过 ImageProvider 失去 ImageStream,通过监听它的状态,进行各种状态的展现。比方frameBuilderloadingBuilder,最终在图片加载胜利后,会rebuild 出 RawImageRawImage 会通过 RenderImage 来绘制,整个绘制的外围是 ImageInfo 中的 ui.Image

  • Image:负责图片加载的各个状态的展现,如加载中、失败、加载胜利展现图片等。\
  • ImageProvider:负责 ImageStream 的获取,比方零碎内置的 NetworkImage、AssetImage 等。
  • ImageStream:图片资源加载的对象。

在梳理 flutter 原生图片计划之后,咱们发现是不是有机会在某个环节将 flutter 图片和 native 以原生的形式买通?

新一代计划

咱们奇妙地将 FFi 计划与外接纹理计划组合,解决了一系列业务痛点。

FFI

正如结尾说的那些问题,Texture 计划有些做不到的事件,这须要其余计划来互补,这其中外围须要的就是ui.Image。咱们把 native 内存地址、长度等信息传递给 flutter 侧,用于生成ui.Image。

首先 native 侧先获取必要的参数(以 iOS 为例):

dart 侧拿到后

咱们能够通过 ffi 拿到 native 内存,从而生成 ui.Image。这里有个问题,尽管通过 ffi 能间接获取 native 内存,然而因为 decodeImageFromPixels 会有内存拷贝,在拷贝解码后的图片数据时,内存峰值会更加重大。

这里有两个优化方向:

  1. 解码前的图片数据给 flutter,由 flutter 提供的解码器解码,从而削减内存拷贝峰值;
  2. 与 flutter 官网探讨,尝试从外部缩小这次内存拷贝。

FFI 这种形式适宜轻度应用、非凡场景应用,反对这种形式能够解决无奈获取 ui.Image 的问题,也能够在模拟器上展现图片(flutter <= 1.23.0-18.1.pre),并且图片缓存将齐全交给 ImageCache 治理。

Texture

Texture 计划与原生联合有一些难度,这里波及到没有 ui.Image 只有 textureId。这里有几个问题须要解决:

  1. 问题一:Image Widget 须要 ui.Image 去 build RawImage 从而绘制,这在本文后面的Flutter 原生计划介绍中也提到了;
  2. 问题二:ImageCache 依赖 ImageInfo 中 ui.Image 的宽高进行 cache 大小计算以及缓存前的校验;
  3. 问题三:native 侧 texture 生命周期治理。

别离都有解决方案:

问题一: 通过自定义 Image 解决,透出 imageBuilder 来让内部自定义图片 widget

问题二: 为 Texture 自定义 ui.Image,如下:

这样的话,TextureImage 实际上就是个壳,仅仅用来计算 cache 大小。实际上,ImageCache 计算大小,齐全没必要间接接触到 ui.Image,能够间接找 ImageInfo 取,这样的话就没有这个问题了。\

问题三: 对于 native 侧感知 flutter image 开释机会的问题。

批改的 ImageCache 开释如下(局部代码):

整体架构

咱们将两种解决方案十分优雅地联合在了一起:

咱们形象出了 PowerImageProvider ,对于 external(ffi)、texture,别离生产本人的 ImageInfo 即可。它将通过对 PowerImageLoader 的调用,提供对立的加载与开释能力。

蓝色实线的 ImageExt 即为自定义的 Image Widget,为 texture 形式透出了 imageBuilder。

蓝色虚线 ImageCacheExt 即为 ImageCache 的扩大,仅在 flutter < 2.2.0 版本才须要,它将提供 ImageCache 开释机会的回调。

这次,咱们也设计了超强的扩大能力。除了反对网络图、本地图、flutter 资源、native 资源外,咱们提供了自定义图片类型的通道,flutter 能够传递任何自定义的参数组合给 native,只有 native 注册对应类型 loader,比方「相册」这种场景,应用方能够自定义 imageType 为 album ,native 应用本人的逻辑进行加载图片。有了这个自定义通道,甚至图片滤镜都能够应用 PowerImage 进行展现刷新。

除了图片类型的扩大,渲染类型也可进行自定义。比方在下面 ffi 中说的,为了升高内存拷贝带来的峰值问题,应用方能够在 flutter 侧进行解码,当然这须要 native 图片库提供解码前的数据。

数据

FFI vs Texture

机型:iPhone 11 Pro;图片:300 张网络图;行为:在listView中手动滚动到底部再滚动到顶部;\
native Cache:20 maxMemoryCount; flutter Cache:30MB

flutter version 2.5.3; release 模式下

这里有两个景象:

FFI:   186MB稳定
Texture:194MB稳定

在 2.5.3 版本中,Texture 计划与 FFI,在内存水位上差别不大,内存稳定下面与 flutter 1.22 论断相同。

图中棋格图,为关上 checkerboardRasterCacheImages  后所展现,能够看出,ffi计划会缓存整个cell,而texture计划,只有cell中的文字被缓存,RasterCache 会使得 ffi 在晦涩度方面会有肯定劣势。

滚动流畅性剖析

设施: Android OnePlus 8t,CPU和GPU进行了锁频。\
case: GridView每行4张图片,300张图片,从上往下,再从下往上,滑动幅度从500,1000,1500,2000,2500,5轮滑动。反复20次。

形式: for i in {1..20}; do flutter drive --target=test_driver/app.dart --profile; done 跑数据,获取TimeLine数据并剖析。

论断:

  • UI thread 耗时 texture 形式最好,PowerImage 略好于 IFImage,FFI形式稳定比拟大。
  • Raster thread 耗时 PowerImage 好于 IFImage。Origin 原生形式好是因为对图片 resize了,其余形式加载的是原图。

更精简的代码

dart 侧代码有较大幅度的缩小,这归功于技术计划贴合 flutter 原生设计,咱们与原生图片共用较多代码。

FFI 计划补全了外接纹理的有余,遵循原生 Image 的设计规范,不仅让咱们享受到 ImageCache 带来的对立治理,也带来了更精简的代码。

单测

为了保障外围代码的稳定性,咱们有着较为欠缺的单测,行覆盖率靠近95%。

对于开源

咱们期待通过社区的力量让 PowerImage 更加欠缺与弱小,也心愿 PowerImage 能为大家在工程研发中带来收益。

Issues

对于 issue,咱们心愿大家在应用 PowerImage 遇到问题与诉求时,踊跃交换,提出 issue 时尽可能提供具体的信息,以缩小沟通老本。在提出 issue 前,请确保已浏览 readme。

对于 bug 的 issue,咱们自定义了模板(Bug report),能够不便地填一些必要的信息。其余类型则能够抉择 Open a blank issue。

咱们每周会花局部工夫对立解决 issues,也期待大家的探讨与 PR。

PR

为了放弃 PowerImage 外围性能的稳定性,咱们有着欠缺的单测,行覆盖率达到了 95%(power_image库)。

在提交PR时,请确保所提交的代码被单测笼罩到,并且波及到的单测代码请同时提交。

得益于 Github 的 Actions 能力,咱们在主分支 push 代码、对主分支进行 PR 操作时,都会触发 flutter test 工作,只有单测通过才可合入。

将来

开源是 PowerImage 的开始,而不是完结,PowerImage 可做的事件还有很多,乏味而丰盛。比方第一个 issue 中形容的 loadingBuilder 如何实现?比方 ffi 计划如何反对动图?再比方Kotlin和Swift···

PowerImage 将来将继续演进,在以后 texture 计划与 ffi 计划共存的状况下,随同着 flutter 自身的迭代,咱们将更偏向于向 ffi 倒退,正如在上文的比照中, ffi 计划能够人造享受 raster cache 所带来的晦涩度的劣势。

PowerImage 也会继续追寻 flutter 的脚步,以始终贴合原生的设计理念,不断进步,咱们心愿更多的同学退出进来,独特成长。

PowerImage相干链接:

GitHub:(✅star)

https://github.com/alibaba/po...

Flutter pub:(✅like)

https://pub.dev/packages/powe...

关注【阿里巴巴挪动技术】,阿里前沿挪动干货&实际给你思考!