自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

前車之鑒:聊聊釘釘 Flutter 落地桌面端踩過(guò)的“坑”

開(kāi)發(fā) 前端 新聞
本文將闡述釘釘基于 Flutter 構(gòu)建的跨四端應(yīng)用框架(代號(hào) Dutter)的技術(shù)實(shí)踐與踩坑經(jīng)驗(yàn)。

本文主要介紹一下釘釘 Flutter 業(yè)務(wù)灰度過(guò)程中,在桌面端遇到并處理過(guò)的幾個(gè) FlutterEngine 層面的 Bug。具體包含:

  • Mac 端:
  • Windows 端:

下面來(lái)為大家分別介紹一下。

FlutterEngine Mac 端問(wèn)題

1.1 FlutterEngine 退出之后內(nèi)存泄漏問(wèn)題

1 問(wèn)題背景

Mac 端 FlutterViewController 在銷毀之后,其開(kāi)辟的內(nèi)存并未并實(shí)際釋放,會(huì)出現(xiàn)內(nèi)存泄漏問(wèn)題。此問(wèn)題在 Flutter issue 中有一些討論,但一直未有明確定位。在釘釘 Mac 端 Flutter 業(yè)務(wù)灰度過(guò)程中也遇到此問(wèn)題,如無(wú)法處理將直接影響 Dutter 在 Mac 端落地的可行性:

2 定位分析

一句話原因:

Mac 端 FlutterEngine 實(shí)現(xiàn)中對(duì) weak property 使用不合理導(dǎo)致。FlutterViewController 強(qiáng)持有 FlutterEngine,后者持有一個(gè)指向 FlutterViewController 的 weak property。FlutterViewController 在 dealloc 流程中嘗試釋放 FlutterEngine,但是此時(shí) FlutterEngine 中持有的 weak property 已經(jīng)無(wú)法正確訪問(wèn)(nil),導(dǎo)致釋放流程未能正常執(zhí)行,出現(xiàn)泄漏。

下面結(jié)合具體實(shí)現(xiàn)來(lái)為大家做一個(gè)簡(jiǎn)單說(shuō)明。

由于設(shè)計(jì)到 OC 和 C++ 對(duì)象生命周期管理問(wèn)題, FlutterEngine 內(nèi)部對(duì)象持有關(guān)系略微特殊一些,大致如下圖所示:

  • FlutterViewController 作為對(duì)外暴露的主要 Class,負(fù)責(zé)創(chuàng)建并持有 FlutterEngine 以及 FlutterView;
  • FluterEngine 在初始化階段會(huì)自己強(qiáng)持有自己,并在 shutdown 時(shí)自我 Release;
  • FlutterEngine 會(huì)創(chuàng)建并持有 FlutterRenderer,F(xiàn)lutterRenderer 會(huì)強(qiáng)持有 FlutterView;
  • FlutterEngine 間接強(qiáng)持有 FlutterView;
  • FlutterEngine 有一個(gè)指向 FlutterViewController 的弱引用指針。

正常情況下,F(xiàn)lutterViewController 退出之后,會(huì)通過(guò)調(diào)用 FlutterEngine 的 setViewController 傳入 nil 的方式,來(lái)觸發(fā) FlutterEngine shudown 動(dòng)作。參考實(shí)現(xiàn)如下:

即正常情況下,F(xiàn)lutterViewController dealloc 之后應(yīng)該觸發(fā) 369 行代碼運(yùn)行,進(jìn)而釋放 FlutterEngine 資源。但是實(shí)際運(yùn)行情況缺不是這樣,在代碼運(yùn)行到 359 行時(shí),嘗試判斷 if (_viewController != controller) 時(shí)并未成立。通過(guò)上述代碼我們知道,controller 是外部傳入的對(duì)象此時(shí)為 nil;_viewController 作為一個(gè) weak proptry,在 FlutterViewController 進(jìn)入 dealloc 流程之后也變?yōu)?nil。因而在此流程下,我們希望中的 shutDownEngine 方法并未被調(diào)用。

3 處理方案

問(wèn)題定位之后處理方式就很簡(jiǎn)單了,可以在 FlutterViewController dealloc 的時(shí)候手動(dòng)觸發(fā) FlutterEngine shutDownEngine 方法。并且通過(guò)在上層通過(guò) OC 動(dòng)態(tài)特性 hook 實(shí)現(xiàn)、或者直接修改重新編譯 FlutterEngine 都可以。

