系統里自帶了一個計算器大家都知道,打開是這個樣子。
但當你點擊計算器左上角的三個橫杠圖標后會發現這個計算器功能非常強大。
不僅可以計算各種三角函數,甚至還能在直角坐標系繪制方程式圖。另外,還可以進行日期、長度、貨幣等很多計算,太全面了,相信很多朋友都沒有深入去用。
截圖也是我們平時使用非常頻繁的功能。一般大家可能會使用微信、釘釘等辦公軟件進行截圖。
其實Win10系統本身就內置了一個非常實用的截圖功能。當我們需要截圖的時候同時按下鍵盤的“Win+Shift+S”就可以截圖了,是不是非常方便。并且還支持不規則截圖、全屏截圖、矩形截圖、窗口截圖四種模式。
Win10在最下方的任務欄點鼠標右鍵,選擇“你顯示觸摸鍵盤按鈕”。
Win11在任務欄點右鍵打開“任務欄設置”,打開“觸摸鍵盤”后面的開關。
任務欄右側就會出現“打開或關閉系統圖標”的圖標。
點擊圖標打開“觸摸鍵盤”,然后點擊左上角的設置圖標,選擇“手寫”,就出現手寫板界面了。
系統自帶的手寫輸入功能不僅支持各種電腦手寫筆,而且還可以用鼠標操作。
這個手寫功能非常適合不會用拼音打字的老人,而另外一個非常實用的用法就是如果你遇到不認識的字也可以用這個功能進行文字輸入,解決了那些拼音你不知道的文字,但是你需要輸入的尷尬。
你有沒有過需要在兩個窗口之間頻繁復制粘貼的經歷。每復制一次粘貼一次,來回循環,這樣實在太麻煩了。如果你需要大量復制粘貼的時候,只要按下鍵盤的“Win+V”就會出現“剪貼板歷史記錄”。
以后如果我們需要大量重復的復制粘貼的時候可以進行批量復制,然后切換到需要粘貼的窗口按下“Win+V”依次粘貼,可以省不少事情。
Win10中我們只要點擊任務欄右側的時間,打開側邊日歷,點擊下面的“開始”后,在框中輸入需要提醒的內容,并設置好提醒時間,到了設定時間后系統就會彈出提醒了。
點擊更多詳細信息可以進行更多內容項目的設置。相當于代辦事項的功能。
在日常辦公當中,我們可能需要錄制電腦屏幕或者錄制一段帶聲音的PPT演講稿。相信大部分朋友都會去找一些第三方的錄屏軟件。
但好用的軟件一般都是收費的,其實我們可以嘗試使用Win10自帶了錄屏功能。
按下鍵盤的“Win+G”快捷鍵就可以進入錄屏界面了,然后點擊“捕獲”窗口里的“開始錄制”按鈕,就可以進行錄屏了,這樣既好用,也可以避免安裝第三方軟件。
這個功能不僅僅能錄制游戲,任何窗口,包括PPT都可以進行屏幕錄制,同時也支持錄制麥克風的聲音。
錄制PPT的時候需要先把ppt全屏播放再按“Win+G”快捷鍵進行錄制。Win10 系統自帶的錄屏功能,穩定性好于第三方錄屏軟件。
《Dutter 系列文章》將闡述釘釘基于 Flutter 構建的跨四端應用框架(代號 Dutter)的技術實踐與踩坑經驗,共分為上、下兩篇,上篇內容可點擊 Dutter | 釘釘 Flutter 跨四端方案設計與技術實踐,本文為下篇,感謝閱讀。
作者:劉太舉(駑良)
本文主要介紹一下釘釘 Flutter 業務灰度過程中,在桌面端遇到并處理過的幾個 FlutterEngine 層面的 Bug。具體包含:
下面來為大家分別介紹一下。
1.1 FlutterEngine 退出之后內存泄漏問題
1問題背景
Mac 端 FlutterViewController 在銷毀之后,其開辟的內存并未并實際釋放,會出現內存泄漏問題。此問題在 Flutter issue 中有一些討論,但一直未有明確定位。在釘釘 Mac 端 Flutter 業務灰度過程中也遇到此問題,如無法處理將直接影響 Dutter 在 Mac 端落地的可行性:
2定位分析
一句話原因:
Mac 端 FlutterEngine 實現中對 weak property 使用不合理導致。FlutterViewController 強持有 FlutterEngine,后者持有一個指向 FlutterViewController 的 weak property。FlutterViewController 在 dealloc 流程中嘗試釋放 FlutterEngine,但是此時 FlutterEngine 中持有的 weak property 已經無法正確訪問(nil),導致釋放流程未能正常執行,出現泄漏。
下面結合具體實現來為大家做一個簡單說明。
由于設計到 OC 和 C++ 對象生命周期管理問題, FlutterEngine 內部對象持有關系略微特殊一些,大致如下圖所示:
正常情況下,FlutterViewController 退出之后,會通過調用 FlutterEngine 的 setViewController 傳入 nil 的方式,來觸發 FlutterEngine shudown 動作。參考實現如下:
即正常情況下,FlutterViewController dealloc 之后應該觸發 369 行代碼運行,進而釋放 FlutterEngine 資源。但是實際運行情況缺不是這樣,在代碼運行到 359 行時,嘗試判斷 if (_viewController != controller) 時并未成立。通過上述代碼我們知道,controller 是外部傳入的對象此時為 nil;_viewController 作為一個 weak proptry,在 FlutterViewController 進入 dealloc 流程之后也變為 nil。因而在此流程下,我們希望中的 shutDownEngine 方法并未被調用。
3處理方案
問題定位之后處理方式就很簡單了,可以在 FlutterViewController dealloc 的時候手動觸發 FlutterEngine shutDownEngine 方法。并且通過在上層通過 OC 動態特性 hook 實現、或者直接修改重新編譯 FlutterEngine 都可以。
但此處修改一定要謹慎,注意完整還原 FlutterEngine 中的 shutdown 流程,否則可能導致我們遇到的第二個問題:死鎖。
1.2 FlutterEngine shutdown 階段死鎖問題
1問題背景
釘釘最初在處理上述「FlutterEngine 泄漏」問題時,采用了一種相對比較簡單的方案:在 FlutterViewController dealloc 方法中,手動調用 FlutterEngine 提供的 shutDownEngine 方法,手動觸發相關資源釋放。
通過此方案,FlutterViewController 退出之后內存確實出現了下降,但是在灰度時發現偶爾會有整個頁面卡死的情況。通過對出現問題的鏈路進行簡單分析以及配合暴力測試,我們在 debug 環境對問題做了還原。最終初確認 UI 線程與 Raster 線程出現死鎖,死鎖之后的線程狀態大致如下。
UI 線程狀態:
Raster 線程:
2定位分析
一句話原因:
釘釘側調用 FlutterEngine shutDownEngine 方法不合理導致。shutDownEngine 之前,必須先調用 FlutterView 的 shutdown 方法來停止渲染流程。待渲染流程正常停止之后,才可進入 FlutterEngine 資源釋放流程,否則即有可能出現上述死鎖問題。
因為此問題為釘釘調用不合理導致,具體異常原因不再深入分析,感興趣的同學可以根據上述線索自行查閱。
3處理方案
在上層補全 FlutterEngine 釋放流程,在調用 FlutterEngine shutDownEngine 之前首先調用 FlutterView shutdown 停止 Raster 線程。
1.3 低版本 macOS OpenGL 析構階段 Crash 問題
1問題背景
此問題還是接兩個問題,在處理完問題1和問題2之后,參考 FlutterEngine shutdown 流程,釘釘會在 FlutterViewController 析構之后做3件事情:
經過一系列處理之后,測試發現內存泄漏和死鎖問題基本得以根治。但是在內部灰度過程中發現低版本 macOS 上會出現 Crash,堆棧大致如下:
2定位分析
一句話原因:
與問題2類似,此問題也是因為釘釘處理泄漏問題而引入。其大致由兩方面因素迭代導致。一方面因為重置 FlutterOpenGLRenderer 綁定的 FlutterView,導致在 embedder 層創建的 OpenGL 對象被提前釋放;另外一方面因為低版本 macOS OpenGL 實現不完善析構流程中未能對關鍵鏈路做保護,進而導致異常。
下面對異常相關代碼做一下簡答分析,避免其他同學再遇到類似問題。
1、在 FlutterEngine setViewController 方法中,如果處于釋放流程,會調用 FlutterOpenGLRenderer setFlutterView 方法,并傳入 nil:
2、FlutterOpenGLRenderer setFlutterView 方法在入參為 nil 時,會釋放其內部維護的 NSOpenGLContext 對象:
3、FlutterEngine 底層實現會在 GrDirectContext 對象析構時執行 flush,如果此時 OpenGL 相關對象已經釋放,在低版本 macOS(10.11, 10.12)會出現 Crash:
3處理方案
由于出現問題的部分是由釘釘上層代碼觸發,處理相對比較簡單。最終我們在所有使用 OpenGL 渲染的 Mac 設備上(macOS 10.14 之前的版本)移除 FlutterView 置空動作。即最終 FlutterViewController 釋放階段只執行以下兩個動作:
2.1 Win7 設備渲染模塊「Crash + 殘影」問題
1問題背景
此問題背景略微有些復雜,如果細分來看的話,此問題應該可以拆分為兩個子問題。
第一個問題是,在部分 Win7 設備上(x86 + x64)出現 d3d11 導致的 Crash,堆棧大致如下:
由于遲遲無法定位導致此問題的具體原因、且 Flutter 官方表示他們對 Win7 設備的覆蓋度并不完善「參考」(https://github.com/flutter/flutter/issues/92650#issuecomment-961341821)。因此我們決定對 FlutterEngine 稍加定制,在 Win7 等陳舊設備上強制通過「軟解模式」來渲染 Flutter 頁面。
本以為通過此方式可以繞過此問題,但很不幸運的是此方案暴露了 FlutterEngine 里另外一個 Bug:通過「軟解模式」來渲染頁面時,FlutterViewController 關閉只有有一定概率會導致 Windows 桌面出現殘影。
2定位分析
一句話原因:
此問題主要是因為 FlutterEngine 內部 shutdown 流程中,未及時修改 FlutterWindowsEngine 指向 FlutterWindowsView 對象的指針,導致多線程場景下出現野指針;因為野指針導致raster 線程在 FlutterWindowsView 已經銷毀情況下仍向其輸出繪制幀,進而導致異常。
在定位時,我們通過增加輔助 log 的方式來加快問題定位過程。通過對關鍵節點補充日志,我們很快發現了可疑點:
上圖是出現問題之后關鍵節點輸出的日志。我們通過日志可以得到以下關鍵信息:
因為最后渲染所使用 Window 句柄為 nullptr,進而導致出現殘影問題。
補充說明:在調用 C++ 成員函數時,即使調用時 this 已經為野指針,但只要成員函數中并未訪問到 this 對象,則不會出現內存訪問異常(Crash)。
3處理方案
修改 FlutterEngine 內部實現,在 SoftwareRenderer 模式下 FlutterWindowsView 析構時,置空 FlutterWindowsEngine 指向其的指針(因 GPU 模式會有異常輸出,暫未修改):
通過此方式,可以保證在 FlutterWindowsView 銷毀之后 raster 線程中的任務不會再回調渲染接口:
2.2 FlutterPlugin 注冊階段野指針 Crash
1問題背景
在釘釘 Flutter 版本「+面板」業務 Windows 端一灰、二灰階段出現較多例 Crash,客戶端整體 Crash 率高達 x%:
通過簡單分析,還原 Crash 堆棧大致如下:
從堆棧可以達到兩個比較重要的信息:
2定位分析
一句話原因:
Flutter 為 Windows 平臺提供 wrapper 層代碼中,包含一個設計上為單例的對象 PluginRegistrarManager。PluginRegistrarManager 主要服務于 FlutterPlugin 注冊、設計上為一個單例,其內部通過 map 維持了一個 FlutterEngine 指針與 Registrar 的映射關系,保證 Registrar 與 FlutterEngine 生命周期保持一致。但是因為 wrapper 層的代碼在構建時被編入了 pulgin.dll,導致每一個 plugin.dll 中都包含一份 PluginRegistrarManager 實現副本,即「單例機制」失效。帶來的問題是 FlutterEngine 析構時無法正確清除 PluginRegistrarManager 中的綁定關系,導致其內部維護一個失效的指針地址,再次訪問時出現 Crash。
下面簡單介紹一下分析過程。通過暴力測試,我們可以復現問題:
根據上圖可以確認,出現 Crash 是因為 FlutterEngine 對象野指針導致。進一步定位插件注冊時 Engine 指針來源,最終可定位到 flutter::PluginRegistrarManager::GetInstance()->GetRegistrar() 方法中:
進一步分析 PluginRegistrarManager 中的實現,可知 GetRegistrar 內部需要 map + emplace 方法來維系 FlutterEngine 地址與 Registrar 關系:
其內部會通過 FlutterDesktopPluginRegistrarSetDestructionHandler 將方法注冊到底層 Engine 對象中,其會在 FlutterEngine 析構時被調用,進而解除綁定關系:
問題即出現在此流程中,如果 PluginRegistrarManager 并非真正的單例,且 FlutterEngine 只能維護一份有效的 OnRegistrarDestroyed 回調,那么在 FlutterEngine 析構時,有部分 PluginRegistrarManager 對象中保存的 FlutterEngine 地址不會被清除,再次使用時即會導致問題。
3處理方案
修改 FlutterEngine wrapper 層 PluginRegistrarManager 實現,優化「單例」實現方案。將單例生命周期周期管理下層到底層,wrapper 層僅負責提供相關服務。
具體可參考:
2.3 Flutter Window 可見性變化之后頁面白屏
1問題背景
在 Windows 端 Flutter 頁面中,如果將 Flutter Window:
會發現 Flutter 頁面內容無法正常展示,畫布上為空白一片。如果在白屏之后通過 setState 或者 拖拽窗口等方式觸發 Flutter 頁面刷新,則內容可被正常渲染。
2定位分析
此問題相對比較明確,Flutter Windows 端實現存在 bug,在 Window 可見性發生變化之后,應重新出發 flush 將最新視圖繪制到對應窗口,但是目前此流程并未實現,導致出現以上問題。
3處理方案
此問題已經提交issue,暫時釘釘側是通過上層補償的方式來繞過此此問題。我們在 Native Window 可視性變化之后,手動通知 Flutter 側刷新當前可見頁面,以此觸發重繪、規避問題。
以上即為釘釘 Flutter 落地過程中桌面端處理的幾大主要問題。從我們實際體驗來看,雖然在 Flutter v2.10 版本已經正式發布對 Windows 的支持。但僅從穩定性角度來看,Flutter 在 Mac 端的表現無疑要優于 WIndows。如果有其它團隊希望在使用 Flutter 在桌面單端做一下嘗試,我們優先推薦選擇 Mac 端,其無論是上手門檻還是性能穩定性表現,相比 Windows 端要更有優勢。
關注【阿里巴巴移動技術】,阿里前沿移動干貨&實踐給你思考!