Flutter 在全新 Platform 和 UI 線程合并后,出現了什么大坑和變化?
在兩個月前,我們就聊過 3.29 上《Platform 和 UI 線程合并》的具體原因和實現方式,而事實上 Platform 和 UI 線程合并,確實為后續原生語言和 Dart 的直接同步調用打了一個良好基礎,在《Flutter Roadmap 2025》 里官方也提到了:
直接從 Dart 調用 Objective C 和 Swift 代碼(適用于 iOS)以及 Java 和 Kotlin(適用于 Android),2025 這種同步調用方式也許可以在 Framework 和 Plugin 層面大規模引入。
沒有 MethodChannel
確實是好事,但是凡事皆有利弊,線程合并隨著也帶來了它的一些負面問題,首先最直觀的就是,在 Android 斷點開發時,斷點 Dart 代碼現在會導致 ANR 彈框:
其實這可以理解,因為現在 Dart 和平臺主線程綁定在一起了,斷點導致的無響應而出現 ANR 很合理,如果比較介意,而目前解決的辦法也很簡單,就是暫時關了線程合并,你可以選擇的 Debug 的 AndroidManifest 關閉線程合并:
<meta-dataandroid:name="io.flutter.embedding.android.DisableMergedPlatformUIThread"android:value="true" />
那如果說上面這只是小問題,那么下面這個可以說是比較關鍵的問題了。
在 #163064 里,因為線程合并之后,啟動引擎、應用和設置 Dart 代碼都運行的平臺線程上,會導致第一個可交互幀的時間變長,并且還看具體場景:
特別是,當平臺線程在 Android (例如 Android Activity 的布局)和 Dart 執行工作之間分配時,就可能會有更多的延遲。
這其實也是可預見的情況,在合并線程這個 feature 提出來時,就有人擔憂類似問題,而解決辦法也很“簡單”,那就是啟動的時候多加一個啟動線程:
所以,一個新功能修復了老 Bug,但是總會帶來好幾個新的 Bug ,所以兩 issue 生四翔,四翔生 Bug 掛。
目前 #166918 這個 PR 已經成功合并,該 PR 將原本的簡單 bool 合并線程啟用狀態修改為三種線程狀態,其中就有全新的 kMergeAfterLaunch
:
在 kMergeAfterLaunch
模式下,Engine 會在單獨的 Dart UI 線程上啟動引擎,然后在引擎初始化后會將 UI 任務移至平臺線程合并,從而改善應用啟動延遲的問題。
簡單說,就是啟動時還是走老的 Dart UI 線程,啟動完成之后再合并到一起。
在啟動之前,引擎通過設置讓 root isolate 關聯到原本的 UI Runner ,從而實現單獨的啟動線程:
而在啟動之后,Dart 的主線程就會移動到平臺線程,雖然說是“移動線程”,但是通過上面的代碼我們可以看到,實際上就是將兩個任務隊列 Merge
合并成一個,也就是原本分別在兩個任務隊列中排隊的任務,啟動成功后會被放入同一個隊列中,并由同一個線程來執行。
我們之前就講過,UI Runner 都是通過獨立的
MessageLoopTaskQueues
來處理任務,而MessageLoopTaskQueues
又是 Flutter Engine 內部用于管理任務隊列的類,它負責創建、維護和調度任務隊列。
因為 Flutter Engine 不會直接控制線程的創建和銷毀,而是通過控制任務隊列的調度來間接影響線程的行為,通過合并任務隊列,Engine 就可以讓多個線程執行的任務集中到一個線程上,從而達到合并線程的作用。
而對應的還有 Unmerge
操作,Unmerge
會將之前合并的任務隊列重新分離成兩個獨立的隊列,這樣在 Engine 需要關閉或者銷毀的時候,就可以將合并的線程恢復到原始狀態。
另外,目前在 kMergeAfterLaunch
模式下,禁止生成共享相同任務運行器的引擎,因為在線程合并后,生成新的引擎可能會導致死鎖:
所以可以看到,增加啟動線程的核心就是用原本的 Dart UI 線程進行啟動,然后啟動完成把任務隊列合并到平臺線程,回歸平臺線程的邏輯。
當然,說起來簡單,事實上這個修改在 Engine 涉及了 36 個文件,所以會不會改出什么新的 bug,暫時不好評價:
另外,目前 maoOS 的線程合并也已經完成,所以下個版本開始 macOS 上也是統一的平臺線程支持了,有了這個,似乎在 macOS 上使用 FFI 制作自己的圖形 API 也不是不可能:
最后,順帶一提,Flutter 官方正式啟動了 Widget 預覽的開發推進,只是從我的角度,總覺得這個沒太大必要,畢竟感覺就算出來了也不會很好用:
所以,你在 Flutter 3.29 上還有遇到過什么線程合并帶來的問題嗎?