但此處修改一定要謹(jǐn)慎,注意完整還原 FlutterEngine 中的 shutdown 流程,否則可能導(dǎo)致我們遇到的第二個(gè)問(wèn)題:死鎖。

1.2 FlutterEngine shutdown 階段死鎖問(wèn)題

1 問(wèn)題背景

釘釘最初在處理上述「FlutterEngine 泄漏」問(wèn)題時(shí),采用了一種相對(duì)比較簡(jiǎn)單的方案:在 FlutterViewController dealloc 方法中,手動(dòng)調(diào)用 FlutterEngine 提供的 shutDownEngine 方法,手動(dòng)觸發(fā)相關(guān)資源釋放。

通過(guò)此方案,F(xiàn)lutterViewController 退出之后內(nèi)存確實(shí)出現(xiàn)了下降,但是在灰度時(shí)發(fā)現(xiàn)偶爾會(huì)有整個(gè)頁(yè)面卡死的情況。通過(guò)對(duì)出現(xiàn)問(wèn)題的鏈路進(jìn)行簡(jiǎn)單分析以及配合暴力測(cè)試,我們?cè)?debug 環(huán)境對(duì)問(wèn)題做了還原。最終初確認(rèn) UI 線程與 Raster 線程出現(xiàn)死鎖,死鎖之后的線程狀態(tài)大致如下。

UI 線程狀態(tài):

Raster 線程:

2 定位分析

一句話原因:

釘釘側(cè)調(diào)用 FlutterEngine shutDownEngine 方法不合理導(dǎo)致。shutDownEngine 之前,必須先調(diào)用 FlutterView 的 shutdown 方法來(lái)停止渲染流程。待渲染流程正常停止之后,才可進(jìn)入 FlutterEngine 資源釋放流程,否則即有可能出現(xiàn)上述死鎖問(wèn)題。

因?yàn)榇藛?wèn)題為釘釘調(diào)用不合理導(dǎo)致,具體異常原因不再深入分析,感興趣的同學(xué)可以根據(jù)上述線索自行查閱。

3 處理方案

在上層補(bǔ)全 FlutterEngine 釋放流程,在調(diào)用 FlutterEngine shutDownEngine 之前首先調(diào)用 FlutterView shutdown 停止 Raster 線程。

1.3 低版本 macOS OpenGL 析構(gòu)階段 Crash 問(wèn)題

1 問(wèn)題背景

此問(wèn)題還是接兩個(gè)問(wèn)題,在處理完問(wèn)題1和問(wèn)題2之后,參考 FlutterEngine shutdown 流程,釘釘會(huì)在 FlutterViewController 析構(gòu)之后做3件事情:

  1. 將 FlutterRenderer 中綁定的 FlutterView 置為 nil;
  2. 調(diào)用 FlutterView shutdown 方法;
  3. 調(diào)用 FlutterEngine shutDownEngine 方法。

經(jīng)過(guò)一系列處理之后,測(cè)試發(fā)現(xiàn)內(nèi)存泄漏和死鎖問(wèn)題基本得以根治。但是在內(nèi)部灰度過(guò)程中發(fā)現(xiàn)低版本 macOS 上會(huì)出現(xiàn) Crash,堆棧大致如下:

2 定位分析

一句話原因:

與問(wèn)題2類似,此問(wèn)題也是因?yàn)獒斸斕幚硇孤﹩?wèn)題而引入。其大致由兩方面因素迭代導(dǎo)致。一方面因?yàn)橹刂?FlutterOpenGLRenderer 綁定的 FlutterView,導(dǎo)致在 embedder 層創(chuàng)建的 OpenGL 對(duì)象被提前釋放;另外一方面因?yàn)榈桶姹?macOS OpenGL 實(shí)現(xiàn)不完善析構(gòu)流程中未能對(duì)關(guān)鍵鏈路做保護(hù),進(jìn)而導(dǎo)致異常。

