Flutter难点问题之GPU后台Crash

发表于 2年以前  | 总阅读数:2201 次

背景介绍

众所周知,在众多跨平台方案中,Flutter的渲染一致性一直是它的一大亮点,可谓是真正的实现了像素级别的控制。这主要归功于Flutter的架构设计,它基于Skia来实现渲染,而后者则以OpenGLES、Metal或Vulkan作为后端,这在最大程度上保证了不同平台的渲染一致性。Flutter的这个架构设计非常先进,当然,同其他项目一样,Flutter也不可避免的存在一些bug。今天我想和大家聊的,就是一个Flutter在iOS后台时访问GPU导致Crash的问题。本文将先对GPU后台Crash发生的原因进行说明,再介绍官方对此问题的修复方案,最后分享闲鱼在此基础上如何在其他三个场景解决该问题。

闲鱼App在使用Flutter开发项目的过程中,发现了一个与Flutter相关的iOS Crash,这个Crash的具体堆栈如下:

根据堆栈中的_gpus_ReturnNotPermittedKillClient可知,App是因为在后台访问了GPU导致了Crash,或许有些同学不太明白,为什么App在后台访问GPU会导致Crash呢?这其实是和iOS系统的策略有关。iOS系统是禁止后台的App访问GPU的,主要是为了保证前台正在运行的App的性能体验。因为GPU在系统看来是非常宝贵且有限的资源,如果App退到后台之后还继续疯狂使用GPU的话,那么前台App的性能可能就无法得到保障了。那么就有同学问了,如果App并没有遵循这个规范,在退到后台之后,继续使用Metal或OpenGLES访问GPU,会发生什么事情呢?答案很简单,会直接Crash。

由于Flutter使用了Skia作为渲染引擎,而后者在iOS则以Metal或OpenGLES作为后端,因此免不了要和GPU打交道,而在LayerTree光栅化上屏或者图片解码上传纹理时,都会使用到GPU,因此如果没有做好相应的保护措施的话,App就有可能Crash。

官方的修复方案

Flutter应用日益增多,开发者们慢慢发现了这个问题,并向官方提了相应的Issue。陆陆续续有开发者向Flutter官方反馈GPU后台Crash的问题,这引起了官方的注意,官方决定跟踪和解决这个问题。那么这个问题该如何解决呢?解决这个问题的关键,就是在收到UIApplicationDidEnterBackgroundNotification这个通知后,不要再执行任何可能会访问到GPU的操作。但是这个通知是在主线程收到的,而真正去访问GPU的则是Raster线程或IO线程,那么该如何通知它们呢?为此,Google软件工程师Aaron Clarke(github名为gaaclarke)设计了一个新的同步机制: SyncSwitch。SyncSwitch简单来说就是可以在一个线程去设置一个类型为bool的value,另一个线程的代码分为两个分支,根据value的值来确定具体走哪个分支。我们先来看看SyncSwitch是如何设计与实现的,以下是SyncSwitch的构造函数和两个API:

当iOS的前后台状态发生改变时,可以通过SetSwitch来设置value来表示GPU是否可用。而逻辑需要根据iOS在前台或者在后台走不同分支时,则调用Execute方法来走对应的逻辑。以下是作为Execute方法参数的结构体Handlers的代码:

以下是上述方法的具体实现,我们可以看到逻辑比较简单,主要就是在 SetSwitchExecude时加锁,然后根据value值去调用true_handler或者false_handler

最终官方通过这个方案,成功修复了ImageDecoder::UploadRasterImage导致的GPU后台Crash,具体代码如下:

这是官方关于用于修复这个问题的PR: #13908 Made a way to turn off the OpenGL operations on the IO thread for backgrounded apps[1]

当然,这个过程也不是一帆风顺的,在这个过程中,也遇到了一些问题,但是gaaclarke都顺利解决了。

问题的进一步解决

闲鱼将Flutter引擎升级并将官方最新的修复Patch打上以后,发现依然存在GPU后台Crash,这说明GPU后台Crash的问题并没有完全解决,难道是官方的解决方案还存在什么疏漏吗?我仔细分析了闲鱼发生GPU后台Crash的堆栈后,确认问题一共分布在3个地方,MultipleFrameCodec、EncodeImage以及DrawToSurface,而之前大家反馈的ImageDecoder则并未出现。所以可以确定的是,官方的解决方案并没有问题,只是并没有覆盖全面。而闲鱼由于业务体量大,场景复杂,再加上大规模使用Flutter,所以这些问题都都被一一暴露了出来。既然问题原因已经确认,那么让我们来看下如何修复吧。

