Flutter 3.7 的 background isolate 绝对是一大惊喜,尽管它在 release note 里被一笔带过 ,可是某种程度上它能够说是 3.7 里最实用的存在:由于运用简略,提高又直观。
Background isolate YYDS
前言
咱们知道 Dart 里能够经过新建 isolate 来履行”真“异步使命,而本身咱们的 Dart 代码也是运转在一个独立的 isolate 里(简称 root isolate),而 isolate 之间不同享内存,只能经过音讯传递在 isolates 之间交换状况。
所以 Dart 里不像 Java 相同需要线程锁。
而在 Dart 2.15 里新增了 isolate groups 的概念,isolate groups 中的 isolate 同享程序里的各种内部数据结构,也便是尽管 isolate groups 仍是不允许 isolate 之间同享可变目标,但 groups 能够经过同享堆来完成结构同享,例如:
Dart 2.15 后能够将目标直接从一个 isolate 传递到另一 isolate,而在此之前只支持根底数据类型。
那么假如运用场景来到 Flutter Plugin ,在 Flutter 3.7 之前,咱们只能从 root isolate 去调用 Platform Channels ,假如你尝试从其他 isolate 去调用 Platform Channels ,就会收成这样的过错正告:
例如,在 Flutter 3.7 之前,Platform Channels 是和
_DefaultBinaryMessenger
这个大局目标进行通信,可是一但切换了 isolate ,它就会变为 null ,由于 isolate 之间不同享内存。
而从 Flutter 3.7 开始,简略地说,Flutter 会经过新增的 BinaryMessenger 来完成非 root isolate 也能够和 Platform Channels 直接通信,例如:
咱们能够在全新的 isolate 里,经过 Platform Channels 获取到渠道上的原始图片后,在这个独立的 isolate 进行一些数据处理,然后再把数据返回给 root isolate ,这样数据处理逻辑既能够完成跨渠道通用,又不会卡顿 root isolate 的运转。
Background isolate
现在 Flutter 在 Flutter 3.7 里引入了 RootIsolateToken
和 BackgroundIsolateBinaryMessenger
两个目标,当 background isolate 调用 Platform Channels 时, background isolate 需要和 root isolate 树立相关,所以在 API 运用上大约会是如下代码所示:
RootIsolateToken rootIsolateToken =
RootIsolateToken.instance!;
Isolate.spawn((rootIsolateToken) {
doFind2(rootIsolateToken);
}, rootIsolateToken);
doFind2(RootIsolateToken rootIsolateToken) {
// Register the background isolate with the root isolate.
BackgroundIsolateBinaryMessenger
.ensureInitialized(rootIsolateToken);
//......
}
经过 RootIsolateToken
的单例,咱们能够获取到当前 root isolate 的 Token ,然后在调用 Platform Channels 之前经过 ensureInitialized
将 background isolate 需要和 root isolate 树立相关。
大约便是 token 会被注册到
DartPluginRegistrant
里,然后BinaryMessenger
在_findBinaryMessenger
时会经过BackgroundIsolateBinaryMessenger.instance
发送到对应的listener
。
完好代码如下所示,逻辑也很简略,便是在 root isolate 里获取 RootIsolateToken
,然后在调用 Platform Channels 之前 ensureInitialized
相关 Token 。
InkWell(
onTap: () {
///获取 Token
RootIsolateToken rootIsolateToken =
RootIsolateToken.instance!;
Isolate.spawn(doFind, rootIsolateToken);
},
////////////////
doFind(rootIsolateToken) async {
/// 注册 root isolaote
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
///获取 sharedPreferencesSet 的 isDebug 标识位
final Future<void> sharedPreferencesSet = SharedPreferences.getInstance()
.then((sharedPreferences) => sharedPreferences.setBool('isDebug', true));
/// 获取本地目录
final Future<Directory> tempDirFuture = path_provider.getTemporaryDirectory();
/// 合并履行
var values = await Future.wait([sharedPreferencesSet, tempDirFuture]);
final Directory? tempDir = values[1] as Directory?;
final String dbPath = path.join(tempDir!.path, 'database.db');
File file = File(dbPath);
if (file.existsSync()) {
///读取文件
RandomAccessFile reader = file.openSync();
List<int> buffer = List.filled(256, 0);
while (reader.readIntoSync(buffer) == 256) {
List<int> foo = buffer.takeWhile((value) => value != 0).toList();
///读取成果
String string = utf8.decode(foo);
print("######### $string");
}
reader.closeSync();
}
}
这里之所以能够在 isolate 里直接传递
RootIsolateToken
,便是得益于前面所说的 Dart 2.15 的 isolate groups
其实入下代码所示,上面的完成换成 compute
也能够正常履行,当然,假如是 compute
的话,有一些比较特殊情况需要注意。
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
compute(doFind, rootIsolateToken);
如下代码所示, doFind2
方法在 doFind
的根底上,将 Future.wait
的 await
修改为 .then
去履行,假如这时候你再调用 spawn
和 compute
,你就会发现 spawn
下代码仍然能够正常履行,可是 compute
却不再正常履行。
onTap: () {
RootIsolateToken rootIsolateToken =
RootIsolateToken.instance!;
compute(doFind2, rootIsolateToken);
},
onTap: () {
RootIsolateToken rootIsolateToken =
RootIsolateToken.instance!;
Isolate.spawn(doFind2, rootIsolateToken);
},
doFind2(rootIsolateToken) async {
/// 注册 root isolaote
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
///获取 sharedPreferencesSet 的 isDebug 标识位
final Future<void> sharedPreferencesSet = SharedPreferences.getInstance()
.then((sharedPreferences) => sharedPreferences.setBool('isDebug', true));
/// 获取本地目录
final Future<Directory> tempDirFuture = path_provider.getTemporaryDirectory();
///////////////////// Change Here //////////////////
/// 合并履行
Future.wait([sharedPreferencesSet, tempDirFuture]).then((values) {
final Directory? tempDir = values[1] as Directory?;
final String dbPath = path.join(tempDir!.path, 'database.db');
///读取文件
File file = File(dbPath);
if (file.existsSync()) {
RandomAccessFile reader = file.openSync();
List<int> buffer = List.filled(256, 0);
while (reader.readIntoSync(buffer) == 256) {
List<int> foo = buffer.takeWhile((value) => value != 0).toList();
String string = utf8.decode(foo);
print("######### $string");
}
reader.closeSync();
}
}).catchError((e) {
print(e);
});
}
为什么会这样?compute
不便是 Flutter 针对 Isolate.spawn
的简易封装吗?
其实原因就在这个封装上,
compute
现在不是直接履行Isolate.spawn
代码,而是履行Isolate.run
,而Isolate.run
针对Isolate.spawn
做了一些特殊封装。
compute
内部会将履行目标封装成 _RemoteRunner
再交给 Isolate.spawn
履行,而 _RemoteRunner
在履行时,会在最终强制调用 Isolate.exit
,这就会导致前面的 Future.wait
还没履行,而 Isolate
就退出了,从而导致代码无效的原因。
另外在 Flutter 3.7 上 ,假如 background isolate 调用 Platform Channels 没有相关 root isolate,也能看到过错提示你初始化相关,所以这也是为什么我说它运用起来很简略的原因。
除此之外,最近刚好遇到有“机智”的小伙伴说 background isolate 无法正常调用,看了下代码是把 RootIsolateToken.instance!;
写到了 background isolate 履行的方法里。
你猜假如这样有效,为什么官方不直接把这个获取写死在 framewok?
其实这也是 isolates 常常引起歧义的原因,isolates 是隔离,内存不同享数据,所以 root isolate 里的 RootIsolateToken
在 background isolate 里直接获肯定是 null ,所以这也是 isolate 运用时需要分外注意的一些小细节。
另外还有如 #36983 等问题,也推动了前面所说的
compute
相关的更改。
最终,假如需要一个完好 Demo 的话,能够参阅官方的 background_isolate_channels ,项目里首要经过 SimpleDatabase
和 _SimpleDatabaseServer
的交互,来模拟展示 root isolate 和 background isolate 的调用完成。
最终
总的来说 background isolate 并不难理解,自从 2018 年在 issue #13937 被提出之后就饱尝重视,乃至官方还主张过咱们经过 ffi 另辟蹊径去完成,其时的 issue 也被搭上了 P5
的 Tag。
信任咱们都知道 P5 意味着什么。
所以 background isolate 能在 Flutter 3.7 看到是相当难得的,当然这也离不开 Dart 的日益老练的支持,一起 background isolate 也给咱们带来了更多的可能性,其中最直观便是功能优化上多了新的可能,代码写起来也变得更顺利。
期待 Flutter 和 Dart 在后续的版本中还能给咱们带来更多的惊喜。