介绍Cyborg, Android VectorDrawable的开源iOS实现

0
介绍Cyborg, Android VectorDrawable的开源iOS实现

创建高质量的移动应用程序需要工程师在多个平台上实现设计。而视觉习语,如标签栏,吐司,甚至一些图标可能是特定平台的,iOS和Android之间很少改变的一件事是插图,设计师经常利用它来产生视觉冲击,简化复杂的概念,或提供愉快的用户体验。

不幸的是,整合资产的管道很少是令人愉快的。由于iOS不支持矢量格式,设计师不得不为iOS创建多个图像资产,然后为Android导出一个VectorDrawable XML文件。

图1。使用位图意味着我们需要为我们的Uber司机应用在Android和iOS上创建同一个图像的多个版本。

带有夜间模式或其他替代主题的应用程序被迫将图像分解为单独的资产,这些资产可以单独着色或发布两张独立的图像。

图2。不同的图像处理,比如优步司机应用的夜间和白天版本,在iOS上需要更多的图像资产。

此外,工程师有时需要忍受繁琐和容易出错的过程编写代码手动绘制图像

在优步,工程师们在构建我们的软件时遇到了所有这些问题新的驱动程序,我们的Mobile Foundations团队决定通过提供一种在iOS上使用矢量资产的方法来解决设计师和工程师的这个痛点。我们需要一种解决方案,能够完全抽象出制作插图的问题,并将其扩展到多个平台、分辨率和本地化。

我们的Android工程师已经通过使用VectorDrawable格式。VectorDrawable是SVG XML模式的一个有效子集,支持路径、渐变、RTL、颜色的语义主题等等。VectorDrawable对我们设计师需要的功能的全面支持已经在我们的骑手、司机、货运和优步外卖应用程序的Android版本中得到了证明。

由于无法找到任何现有的解决方案来满足我们的需求,我们只好进行构建Cyborg,我们的iOS VectorDrawable实现。该库由一个VectorDrawable解析器和一个UIView子类组成,它支持在iOS上显示Android应用程序中使用的相同的VectorDrawable文件。

为什么要建一个新图书馆?

现有矢量图形实现全部实现的子集SVG规范这并没有充分的证据。许多解决方案都选择性能较差的技术,比如Core Graphics,它提供了实现规范中某些部分所必需的特性,而插图实际上并不需要这些特性。

图标字体是非常高性能的,不需要NSTextAttachment支持直接嵌入文本。但是,它们只适用于简单的单色图标。

我们已经有使用经验了洛蒂对于渲染动画,它支持开箱即用的主题,因此我们探索将其用于静态插图。但是,将它用于静态图像将要求我们构建新的导出工具,或者要求我们的设计人员从导出他们的设计FigmaAdobe After EffectsJSON。这个工作流程的非正式测试显示,在经历了这个过程后,在Figma中制作的许多矢量插图都有意想不到的外观,设计师被迫专门为这个工作流程重新创建资产。

优步推出Cyborg

在cyborg之前,Uber的图像工作流程非常传统:设计师提供了@2x和@3x的图像变体,用于图标和插图,Lottie用于渲染复杂的矢量动画。在我们的驱动程序中,我们第一次用图标字体广泛使用矢量图形。

图3。我们对Cyborg的第一次测试涉及用VectorDrawable视图替换图标字体。

我们对新生的Cyborg库进行了实验,将各种图标字体视图替换为VectorDrawable视图,并在视图显示在屏幕上时仔细记录性能指标,以确保不会出现倒退。在确保解析绘图和在屏幕上显示绘图都不会导致丢帧后,我们有足够的信心将VectorDrawables合并到我们的rider应用程序的核心流程中。

图4。在试用Cyborg时,我们使用该工具在一个基准应用程序中使用Uber的通用图标集渲染图标。