MultipleFrameCodec::getNextFrame场景的Crash

在闲鱼遇到的3个GPU后台Crash中,MultipleFrameCodec::getNextFrame引起的占比是最高的,因此我决定先从这个问题下手。我们先来看一下问题的堆栈信息,来分析一下Crash具体是如何发生的。

根据堆栈可知,在发生Crash时,Flutter调用了SkImage::MakeCrossContextFromPixmap来生成一个基于texture的SkImage,该方法与问题相关的逻辑如下:

我们看到,在生成SkImage之前,会先调用了GrGpu::prepareTextureForCrossContextUsage来获取一个GrSemaphore,那么这个方法具体是什么用的呢,我们先来看看官方的文档注释:

根据文档注释可以看到,这个方法主要是为了让保障texture能够在多个context下安全使用。根据具体的后端实现,这个方法可能会返回一个GrSemaphore用于同步。接下来看看使用OpenGLES的情况下这个方法是如何实现的吧。

我们注意到,这个方法会创建一个GrGLSync,并且会调用一次flush来确保GrGLSync对象已经创建并且发送到了gpu。这个flush方法会去调用OpenGLES的APIglFlush,如果此时应用正处于后台,那么调用glFlush会导致应用直接崩溃。

上面我们分析了OpenGLES的实现,那么在Metal下是否也存在GPU后台Crash呢?答案是肯定的,Metal也有这方面的限制,我们在flutter issue里找到了一个与上面相似的堆栈。

既然已经找到问题的原因了,那么我们来看看如何修复吧。先来看一下MultipleFrameCodec::getNextFrame方法与之相关的逻辑,逻辑还是比较清晰的,如果有resourceContext,则使用SkImage::MakeCrossContextFromPixmap来生成SkImage,否则则使用SkImage::MakeFromBitmap来生成。

那么该如何修复这个问题呢,相信细心的读者可能已经想到了解决方法,可以使用gpu_disable_sync_switch来确保只有在GPU可用时才会调用SkImage::MakeCrossContextFromPixmap生成SkImage,而如果GPU不可用,则回退到调用SkImage::MakeFromBitmap生成SkImage

有了这个方案后,那么只需要稍加修改代码,功能也就实现了。当然,为了确保功能正确以及后续不会因为其他改动而导致不可用,我们还需要写一个单元测试。最终的PR如下:

#28159 Prevent app from accessing the GPU in the background in MultiFrameCodec[2]

gaaclarke在review了这个PR之后给予了肯定,目前这个PR已经成功合入到了master。

EncodeImage场景的Crash

第二个发生Crash的场景是在EncodeImage的时候,具体堆栈如下

根据这个堆栈,我很快就定位到了场景,这是在image_encoding.cc中的EncodeImage方法未使用is_gpu_disabled_sync_switch导致的Crash,具体代码如下:

有了上一次的经验,我很快在这个基础上加上is_gpu_disabled_sync_switch的逻辑,这部分代码比较简单,就不贴了。定位问题和修改问题可以说都很顺利,但是如何去写单元测试则让我犯了难。我修改的ConvertToRasterUsingResourceContext是一个内部方法,写单元测试时访问不到,另外即使将这个方法暴露出来,我们也没有办法传入一个flutter::SyncSwitch来用于测试,原因是flutter::SyncSwitch内部并没有属性来判断它自己是否被访问过。由于写不出单元测试,所以我只好向flutter官方的同学求助。

gaaclarke非常热心地给了我一个方案,让我将ConvertToRasterUsingResourceContext放到头文件,并改成模板,这样单元测试里不用传入flutter::SyncSwitch,只需要传入另一个Mock的其它类型的SyncSwitch就行。

我尝试了这个他给的这个方案,觉得改动有点大,在当时的我看来,单元测试的作用是为了保证自己的功能不被意外回滚。而我觉得这个PR被回滚的概率很小,因此我想着是不是可以和官方同学商量一下,不用写测试。

官方同学给我的回复让我对单元测试有了新的认知。gaaclarke觉得一个不完美的测试也比没有测试要好,而zanderso则给出了另一个理由,所有能被cherry-pick到beta或stable分支的功能都需要有单元测试,如果一个功能没有单元测试,那么即使有需要,它也不可能被cherry-pick到beta或stable分支。

