当前位置: 首页 > 科技观察

如何让Flutter应用程序更好地使用SVG?

时间:2023-03-15 11:07:54 科技观察

SVG作为一种强大的矢量图形标准格式,在图像清晰度和表现力方面具有位图无法比拟的优势。那么SVG是绝对的首选吗?可能不会。本文将带你了解SVG在Flutter应用中的性能问题,分享UC浏览器核心技术团队在Flutter应用中改进SVG应用的探索实践。例如,历史上的计算机世界,很多空间优化隐藏了计算消耗。比如下面这张色彩和形状丰富的4K图片(其实也可以是8K,你在足够大的屏幕上就可以看到),压缩后大小只有5kB。如果这张5kB的图片存储在PNG中,就会像下图这样。表现力是天壤之别。为了达到类似的清晰度,一般的操作系统在应用打包的时候都会协助在UI资源中收集多种分辨率的图片。上面32x3264x64256x2561024x1024的图标,资源包占用120kB多,最大版本占用4MB运行内存。那么,SVG图像应该是绝对的首选?并不真地。在分析SVG对Flutter的支持之前,开发者可能会觉得没有提供各种移动系统API是一大缺憾。通过分析光栅化成本数据,我们也可以了解到系统对于盲目使用SVG带来的问题的担忧。比如上面的SVG图片,在骁龙626手机上,Flutter光栅化到64x64的区域需要34ms,一张SVG让应用完全错过了60帧的流畅度。实测iPhoneX耗时8ms,只能流畅显示两个。另外,SVG或者矢量图的应用需求是在UI扁平化趋势兴起之后才出现的。在拟物化时代,无论光栅化速度如何,矢量图形在显示逼真图标时的缺陷都是无法容忍的。比如doggy,在使用了更激进的tracking和vectorization之后(右图),已经数字感十足,存储占用也远超PNG。好在项目在推进的时候,扁平化的矢量图也在有意无意地规避了上面提到的问题,而且大部分都遵循简洁的风格。所以只要避开陷阱,SVG在很多场景下还是可以有很好的表现的。应用现状Flutter项目主线不支持Flutter基础组件。Skia代码中有一个SVG目录,但不要误会,Skia只有序列化为SVG的功能,并没有解码和绘制SVG的能力。框架开发计划目前没有支持的计划:https://github.com/flutter/flutter/issues/1831OS也无意支持,可以理解,因为Android、iOS等庞大的系统不支持默认情况下:https://stackoverflow.com/questions/34990236/how-to-use-svg-image-in-imageviewhttps://stackoverflow.com/questions/35691839/how-to-display-svg-image-using-swift每个人的共识是的,全功能的SVG支持需要大量工作,并且存在性能风险(都在拐角处提到)。SVG的锅,矢量字体的解决方法不用参考之前的SVG咨询。在建议的解决方案中,提到用矢量字体解决它。笔划字体:主流操作系统支持。基本上只有单色。无需依赖xml。由于是单色输出,排除了很多图层绘制叠加等不可控的影响性能的因素。该系统便于位图缓存管理(我们的开发者工具可以稍后研究)。尽管在SVG上投入了大量的研究,但不得不承认的是,字体矢量图形输出是目前非常务实高效的解决方案。配合工具流完善SVG应用SVG作为一种强大的矢量图形标准格式,仍然可以找到合适的应用。例如,彩色图标方便热更新,生产工具广泛支持这种格式。MakeSVGGreatAgain当OS和runtime都抛弃SVG的时候,flutter_svg包毅然扛起大旗,为Flutter提供了简单快速渲染和解码SVG的能力,显示出Flutter/Dart巨大的扩展潜力。flutter_svg的使用非常简单,提供了一个类似于flutter框架中image_provider的接口。下面两段代码分别显示来自asset和network的SVG图片:('https://raw.githubusercontent.com/dnfield/flutter_svg/master/example/assets/deborah_ufw/new-camera.svg',placeholderBuilder:(BuildContextcontext)=>Container(child:constCircularProgressIndicator()),),使用避免坑的工具不能坐视和忽视SVG的性能陷阱。UC浏览器内核技术团队开发了一个【资源面板】工具,可以方便的接入Flutter应用,实时显示资源分配的内存。对于里面的SVG图片,资源面板提供了预览和获取光栅化损失的功能。通过记录并对比SVG在实际移动设备上的光栅化损失,我们可以轻松识别出隐藏的SVG文件,合理安排SVG应用。通过实际RasterizationCost的对比可以看出,简约风格图标的耗时为16.66ms,在骁龙626上还是可以接受的。实现原理flutter_svgflutter_svg是一个dart包,提供从network,asset,memory等。由于解析结果不是ui.Image那样的位图,flutter_svg不配合ImageCache,而是自己实现了一套PictureCache。PictureCache中的缓存是ui.Picture。这个类其实就是skia引擎的SkPictureWrapper,二进制方法来记录具体的SVG绘制指令。ui.Picture类占用的内存不会很大,缓存基本是为了避免重复解析xml。比如SvgPicture.asset的构造接口如下:SvgPicture.asset(StringassetName,{Keykey,this.matchTextDirection=false,AssetBundlebundle,Stringpackage,this.width,this.height,this.fit=BoxFit.contain,this.alignment=Alignment.中心,this.allowDrawingOutsideViewBox=false,this.placeholderBuilder,Colorcolor,BlendModecolorBlendMode=BlendMode.srcIn,this.semanticsLabel,this.excludeFromSemantics=false,}):pictureProvider=ExactAssetPicture(allowDrawingOutsideViewBox==true?svgStringDecoderOutsideViewBox:svgStringDecoder,assetName,bundlebundle,package:package,colorFilter:_getColorFilter(color,colorBlendMode)),super(key:key);SvgPicture的_picture,由pictureProvider的流通知更新:void_resolveImage(){finalPictureStreamnewStream=widget.pictureProvider.resolve(createLocalPictureConfiguration(context));assert(newStream!=null);_updateSourceStream(newStream);}pictureProvider的流由来自pictureCache的完成者填充ui.Picture。//inPictureProvider.resolvestream.setCompleter(_cache.putIfAbsent(key,()=>load(key,onError:onError),),);在Debug和Profile模式下,通过添加合作代码,开发者工具可以查询所有存在的SvgPictures的PictureCache。光栅化时间获取接口是ui.Picutre.toImage方法,具体时间在光栅化线程中。补充AndroidVectorDrawableAndroid提供了一套VectorDrawable解决方案,是SVG的简化版。格式和特性不完全兼容,提供转换工具。从文档来看,过于复杂的SVG会影响性能确实值得关注。参考文档:https://developer.android.com/studio/write/vector-asset-studio单独的SVG位图缓存优化目前Flutter采用的是每帧一次性光栅化输出方式,chromium的cc是通过regionBitmaprecomposition构建的是不同的。如果光栅化输出时标记SVGPicture,缓存这部分位图可以增加帧数,但代价当然是内存损失。这个特性目前单纯用Dart实现并不方便,因为在dart.ui线程中,RenderPicture无法预见具体的光栅化分辨率。