flutter嵌入原生组件_Alibaba.com Flutter 探索之路:Flutter 图片性能优化

5d20a438a34c8a4dfbc78c7f4d4073cb.png

Flutter 作为全新的技术栈自去年9月份在 Alibaba.com 国际站无线端订单评价模块中进行小范围尝试后,逐步支撑了在交易链路中的订单、购物车等多个场景,在开发体验、研发效率、及解决平台一致性等方面都有相对原生开发有较为显著的提升。但随之也引入了一些新的问题,如页面路由、页面堆栈、页面内存等等。

在上半年,Alibaba.com 无线端针对全球链路的图片加载性能做了诸多优化,例如图片压缩(坑位自适应、q值、webp等)、多级缓存、域名收敛、Http2 协议升级等,相比较 Weex 可以直接通过 NativeAPI 桥接的方案,甚至在 H5 页面中也可以通过接管容器中的网络请求,以此达到来加载优化后的图片来达到与 Native 侧优化效果一致的目的,而 Flutter 由于独立于平台之外的渲染机制而提供框架自身的图片加载 API,如何让 Flutter 侧图片加载与 Native 侧图片加载优化策略保持一致就是我们要解决的一个问题

一、初期方案

团队初期接入 Flutter 框架再对整个机制较为不熟悉的情况下,在框架自身提供的 NetworkImage 加载网络图片接口之上做了一层额外封装,主要对等了 Native 侧的图片坑位自适应及 webp 两方面优化,然而如果在 Flutter 侧将整个 Naitve 侧的所有图片优化策略都使用 Dart 语言从新实现一套成本难以想象,这也与我们引入 Flutter 框架提升开发效率相悖,同样也面临稳定性的问题。第二也是 Flutter 侧最为致命的一点,其只提供有内存级别的缓存而缺失磁盘缓存能力,而我们交易链路的图片缓存利用率其实是很高的,毕竟通常进入到下单流程产品几乎之前都应该被访问过,这将导致每次用户在 Flutter 模块加载的商品图片都会重新去请求一次。所以最高效的方式还是 Flutter 侧能像Weex 那样通过”桥接“的方式加载 Native 侧解码后的图像数据,这样既收拢所有的策略的入口,也便于后期图片优化的策略升级和维护。

// ImageCdnHelper 中我们对 图片进行了裁剪、webp 的处理
String url = ImageCdnHelper.convertUrl(widget.imageUrl??'', width.toInt(),height.toInt());
// Flutter 获取网络图片接口
ImageProvider image = NetworkImage(_url);
// 在初期为了解决在 Flutter 侧图片加载慢的问题,做了FadeIn 的动画效果来提升体验。
Widget build(BuildContext context) { return FadeInImage(key: key, height: height, width:width, image: image)}
二、持续优化

方案一:MethodChannel

MethodChannel 是 Flutter 框架自身提供的一种与 Native 侧通信的机制。Flutter 侧主动发起调用,最终 Native 数据会以 block 的方式回调给 Flutter 侧。那这样既想当然可以直接桥接图片库的下载能力,既在 Native 侧下载完图片数据后,将 Image 解码后的数据转化为在 dart 的 Uint8List 类型进行接收,而 Flutter 的图像 ImageProvider 的子类 MemoryImage 正好可以进行接收用于展示。