下面對(duì)異常相關(guān)代碼做一下簡(jiǎn)答分析,避免其他同學(xué)再遇到類似問(wèn)題。

  1. 在 FlutterEngine setViewController 方法中,如果處于釋放流程,會(huì)調(diào)用 FlutterOpenGLRenderer setFlutterView 方法,并傳入 nil:

  1. FlutterOpenGLRenderer setFlutterView 方法在入?yún)?nil 時(shí),會(huì)釋放其內(nèi)部維護(hù)的 NSOpenGLContext 對(duì)象:

  1. FlutterEngine 底層實(shí)現(xiàn)會(huì)在 GrDirectContext 對(duì)象析構(gòu)時(shí)執(zhí)行 flush,如果此時(shí) OpenGL 相關(guān)對(duì)象已經(jīng)釋放,在低版本 macOS(10.11, 10.12)會(huì)出現(xiàn) Crash:

3 處理方案

由于出現(xiàn)問(wèn)題的部分是由釘釘上層代碼觸發(fā),處理相對(duì)比較簡(jiǎn)單。最終我們?cè)谒惺褂?OpenGL 渲染的 Mac 設(shè)備上(macOS 10.14 之前的版本)移除 FlutterView 置空動(dòng)作。即最終 FlutterViewController 釋放階段只執(zhí)行以下兩個(gè)動(dòng)作:

  1. 調(diào)用 FlutterView shutdown 方法;
  2. 調(diào)用 FlutterEngine shutDownEngine 方法。

FlutterEngine Windows 端問(wèn)題

2.1 Win7 設(shè)備渲染模塊「Crash + 殘影」問(wèn)題

1 問(wèn)題背景

此問(wèn)題背景略微有些復(fù)雜,如果細(xì)分來(lái)看的話,此問(wèn)題應(yīng)該可以拆分為兩個(gè)子問(wèn)題。

第一個(gè)問(wèn)題是,在部分 Win7 設(shè)備上(x86 + x64)出現(xiàn) d3d11 導(dǎo)致的 Crash,堆棧大致如下:

由于遲遲無(wú)法定位導(dǎo)致此問(wèn)題的具體原因、且 Flutter 官方表示他們對(duì) Win7 設(shè)備的覆蓋度并不完善「參考」(https://github.com/flutter/flutter/issues/92650#issuecomment-961341821)。因此我們決定對(duì) FlutterEngine 稍加定制,在 Win7 等陳舊設(shè)備上強(qiáng)制通過(guò)「軟解模式」來(lái)渲染 Flutter 頁(yè)面。

本以為通過(guò)此方式可以繞過(guò)此問(wèn)題,但很不幸運(yùn)的是此方案暴露了 FlutterEngine 里另外一個(gè) Bug:通過(guò)「軟解模式」來(lái)渲染頁(yè)面時(shí),F(xiàn)lutterViewController 關(guān)閉只有有一定概率會(huì)導(dǎo)致 Windows 桌面出現(xiàn)殘影。

2 定位分析

一句話原因:

此問(wèn)題主要是因?yàn)?FlutterEngine 內(nèi)部 shutdown 流程中,未及時(shí)修改 FlutterWindowsEngine 指向 FlutterWindowsView 對(duì)象的指針,導(dǎo)致多線程場(chǎng)景下出現(xiàn)野指針;因?yàn)橐爸羔槍?dǎo)致raster 線程在 FlutterWindowsView 已經(jīng)銷毀情況下仍向其輸出繪制幀,進(jìn)而導(dǎo)致異常。

在定位時(shí),我們通過(guò)增加輔助 log 的方式來(lái)加快問(wèn)題定位過(guò)程。通過(guò)對(duì)關(guān)鍵節(jié)點(diǎn)補(bǔ)充日志,我們很快發(fā)現(xiàn)了可疑點(diǎn):

上圖是出現(xiàn)問(wèn)題之后關(guān)鍵節(jié)點(diǎn)輸出的日志。我們通過(guò)日志可以得到以下關(guān)鍵信息:

  1. OnBitmapSurfaceUpdated 是 FlutterWindowsView 的成員函數(shù)。但是在輸出最后兩行 OnBitmapSurfaceUpdated 方法時(shí),F(xiàn)lutterWindowsView 的析構(gòu)函數(shù)已被執(zhí)行(野指針);
  2. 最后一次執(zhí)行 OnBitmapSurfaceUpdated 時(shí),渲染使用的 Window 句柄為 nullptr,即可供渲染的窗口(與 FlutterWindowsView 綁定)以被釋放。

因?yàn)樽詈箐秩舅褂?Window 句柄為 nullptr,進(jìn)而導(dǎo)致出現(xiàn)殘影問(wèn)題。

