AliFlutter图片解决方案与优化

简介: Flutter与Native混合开发将是接下来很长时间的主流开发方式。一套稳定、高效、与官方体系无缝融合的外接图片缓存方案是必不可少的。在AliFlutter系列第三场直播中,由阿里巴巴新零售淘系技术部无线开发专家王乾元为大家介绍AliFlutter提供的适合混合应用的外接图片库方案。首先对Flutter官方原生方案进行了分析,并提出了AliFlutter方案的切入点以及具体优化手段。

Flutter与Native混合开发将是接下来很长时间的主流开发方式。一套稳定、高效、与官方体系无缝融合的外接图片缓存方案是必不可少的。在AliFlutter系列第三场直播中,由阿里巴巴新零售淘系技术部无线开发专家王乾元为大家介绍AliFlutter提供的适合混合应用的外接图片库方案。首先对Flutter官方原生方案进行了分析,并提出了AliFlutter方案的切入点以及具体优化手段。

演讲嘉宾简介:王乾元,花名神漠,13年加入阿里,先后负责过天猫、支付宝、手机淘宝App的iOS架构工作。目前在AliFlutter团队负责基础组件、iOS架构,以及引擎、工具链等方面的研究。
以下内容根据演讲视频以及PPT整理而成。
观看回放http://mudu.tv/watch/5624777

本次分享主要围绕以下三个方面:

            一、Flutter如何显示、加载图片  
            二、AliFlutter图片解决方案优化  
            三、如何选择最适图片解决方案  

一、Flutter如何显示、加载图片

介绍Flutter如何加载、显示图片,以及在线程、缓存设计层面的特点。

线程

闲鱼分享的文章《深入理解Flutter引擎线程模式》中,详细讲解了Flutter引擎线程模型的原理以及作用。

Platform Thread:IOS与安卓平台层应用的主线程。进行Flutter Engine接口的调用,用户手势和输入等也通过Platform Thread输入给Flutter Engine。

UI Thread:Flutter的主线程,也称为Dart线程。同时可运行C++代码。

IO Thread:进行图片上传。图片在IO Thread进行异步上传生成GPU纹理.

GPU Thread:负责Flutter最终的GPU调用。
Worker Thread:Flutter中的fml会创建若干个并发工作线程。可进行图片解码等工作。

屏幕快照 2020-06-22 下午2.39.18.png

如上图所示,图片在Worker Thread完成解码,在IO Thread进行异步上传,在引擎启动时创建的ShellIOManager会创建OpenGL Context。同时GPU Thread创建GPU Context。IO Context与GPU Context将存放在Share Group中共享纹理。Flutter中纹理对象是C++的对象,在Flutter底层不会对纹理对象进行任何缓存,而是通过Dart层的ui.Image对象通过引用计数进行管理。

图片加载、显示流程

下图为Flutter从图片加载到显示的相关类关系图,包括类所在文件。

图片加载用到Flutter的Image Widget,一般是使用其“.network”接口加载网络图片。Image Widget进行显示绘制时需要ImageState。ImageState有两个功能,一是驱动Provider下载图片,二是调用State管理底层Render object。Render object负责图片的渲染上屏。

NetworkImage(Provider)在自身resolve方法中异步调用http下载图片。resolve方法调用Provider获取自己的ImageStream。ImageStream会添加到StreamCompleter作为Listener。StreamCompleter可以添加多个ImageStream作为Listeners。图片下载完成后通过Dart层和C++层的接口函数instantiateImageCodec创建底层C++解码器的C++对象。解码器对象获取图片流后在底层进行异步解码,并生成纹理。ImageState接收到事件后获取纹理对象绘制图片。上层获取图片纹理后会调用ImageState的SetState方法将纹理对象传给底层Render object,排版完成后图片就会绘制到屏幕。

底层纹理对象会被上层Dart对象引用,具体为以下几个对象。StreamCompleter负责驱动底层解码器获取纹理对象。因此StreamCompleter会持有底层GPU纹理,并通过Listeners通知所有ImageState。因此ImageState也会持有纹理对象。ImageState将图片传给底层Render object,因此Render object也会持有纹理对象。当上层Image Widget被销毁,Image Cache清空时,触发底层纹理的释放。