class MemoryImage extends ImageProvider<MemoryImage> {
/// Creates an object that decodes a [Uint8List] buffer as an image.
///
/// The arguments must not be null.
    const MemoryImage(this.bytes, { this.scale = 1.0 })
        : assert(bytes != null),assert(scale != null);
/// The bytes to decode into an image.
    final Uint8List bytes;
/// The scale to place in the [ImageInfo] object of the image.
    final double scale;

但这里同样会有一个问题,就是这种方式会导致内存过大,先不论我们从 Native 侧先用 GPU 解码后再从 CPU 拷贝到 Flutter 侧使用 GPU 进行渲染,经历了一个 GPU->CPU->GPU 的过程会导致内存的巨大消耗。连缓存都是两份,我们在 Native 侧加载成功后会有一份内存缓存,Flutter 侧如果使用 MemoryImage 接收的话自身也会进行内存缓存(所有的 ImageProvider 子类都均由父类实现缓存),这样同一份图片解码后的数据就会内存里存在两份。实际测试也确实是这样,当出现大量解码后的,内存会出现急剧的增长,最终导致 OOM。

d5497f0713b2cd09e7a453fd40b50bea.png

方案二:PlatformView

PlatformView是 Flutter 官方提供的一个可以嵌入 Android 和 iOS 平台原生 View 的小部件。这里我也曾尝试在 Flutter 侧嵌入原生 ImageView 的方式,但测试下来效果极其糟糕,连最基本的可用性都达不到,尤其在长列表中当图片达到五六张之后,会发生非常明显的掉帧卡顿现象。所以如果不是 Flutter 没有提供的视图组件,例如地图或者视频等等,最好不要使用 PlatformView 机制去渲染原生视图。

969fd6f3dbfbee94f50b31e08a67c2cf.png

本质上渲染原生视图采用的是外接纹理的一种方式,既 Native 测渲染完成后通知 Flutter 将视图直接展示出来,但同时额外还需要处理 Native 与 Flutter 间的手势等问,视图的层级也变得更为复杂。第二,PlatformView 的大小在 Flutter 侧是由其父容器控制的,在 Native (以iOS为例)这里 frame 无法传递过来,所以创建的原生 ImageView 的大小就需要通过 args 的方式进行传递,这里最主要创建正确的 ImageView 大小是要用来控制图片的 contentMode;

方案三:Texture Widget + PixelBuffer

这个方案也是在团队在做视频组件预研时想到的方案,Flutter 官方提供的插件中即通过外接纹理的方式,来在 Flutter 侧展示 Naitve 端播放器的每一帧。其大概原理既在 Native 侧起了一个 CADisplayLink 的定时器,与屏幕刷新频率保持一致,然后通过 AVPlayerItemVideoOutput 生成当前画面的 PixelBuffer ,通过 EventChannel 传递给 Flutter 侧的 Texture Widget 进行显示,这样就能在 Flutter 视图中进行图片的展示。既然视频可以这么做,那么图片其实也可以理解成是单帧的视频么,所以只要能解决 Image to PixelBuffer 的问题好了。相比 AVPlaye 直接提供与 PixelBuffer 的转换 API,而系统 UIImage 是没有这样的接口的,而需要开发者自己去实现。

所以在之前如果没有太过接触图像与视频开发的同学来讲,这里有非常坑的一点,虽然网上有非常多 Image 与 PixelBuffer 互相转换的代码,但是在这里直接贴过来多半无法正常展示,一个是图像可能根本无法渲染出来,另外一个可能图像展示出来后图片的色块存在偏差,如下图:476fa9ad8e2564562976bc55b1a338b2.png

这里这个坑我接近踩了两天。第一是创建的转换 options 里一定要设置 kCVPixelBufferIOSurfacePropertiesKey,这个属性能让生成的 PixelBuffer 在 CPU 和 GPU 中共享一份内存;第二是 Pixel format 一定要设置成 kCVPixelFormatType_32BGRA。Convert UIImage to CVImageBufferRef 核心代码如下:

1906e40904f91340a10bdf8fd36b6277.png

另外就是要解决图像的 contentMode 问题,Flutter 侧在接收到图像是用的 Textrue Widget,是没有 BoxFit 属性的,所以需要额外使用 FittedBox 进行包裹才能正确设置图像的展示方式。

三、最后

最后就是关于内存的问题,这里即使用目前第三种方案,额外毕竟还是有使用 CPU 去创建 Pixbuffer 的操作且每次屏幕刷新 Flutter 侧都要拉取一次 Pixbuffer, 重复的创建也没有必要,所以仍旧一定要对 PixelBuffer 进行有效缓存优化和内存的及时回收,否则同样会出现内存暴涨的问题。这里经过优化之后基本可达到与 Flutter 自身 API 同样的内存消耗及 CPU 达到可用的标准。


e045f5697a9e56d5fcf6d9464b0bc630.png

关注「阿里巴巴国际技术」

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值