補(bǔ)充說(shuō)明:在調(diào)用 C++ 成員函數(shù)時(shí),即使調(diào)用時(shí) this 已經(jīng)為野指針,但只要成員函數(shù)中并未訪問(wèn)到 this 對(duì)象,則不會(huì)出現(xiàn)內(nèi)存訪問(wèn)異常(Crash)。

3 處理方案

修改 FlutterEngine 內(nèi)部實(shí)現(xiàn),在 SoftwareRenderer 模式下 FlutterWindowsView 析構(gòu)時(shí),置空 FlutterWindowsEngine 指向其的指針(因 GPU 模式會(huì)有異常輸出,暫未修改):

通過(guò)此方式,可以保證在 FlutterWindowsView 銷毀之后 raster 線程中的任務(wù)不會(huì)再回調(diào)渲染接口:

2.2 FlutterPlugin 注冊(cè)階段野指針 Crash

1 問(wèn)題背景

在釘釘 Flutter 版本「+面板」業(yè)務(wù) Windows 端一灰、二灰階段出現(xiàn)較多例 Crash,客戶端整體 Crash 率高達(dá) x%:

通過(guò)簡(jiǎn)單分析,還原 Crash 堆棧大致如下:

從堆??梢赃_(dá)到兩個(gè)比較重要的信息:

  1. Crash 出現(xiàn)在 FlutterEngine 初始化階段,具體是在 Plugin 注冊(cè)時(shí)出現(xiàn)異常;
  2. 導(dǎo)致 Crash 原因是野指針問(wèn)題。

2 定位分析

一句話原因:

Flutter 為 Windows 平臺(tái)提供 wrapper 層代碼中,包含一個(gè)設(shè)計(jì)上為單例的對(duì)象 PluginRegistrarManager。PluginRegistrarManager 主要服務(wù)于 FlutterPlugin 注冊(cè)、設(shè)計(jì)上為一個(gè)單例,其內(nèi)部通過(guò) map 維持了一個(gè) FlutterEngine 指針與 Registrar 的映射關(guān)系,保證 Registrar 與 FlutterEngine 生命周期保持一致。但是因?yàn)?wrapper 層的代碼在構(gòu)建時(shí)被編入了 pulgin.dll,導(dǎo)致每一個(gè) plugin.dll 中都包含一份 PluginRegistrarManager 實(shí)現(xiàn)副本,即「單例機(jī)制」失效。帶來(lái)的問(wèn)題是 FlutterEngine 析構(gòu)時(shí)無(wú)法正確清除 PluginRegistrarManager 中的綁定關(guān)系,導(dǎo)致其內(nèi)部維護(hù)一個(gè)失效的指針地址,再次訪問(wèn)時(shí)出現(xiàn) Crash。

下面簡(jiǎn)單介紹一下分析過(guò)程。通過(guò)暴力測(cè)試,我們可以復(fù)現(xiàn)問(wèn)題:

根據(jù)上圖可以確認(rèn),出現(xiàn) Crash 是因?yàn)?FlutterEngine 對(duì)象野指針導(dǎo)致。進(jìn)一步定位插件注冊(cè)時(shí) Engine 指針來(lái)源,最終可定位到 flutter::PluginRegistrarManager::GetInstance()->GetRegistrar() 方法中:

進(jìn)一步分析 PluginRegistrarManager 中的實(shí)現(xiàn),可知 GetRegistrar 內(nèi)部需要 map + emplace 方法來(lái)維系 FlutterEngine 地址與 Registrar 關(guān)系:

其內(nèi)部會(huì)通過(guò) FlutterDesktopPluginRegistrarSetDestructionHandler 將方法注冊(cè)到底層 Engine 對(duì)象中,其會(huì)在 FlutterEngine 析構(gòu)時(shí)被調(diào)用,進(jìn)而解除綁定關(guān)系:

問(wèn)題即出現(xiàn)在此流程中, 如果 PluginRegistrarManager 并非真正的單例,且 FlutterEngine 只能維護(hù)一份有效的 OnRegistrarDestroyed 回調(diào) ,那么在 FlutterEngine 析構(gòu)時(shí),有部分 PluginRegistrarManager 對(duì)象中保存的 FlutterEngine 地址不會(huì)被清除,再次使用時(shí)即會(huì)導(dǎo)致問(wèn)題。

3 處理方案