屏幕快照 2020-06-22 下午2.40.04.png

Flutter加载显示图片的流程包括了图片的组件、下载、解码、上传、绘制等工作,看似复杂,但是其逻辑较为简单。

二、AliFlutter图片解决方案优化

问题

首先,利用Flutter制作淘宝商品详情页面,图片多,内存、CPU等占用非常高,性能要求高。Flutter图片管理能力较弱,缺乏本地缓存能力,图片的重复下载极易造成内存飙高,易发生OOM(OutOfMemory)情况。因此Flutter原生方案无法满足需求,需要构建适合的AliFlutter方案。

第二,电商APP需要与Native图片库对接,共享缓存、CDN能力以及监控设施。

第三,在使用简单的基础上,AliFlutter需要基于Flutter的强大扩展能力,支持小程序、Canvas等多种场景。

第四,希望AliFlutter与官方Flutter体系尽可能兼容与融合。

屏幕快照 2020-06-22 下午2.40.40.png

AliFlutter图片解决方案总体架构

下图红色标签为AliFlutter方案的重点。在Dart层实现了新的Provider,在C++层实现了新的解码器对象,并基于Flutter规范提供了不同平台的ObjC、安卓的Java接口。

AliFlutter图片解决方案追求以下三个特点。

一致性:与官方体系无缝融合。仅在官方基础上添加代码。
高性能:优化CPU、内存占用,增强List回收能力。
易用性:适配简单、使用简单、易扩展。

屏幕快照 2020-06-22 下午2.41.30.png

AliFlutter:如下图所示,高亮部分为AliFlutter改进部分。
Image Widget添加了新类型的Provider,ExternalAdapterImage。新Provider接收的参数是URL、图片尺寸信息等。可将参数通过Adapter传给Native图片库,进行图片下载或从缓存中加载。Completer会创建新的解码器对象,通过Adapter对接Native图片库,让Native图片库提供图片的原始Buffer,并进行解码。即不依赖Flutter的图片解码能力,而是依赖平台层例如IOS和安卓原生的图片解码能力,可支持更多图片格式。将平台层解码后的bitmap返回给解码器对象,通过位图数据进行图片纹理的上传。AliFlutter解码器底层的C++对象支持这两种工作模式。

屏幕快照 2020-06-22 下午2.41.58.png

一次完整图片加载过程时序图:首先从Image Widget拿到图片请求URL,调用到底层解码器对象的getNextFrame方法会将请求异步上传给对接的Native图片库。由Native图片库做请求,获取平台层的图片对象或Buffer,将图片对象返回给解码器对象。解码器对象在Worker Thread中进行图片解码。图片解码完成后在IO Thread进行图片的GPU纹理上传。上传完成后在UI Thread将图片返回给Dart。上述流程完成一次图片加载,线程模型与Flutter原生保持一致。
图片取消:AliFlutter方案相比Flutter原生方案新增了Cancel能力。Widget通过State将自己添加到Completer的Listeners中。因此Widget销毁时会将自己从Listeners中移除。当Completer的Listeners全部清空时,表示这次图片请求已经不再需要了,调用底层解码器对象的cancel方法。如果图片还未从Native图片库返回,可以取消下载;如果已经返回,还有解码或上传GPU过程,都可以及时取消操作。Cancel能力可以避免许多无用的CPU和内存的消耗,尤其是电商App中常见的快速滑动商品列表的场景。

屏幕快照 2020-06-22 下午2.42.21.png

性能优化

AliFlutter进行了以下层面的优化,除图片取消外,还包括延迟加载、解码并发控制、GIF逐帧上传纹理、增强List回收能力等。

屏幕快照 2020-06-22 下午2.42.48.png

适配与使用:介绍AliFlutter图片方案最终对接到平台层Native图片库的接口。