他们的回复让我更加明白了单元测试的重要性,但是我当时觉得gaaclarke给的方案改动有点大,所以想了一个新方案,使用FLUTTER_RELEASE这个宏来做条件编译,在非release模式下为SyncSwitch增加逻辑使得其可以知道它是否被调用过,这样可以尽量少改动具体实现来做单元测试。但是这个方案最终没有被gaaclarke采纳,他觉得条件编译使得维护变得复杂,并不是一个好方案。

所以最终我还是按照gaaclarke的建议实现了最终版本的单元测试,同时也向gaaclarke表达了我自己的担忧。这个方案将原本无需暴露的头文件都暴露到了image_encoding.h中,gaaclarke给了我一个建议,可以增加一个image_encoding_impl.h来解决这个问题,这的确是个好主意。

在经过多轮的尝试和探讨后,这个PR终于成功合入官方。

#28369 Prevent app from accessing the GPU in the background in EncodeImage[3]

整个过程和结果得到了gaaclarke的认可,他对此表示赞许以及感谢。

其实我觉得这个过程中,我从gaaclarke那边学到了非常多的东西,包括编码能力以及如何写好单元测试等等。

Rasterizer::DrawToSurface场景的Crash

这是闲鱼GPU后台Crash的最后一个场景,也是三个场景中最为棘手的一个,其堆栈如下:

从堆栈分析,问题非常清晰。我们需要确保Rasterizer::DrawToSurface方法不要在后台访问GPU。但是这个场景和之前场景却有着比较大的区别,之前的场景如果我们无法访问GPU,那么我们可以使用CPU来做兜底逻辑。但是在Rasterizer::DrawToSurface时无法访问GPU,那么应该怎么处理呢。

正在我还在苦恼如何来解决这个问题时,官方突然提了一个Issue:Crash in Metal from MTLReleaseAssertionFailure[4],我仔细看了一下堆栈,发现他们遇到的竟然和我遇到的是同一个问题!这个Issue的优先级是P2,还是非常紧急的,因为我决定尽我所能,和官方一起解决这个问题。

为了说清楚这个问题,我写了一段具体的分析过程[5],阐述了这个问题和之前遇到的GPU后台Crash是一类问题,所以我们需要在Rasterizer::DrawToSurface时,也使用is_gpu_disabled_sync_switch。那么如果当前无法访问GPU,该怎么做呢,我突然想到,DrawToSurface是为了让这一帧上屏,让用户能够看见。那么如果此时应用在后台,用户本来就看不见这一帧,那么我们为什么不直接将这一帧丢弃掉呢?这一帧丢掉会有问题吗,我仔细分析了一下,应该没有问题,因为当用户从后台回到前台时,Animator::Start会被调用,然后会调用RequestFrame去确保最新的一帧上屏。

为了能更快解决这个问题,我还提了一个PR,供官方作为解决问题的一个选择方案。gaaclarke在看了我的分析后,觉得有道理,不过他还是不太确定是不是应该在Rasterizer::DrawToSurface这么顶层的地方使用is_gpu_disabled_sync_switch。他觉得或许这个问题应该从Skia层解决更为合适。

而在经过一阵子调研后,gaaclarke决定采纳我的这个方案,最终经过几轮的讨论和改进,我和gaaclarke一起完成了这个PR,这个PR最终被合入了主干。

#28383 Started providing the GPU sync switch to Rasterizer.DrawToSurface()

总结

Flutter应用在后台访问GPU导致Crash的问题至此得到了圆满解决,相信不久的将来大家就能在Flutter release版本体验到。未来闲鱼团队会一如既往在Flutter上继续深耕,解决Flutter在落地过程中遇到的各种问题,给大家带来更好的用户体验。

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/KVux8rYCPXcvDODUskinBQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:6月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:6月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:6月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:6月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:6月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:6月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:6月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:6月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:6月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:6月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:6月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:6月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:6月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:6月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:6月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:6月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:6月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:6月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:6月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:6月以前  |  398次阅读  |  详细内容 »
 相关文章
如何有效定位Flutter内存问题? 3年以前  |  14692次阅读
Flutter的手势GestureDetector分析详解 4年以前  |  10800次阅读
Flutter插件详解及其发布插件 4年以前  |  10578次阅读
在Flutter中添加资源和图片 5年以前  |  7838次阅读
 目录