修改 FlutterEngine wrapper 層 PluginRegistrarManager 實(shí)現(xiàn),優(yōu)化「單例」實(shí)現(xiàn)方案。將單例生命周期周期管理下層到底層,wrapper 層僅負(fù)責(zé)提供相關(guān)服務(wù)。

具體可參考:

2.3 Flutter Window 可見(jiàn)性變化之后頁(yè)面白屏

1 問(wèn)題背景

在 Windows 端 Flutter 頁(yè)面中,如果將 Flutter Window:

  • 先通過(guò) ShowWindow(flutter_wnd, SW_HIDE) 隱藏;
  • 再通過(guò) ShowWindow(flutter_wnd, SW_SHOWNORMAL) 顯示出來(lái)。

會(huì)發(fā)現(xiàn) Flutter 頁(yè)面內(nèi)容無(wú)法正常展示,畫(huà)布上為空白一片。如果在白屏之后通過(guò) setState 或者 拖拽窗口等方式觸發(fā)  Flutter 頁(yè)面刷新,則內(nèi)容可被正常渲染。

2 定位分析

此問(wèn)題相對(duì)比較明確,F(xiàn)lutter Windows 端實(shí)現(xiàn)存在 bug,在 Window 可見(jiàn)性發(fā)生變化之后,應(yīng)重新出發(fā) flush 將最新視圖繪制到對(duì)應(yīng)窗口,但是目前此流程并未實(shí)現(xiàn),導(dǎo)致出現(xiàn)以上問(wèn)題。

3 處理方案

此問(wèn)題已經(jīng)提交issue,暫時(shí)釘釘側(cè)是通過(guò)上層補(bǔ)償?shù)姆绞絹?lái)繞過(guò)此此問(wèn)題。我們?cè)?Native Window 可視性變化之后,手動(dòng)通知 Flutter 側(cè)刷新當(dāng)前可見(jiàn)頁(yè)面,以此觸發(fā)重繪、規(guī)避問(wèn)題。

總結(jié)

以上即為釘釘 Flutter 落地過(guò)程中桌面端處理的幾大主要問(wèn)題。從我們實(shí)際體驗(yàn)來(lái)看,雖然在 Flutter v2.10 版本已經(jīng)正式發(fā)布對(duì) Windows 的支持。但僅從穩(wěn)定性角度來(lái)看,F(xiàn)lutter 在 Mac 端的表現(xiàn)無(wú)疑要優(yōu)于 WIndows。如果有其它團(tuán)隊(duì)希望在使用 Flutter 在桌面單端做一下嘗試,我們優(yōu)先推薦選擇 Mac 端,其無(wú)論是上手門(mén)檻還是性能穩(wěn)定性表現(xiàn),相比 Windows 端要更有優(yōu)勢(shì)。

責(zé)任編輯:張燕妮 來(lái)源: 阿里巴巴移動(dòng)技術(shù)
相關(guān)推薦

2010-11-08 10:24:34

2017-12-03 13:00:23

CIO商業(yè)智能

2014-02-27 09:19:13

OpenStackCloud FoundSAP

2015-08-31 09:33:20

2020-10-08 18:12:36

數(shù)據(jù)科學(xué)職位面試數(shù)據(jù)科學(xué)家

2020-11-05 15:00:55

以太坊區(qū)塊鏈USDT

2022-12-06 08:00:16

awscli工具監(jiān)控

2024-04-01 08:05:27

Go開(kāi)發(fā)Java

2009-09-17 08:28:30

Windows 7兼容性

2020-02-17 15:17:57

釘釘

2017-07-17 15:46:20

Oracle并行機(jī)制

2021-05-29 14:14:16

阿里云釘釘低代碼開(kāi)發(fā)

2022-05-11 12:52:25

框架實(shí)踐應(yīng)用

2018-01-10 13:40:03

數(shù)據(jù)庫(kù)MySQL表設(shè)計(jì)

2020-06-10 14:01:46

阿里云釘釘Windows

2020-05-18 08:58:33

Python開(kāi)發(fā)工具

2016-09-06 18:20:43

存儲(chǔ)

2018-08-10 12:56:00

大數(shù)據(jù)

2023-08-22 20:48:06

模型釘釘阿里云

2021-06-02 08:47:03

Zabbix5.2釘釘機(jī)器人告警圖運(yùn)維
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)