yii.
← Writing
Flutter 技術 · · 3 min read

Isolates 在 Flutter 3.7 & Dart 2.19 的升級,你該知道一下!

這篇寫於 2023 年。工具和 API 變化很快,部分步驟可能已經不適用。

Isolate is a good friend

隔離在 Flutter 是個很重要的角色,讓我們可以藉由它處理一些繁重的任務,例如:文字解析、圖像處理或是大量的資料存取,適當的使用可以避免 Main isolate 的阻塞,讓 APP 在運行時更為順暢。

本篇告訴你在近期的 Flutter 與 Dart 改版後,Isolate成長了多少, 有哪些新 API 和功能,快速了解一下吧。


1. RootIsolateToken#

從 Flutter 3.7 開始,可以在其他 background isolates 使用任何套件(ex: SharedPreferences)以及 Platform Channel。

情境:A 啟動一個 background isolate,從 Firebase Cloud Store 下載 8k 圖像,將它壓縮到所需的導出大小,接著將圖像保存到手機相簿,最後在完成時發布本地通知

下載、壓縮圖像、發送通知都使用 isolate 在背景處理,可以確保不影響 main isolate,但同時又能透過套件執行一些任務

實作範例#

首先將 Root isolate 傳遞給 other isolates,讓 isolates 可以透過 Root isolate 進行初始化,代表兩者產生關聯,接著就可以存取套件了,例如:取得 flutter_secure_storage 資料、發送 flutter_local_notifications 本地通知

一開始我們創建 RootIsolateToken 以及 ReceivePort 實體,RootIsolateToken 要給 isolates 進行通訊的初始化,而 ReceivePort 為了接收 isolates 返回的結果。使用 Isolate.spawn 創建 isolate,給予執行函式和關鍵參數

void main() {
  // 將 RootIsolateToken 傳遞給 background isolate 使用
  RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
  ReceivePort receivePort = ReceivePort();

  Isolate.spawn(_isolateGetData, [rootIsolateToken, receivePort.sendPort]);
}

在 isolate 接收兩個參數,並執行 ensureInitialized()初始化RootIsolateToken,接著就能使用套件做一些處理,例如要從 FirebaseFirestore 取資料也行。最終再將訊息、數據經由 SendPort 回傳給 main isolate 使用,完成隔離的背景操作

void _isolateGetData(List<Object> args) async {
  final rootIsolateToken = args[0] as RootIsolateToken;
  final sendPort = args[1] as SendPort;
  
  // 使用 RootIsolateToken 註冊 background isolate
  BackgroundIsolateBinaryMessenger
  .ensureInitialized(rootIsolateToken);
  
  // 接下來就可以存取套件了
  SharedPreferences sharedPreferences =
  await SharedPreferences.getInstance();
  
  ...
  
  // 取得結果後再透過 SendPort 返回 Main isolate
  // sendPort.send(result);
}

BackgroundIsolateBinaryMessenger.ensureInitialized() 是最重要的環節,讓 isolate 可以跟 Root isolate 產生連結,綁定生命週期,在 APP 和 Flutter Engine 銷毀時可以關閉 Platform Channel,避免其他 isolates 嘗試跟 Flutter engine 溝通,造成不必要的消耗

2. Isolate.run()#

  • run() 為一個更簡單且強大的 Api,省略原本使用 isolate 的複雜代碼,可替代原有的 compute() 操作
  • 不需要 SendPort、ReceivePort,也不用維護 isolate 的創建、銷毀,甚至錯誤捕捉都已經處理好了,只需要一般情境的 try catch 即可
  • 除了可以處理非同步的函式外,也支援同步處理,回傳值一律都為 Future
  • 函式參數可以使用匿名函式,不需要特別使用全域和靜態
  • 最後使用 exit() 將在 background isolate 中處理的結果內存回傳給 main isolate (不需要因為複製而導致佔用記憶體),然後安全地銷毀 isolate
  • 適合情境:一次性的任務處理
void main() async {
  try {
    final jsonData = await Isolate.run(_readAndParseJson);
  } catch (error, stackTrace) {
    debugPrint("$error , $stackTrace");
  }
}

注意:如果應用是有支援 Native 平台跟 Web,會建議使用 compute(),在所有環境才能正常運作。如果確定只支援 Native 平台,就可使用 run() 高級 API

run method - Isolate class - dart:isolate library - Dart APIRuns computation in a new isolate and returns the result. int slowFib(int n) => n slowFib(40)); If computation is…api.dart.dev


Conclusion#

現在有了 Flutter 3.7 和 Dart 2.19,我們可以在提升 App 性能表現之外更精準的使用套件、存取原生平台的功能,趕快在專案上適當地使用它們吧!

Reference#


About#

Contribution#

如果覺得文章不錯的話可以贊助,讓我有更多動力和熱情分享學習紀錄和生活!請我喝一杯咖啡吧~

https://www.buymeacoffee.com/yiichenhi

希望有幫助到你/妳,歡迎追蹤我,方便瀏覽最新的文章~

本文原刊登於 Medium。