Flutter的封装是在IOS平台公开了Objective-C接口,在安卓平台提供了Java接口,因此AliFlutter遵循Flutter规范提供了OC接口与Java接口。

IOS平台OC接口只需要实现一个回调。OC回调在对接图片库时接收的是URL以及一些参数,获取图片后向底层返回UIImage即可。使用时可以直接调用Dart的Image.externalAdapter方法加载一张图片。在此可以指定placeholderProvider,可以是AssetImage或其他网络图片,以此可在主图加载失败时加载一张副图。

屏幕快照 2020-06-22 下午2.43.22.png

增强List回收能力

优化前后对比:下图左侧所示为使用Flutter制作的淘宝商品详情页面,其中有多个Cell。其中一个Cell为宝贝详情。宝贝详情Cell最初的实现方式是解析一段HTML。商家有时会上传多张高清大图,若此时将连续的图文详情放在一个Cell中,用户浏览详情页时会同时加载多张大图。另外Flutter默认对所有Cell添加RepaintBoundary属性,该属性默认将Cell中所有内容绘制到一个纹理中,下次浏览时若Cell中内容不变,直接使用纹理绘制图片会比较快速。因此易导致内存飙高问题。

如下图所示,优化前内存容易暴增到600+MB甚至1G,几乎100%会出现OOM问题。在业务代码不进行修改的情况下,优化后的内存增长变得较为平缓。

屏幕快照 2020-06-22 下午2.43.46.png

Flutter List特点:Flutter List回收以Cell为单位。下图所示红色框部分为屏幕大小。默认情况下Flutter默认对所有Cell添加RepaintBoundary。当列表滚动时,若Cell 1绘制过,下次绘制时直接将及纹理上屏即可,无需绘制内部图文元素。而Cell 2会占用大量内存,首先其图文多,同时RepaintBoundary形成的纹理也会占用大量内存。

因此增强List回收能力首先需要解除对部分Cell的RepaintBoundary设置。

屏幕快照 2020-06-22 下午2.44.28.png

优化流程:假设一个Image Widget在一个Cell中,正常情况下当Cell出现,Image Widget也会被创建并且请求图片。List回收能力的优化中试图解除此约定,根据图片是否在屏来判断是否需要图片纹理。若不需要,则释放,若需要,进行请求。

Image Widget的宽、高已知情况下,其排版信息是有效的。SetState完成后触发底层Render Object排版与绘制。在绘制图片过程中添加一段逻辑判断图片是否在屏。若图片不在屏,不作任何处理。若图片在屏幕中,进行图片请求获取真实图片后重复调用SetState,重新进行图片排版和绘制,并判断是否在屏。若图片随着列表滚动不在屏幕中,则回调通知上层解除纹理引用。
若Image Widget的宽、高未知,Flutter只能在获取图片后根据其真实尺寸进行排版。原本底层解码器对象持有getNextFrame接口,该接口导致GPU纹理的生成。在优化后可以不依赖图片纹理上传完成再进行排版。在流程中添加了Request Size接口,Image Widget的宽、高未知时调用该接口可以预先通知底层C++解码器获取图片尺寸。得到图片尺寸后再从SetState开始流程,避免了无效的纹理上传。

屏幕快照 2020-06-22 下午2.45.07.png

关键代码:判断图片是否在屏是通过Dart层的Image Render Object。其paint方法中进行图片是否在屏的判断,根据其是否在屏向上层ImageState发送回调通知。
实现指定Cell不添加RepaintBoundary是通过建立虚类NoRepaintBoundaryHint。若List检测到上层某个Cell继承自NoRepaintBoundaryHint,则不给该Cell添加RepaintBoundary。因此可以在每次屏幕滚动时重新进行绘制,了解图片的在屏、离屏信息。

屏幕快照 2020-06-22 下午2.45.30.png

图片解码时通过Image Codec接口实现只获取图片尺寸,不上传纹理。图片尺寸可以直接从图片的头部信息获取,并不需要分配内存。

图片排版时可以仅根据图片的尺寸信息进行排版,无需获取真实图片。