随着我们的最新设计,我们有机会将集合中的所有图标提供为VectorDrawables,并内置了对从右到左的语言(如阿拉伯语)的支持。我们也在考虑用VectorDrawables替换旧的图标字体,这是我们努力提高Cyborg的可扩展性和性能的唯一可能。

优化Cyborg

《Cyborg》的第一次迭代速度太慢,反序列化一个简单的图标需要4毫秒。在对182个驱动程序图标进行了广泛的基准测试和调整后,Cyborg的解析性能现在可以与用户界面图像:平均需要相同的时间来解析。它也比我们能找到的最快的SVG替代品要快。

在对下面列出的实现进行了三个关键更改之后,我们成功地将解析这些图标所需的时间从600多毫秒减少到200毫秒以下。

使用低级解析器

第一个关键的改变是避免Foundation的XML解析器.Foundation的XML解析器包装了CFXMLParser,它使用libxml2.我们选择直接使用libxml2。这使我们避免了从libxml2的内部数据结构转换为nsdictionary、nsarray和nsstring的开销,以及随后将这些数据结构包装在Swift对象中的额外开销。

创建自定义字符串

第二个也是最激进的优化是避开Swift自己的优化字符串数据结构,有利于更简单的替换。斯威夫特的字符串为用户提供了许多看似免费的功能,但这恰恰是我们的问题所在:我们不需要这些功能,而且它们的成本相当高。例如,没有必要确保只处理ASCII文本的解析器正确地处理Unicode的所有复杂性,如果简单地忽略这些复杂性,我们就可以节省大量时间。

结合起来自定义ASCII字符串实现通过使用libxml2,还可以避免不必要的内存分配。我们总是将这些不安全的字符串从XML转换为路径和颜色等数据结构,因此这段代码是安全的,前提是我们坚持不允许XML字符串“逃脱”并经过解析。这也允许我们利用现有的,由iOS提供的高度优化的例程来将C样式字符串转换为双精度对象,例如strtod.在写Cyborg的时候,Swift Strings使用UTF-16,所以得到一个UnsafePointer < UInt8 >提供给这些函数会导致String每次分配一个新的缓冲区,极大地降低了性能。

尽量减少不必要的字符串创建

最后的优化还涉及到最小化我们对Swift字符串的使用。Cyborg的路径解析器使用解析器组合子,它们比正则表达式更具可读性和可重用性。在解析器组合子中,解析器由多个原语构建,例如“一个或多个的”或“一个数字”。因为SVG路径规范在它认为有效的方面是相当自由的,所以对于整个解析器来说,不需要成功的解析器通常会失败。例如,SVG路径规范规定一条水平线用字母“h”表示,后面跟着任意数量的双精度值,前提是这个数字是2的倍数。这可以用解析器表示:

序列(文字(h),
zeroOrMore (whiteSpacesOrCommas (),
oneOrMore(sequence(whiteSpaceOrCommas(), double(), whiteSpaceOrCommas() double())))

即使在成功的解析中,该解析器的子解析器也将失败至少四次:每个空白或逗号解析器在遇到数字的开头时将失败,然后当路径数据结束或下一个命令开始时,空白解析器将失败最后一次。这取决于whiteSpaceOrCommas实现时,其自己的子解析器可能会失败数十次。

最初,当一个命令失败时,Cyborg会产生如下错误消息:

返回”。错误("没有在\(lastIndex)找到文本")")"

然而,迅速字符串S在一个紧密循环中分配和构造的代价可能很高,特别是当构造它们涉及字符串插值或追加时。通过切换到枚举表示,并且只构造字符串当向用户显示错误时,解析时间缩短了三分之一以上。

这些优化使Cyborg更适合我们的需求,而且它们应该可以扩展到除了最需要插图的应用程序之外的所有应用程序。

自己试试吧

Cyborg很容易集成到应用程序中。虽然它完全支持最流行的VectorDrawable功能,但仍然有还有更多工作要做,贡献是受欢迎的!

评论

没有帖子显示