热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Flutter小技巧之3.7性能优化backgroundisolate

Flutter3.7的backgroundisolate绝对是一大惊喜,尽管它在releasenote里被一笔带过,但是某种程度上它可以说是3.7里最实


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 里引入了 RootIsolateTokenBackgroundIsolateBinaryMessenger 两个对象,当 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 &#61; SharedPreferences.getInstance()
.then((sharedPreferences) &#61;> sharedPreferences.setBool(&#39;isDebug&#39;, true));
/// 获取本地目录
final Future<Directory> tempDirFuture &#61; path_provider.getTemporaryDirectory();

/// 合并执行
var values &#61; await Future.wait([sharedPreferencesSet, tempDirFuture]);

final Directory? tempDir &#61; values[1] as Directory?;
final String dbPath &#61; path.join(tempDir!.path, &#39;database.db&#39;);
File file &#61; File(dbPath);
if (file.existsSync()) {
///读取文件
RandomAccessFile reader &#61; file.openSync();
List<int> buffer &#61; List.filled(256, 0);
while (reader.readIntoSync(buffer) &#61;&#61; 256) {
List<int> foo &#61; buffer.takeWhile((value) &#61;> value !&#61; 0).toList();
///读取结果
String string &#61; utf8.decode(foo);
print("######### $string");
}
reader.closeSync();
}
}


这里之所以可以在 isolate 里直接传递 RootIsolateToken &#xff0c;就是得益于前面所说的 Dart 2.15 的 isolate groups


其实入下代码所示&#xff0c;上面的实现换成 compute 也可以正常执行&#xff0c;当然&#xff0c;如果是 compute 的话&#xff0c;有一些比较特殊情况需要注意

RootIsolateToken rootIsolateToken &#61; RootIsolateToken.instance!;
compute(doFind, rootIsolateToken);

如下代码所示&#xff0c; doFind2 方法在 doFind 的基础上&#xff0c;将 Future.waitawait 修改为 .then 去执行&#xff0c;如果这时候你再调用 spawncompute &#xff0c;你就会发现 spawn 下代码依然可以正常执行&#xff0c;但是 compute 却不再正常执行

onTap: () {
RootIsolateToken rootIsolateToken &#61;
RootIsolateToken.instance!;
compute(doFind2, rootIsolateToken);
},
onTap: () {
RootIsolateToken rootIsolateToken &#61;
RootIsolateToken.instance!;
Isolate.spawn(doFind2, rootIsolateToken);
},
doFind2(rootIsolateToken) async {
/// 注册 root isolaote
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
///获取 sharedPreferencesSet 的 isDebug 标识位
final Future<void> sharedPreferencesSet &#61; SharedPreferences.getInstance()
.then((sharedPreferences) &#61;> sharedPreferences.setBool(&#39;isDebug&#39;, true));
/// 获取本地目录
final Future<Directory> tempDirFuture &#61; path_provider.getTemporaryDirectory();
/ Change Here //
/// 合并执行
Future.wait([sharedPreferencesSet, tempDirFuture]).then((values) {
final Directory? tempDir &#61; values[1] as Directory?;
final String dbPath &#61; path.join(tempDir!.path, &#39;database.db&#39;);
///读取文件
File file &#61; File(dbPath);
if (file.existsSync()) {
RandomAccessFile reader &#61; file.openSync();
List<int> buffer &#61; List.filled(256, 0);
while (reader.readIntoSync(buffer) &#61;&#61; 256) {
List<int> foo &#61; buffer.takeWhile((value) &#61;> value !&#61; 0).toList();
String string &#61; utf8.decode(foo);
print("######### $string");
}
reader.closeSync();
}
}).catchError((e) {
print(e);
});
}

为什么会这样&#xff1f;compute 不就是 Flutter 针对 Isolate.spawn 的简易封装吗&#xff1f;



其实原因就在这个封装上&#xff0c;compute 现在不是直接执行 Isolate.spawn 代码&#xff0c;而是执行 Isolate.run &#xff0c;而 Isolate.run 针对 Isolate.spawn 做了一些特殊封装。


compute 内部会将执行对象封装成 _RemoteRunner 再交给 Isolate.spawn 执行&#xff0c;而 _RemoteRunner 在执行时&#xff0c;会在最后强制调用 Isolate.exit &#xff0c;这就会导致前面的 Future.wait 还没执行&#xff0c;而 Isolate 就退出了&#xff0c;从而导致代码无效的原因。

另外在 Flutter 3.7 上 &#xff0c;如果 background isolate 调用 Platform Channels 没有关联 root isolate&#xff0c;也能看到错误提示你初始化关联&#xff0c;所以这也是为什么我说它使用起来很简单的原因。

除此之外&#xff0c;最近刚好遇到有“机智”的小伙伴说 background isolate 无法正常调用&#xff0c;看了下代码是把 RootIsolateToken.instance!; 写到了 background isolate 执行的方法里。



你猜如果这样有效&#xff0c;为什么官方不直接把这个获取写死在 framewok&#xff1f;


其实这也是 isolates 经常引起歧义的原因&#xff0c;isolates 是隔离&#xff0c;内存不共享数据&#xff0c;所以 root isolate 里的 RootIsolateToken 在 background isolate 里直接获肯定是 null &#xff0c;所以这也是 isolate 使用时需要格外注意的一些小细节。



另外还有如 #36983 等问题&#xff0c;也推动了前面所说的 compute 相关的更改。


最后&#xff0c;如果需要一个完整 Demo 的话&#xff0c;可以参考官方的 background_isolate_channels &#xff0c;项目里主要通过 SimpleDatabase_SimpleDatabaseServer 的交互&#xff0c;来模拟展示 root isolate 和 background isolate 的调用实现。


最后

总的来说 background isolate 并不难理解&#xff0c;自从 2018 年在 issue #13937 被提出之后就饱受关注&#xff0c;甚至官方还建议过大家通过 ffi 另辟蹊径去实现&#xff0c;当时的 issue 也被搭上了 P5 的 Tag。



相信大家都知道 P5 意味着什么。


所以 background isolate 能在 Flutter 3.7 看到是相当难得的&#xff0c;当然这也离不开 Dart 的日益成熟的支持&#xff0c;同时 background isolate 也给我们带来了更多的可能性&#xff0c;其中最直观就是性能优化上多了新的可能&#xff0c;代码写起来也变得更顺畅。

期待 Flutter 和 Dart 在后续的版本中还能给我们带来更多的惊喜。







推荐阅读
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
author-avatar
刘国彬2012_380
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有