总结起来,大Cell优化就是避免图片纹理上传,图片真正在屏时,再获取其纹理,当图片离屏时,立刻清除其纹理。

屏幕快照 2020-06-22 下午2.46.21.png

优化效果:经过以上优化,List回收能力的增强取得了较好效果。当商品详情页面有几十张大图在同一列表的同一个Cell中出现,可以做到仅加载在屏图片,若图片离屏则释放。

屏幕快照 2020-06-22 下午2.46.47.png

案例-优化前:十张图片放在一个Cell中,内存突增突降。

屏幕快照 2020-06-22 下午2.47.17.png

案例-优化后:根据图片是否在屏进行加载或释放,内存增降均较为平缓。

屏幕快照 2020-06-22 下午2.47.36.png

后续改进

AliFlutter图片解决方案还有以下方面可以改进。
功能改进:第一,图片在屏、离屏判断优化。第二,支持图片库返回图片原始文件,精简链路。第三,支持业务定制化缓存策略。

包优化大小:允许定制化裁剪Flutter中的若干图片解码库,同时保证Flutter所有功能正常。

与官方探讨:如何将AliFlutter优化融合到Flutter主干。

三、如何选择最适图片解决方案

Flutter图片解决方案诞生以来,开发者也进行了许多尝试,创建图片库方案。难以定论哪些图片方案更加优秀。
如下图所示,开发者可以考虑图片解决方案是否为纯Flutter应用,网络图片场景多不多,图片有无必要缓存等方面。根据自己的应用场景选择最适合自己的图片解决方案。

屏幕快照 2020-06-22 下午2.48.20.png

关注「淘系技术」微信公众号,一个有温度有内容的技术社区~
image.png

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
7月前
|
机器学习/深度学习 开发工具 计算机视觉
视觉智能平台常见问题之视频文件较大上传时可以分段上传或者切割视频如何解决
视觉智能平台是利用机器学习和图像处理技术,提供图像识别、视频分析等智能视觉服务的平台;本合集针对该平台在使用中遇到的常见问题进行了收集和解答,以帮助开发者和企业用户在整合和部署视觉智能解决方案时,能够更快地定位问题并找到有效的解决策略。
|
存储 前端开发 定位技术
前端加载超大图片实现秒开解决方案
前端加载超大图片实现秒开解决方案
|
5月前
|
测试技术 API
在线图片生成工具:定制化占位图片的利器
摘要:占位图片在网页设计中用于布局和样式展示,加载性能测试及响应式设计验证。在线图片生成工具(如[amd794.com/placeholder](https://amd794.com/placeholder))提供定制尺寸、样式和格式的功能,帮助开发人员高效创建占位图,提升开发效率和协作效果。该工具支持API调用,方便设置图片属性并生成或下载。
100 1
|
6月前
|
存储 网络协议 文件存储
技术心得:图片存储方案
技术心得:图片存储方案
81 0
在图片上停留时逐渐增强或减弱的透明效果demo效果示例(整理)
在图片上停留时逐渐增强或减弱的透明效果demo效果示例(整理)
|
Web App开发 缓存 JSON
可以用到项目的优化网站加载速度方案
可以用到项目的优化网站加载速度方案
80 0
|
7月前
|
编解码 搜索推荐 算法
网站图片优化技巧及最佳实践
网站图片优化技巧及最佳实践
181 1
图片压缩后,依然很大的解决方案
图片压缩后,依然很大的解决方案
117 0
|
JavaScript 前端开发 定位技术
高德地图「海量点标记 + 海量标注」卡顿问题 解决方案
高德地图「海量点标记 + 海量标注」卡顿问题 解决方案
969 1
|
测试技术 C# C++
C# 如何部分加载“超大”解决方案中的部分项目
在有的特有的项目环境下,团队会将所有的项目使用同一个解决方案进行管理。这种方式方面了管理,但是却会导致解决方案变得非常庞大,导致加载时间过长。那么,如何部分加载解决方案中的部分项目呢?
197 0
C# 如何部分加载“超大”解决方案中的部分项目