Flutter 小技巧之 3.7 性能优化background isolate

news2024/9/21 0:33:24

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 = 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.waitawait 修改为 .then 去执行,如果这时候你再调用 spawncompute ,你就会发现 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 在后续的版本中还能给我们带来更多的惊喜。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/196584.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CODESYS开发教程9-文件读写(CAA File库)

今天继续我们的小白教程&#xff0c;老鸟就不要在这浪费时间了&#x1f60a;。 前面一期我们介绍了CODESYS的定时器及触发相关的功能块。这一期主要介绍CODESYS的CAA.File库中的目录和文件读写功能块&#xff0c;主要包括文件路径、名称、大小的获取以及文件的创建、打开、读、…

软测(概念) · 软件测试的基本概念 · 什么是需求 · 测试用例的概念 · 软件错误(bug)的概念

一、什么是软件测试软件测试和开发的区别测试和调试的区别一个优秀的软件测试人员具备的素质二、什么是需求从测试人员角度看待需求三、测试用例的概念四、软件错误&#xff08;bug&#xff09;的概念一、什么是软件测试 最常见的解释是&#xff1a;软件测试就是找 BUG&#x…

个人博客美化

总体参考&#xff1a; Butterfly 文档&#xff1a;https://butterfly.js.organzhiyu &#xff1a;https://anzhiy.cn张洪 Heo &#xff1a;https://blog.zhheo.comLeonus &#xff1a;https://blog.leonus.cn 注&#xff1a;博客所有美化大部分&#xff08;全部&#xff09;都参…

React项目实战之租房app项目(九)登录模块基础布局和功能实现

前言 目录前言一、房屋详情模块二、登录模块2.1 登录模块效果图2.2 基础布局2.3 调用接口实现登录2.4 实现表单验证功能2.4.1 formik介绍2.4.2 formik基本使用2.4.3 添加表单验证2.5 代码优化总结一、房屋详情模块 房屋详情模块主要是展示之前获取到的房源信息&#xff0c;由于…

为防护加码,飞凌嵌入式i.MX93系列开发板让通信安全又稳定

来源&#xff1a;飞凌嵌入式官网www.forlinx.com随着新基建的加快推进&#xff0c;智能制造迎来了更好的发展时机&#xff0c;嵌入式板卡等智能设备也在更多的应用场景中大放异彩。但随着现场的设备数量的剧增&#xff0c;环境中的各种干扰信号也随之增加&#xff0c;这就对设备…

windows下GitHub的SSH key配置

SSH Key 是一种方法来确定受信任的计算机&#xff0c;从而实现免密码登录。 Git是分布式的代码管理工具&#xff0c;远程的代码管理是基于SSH的&#xff0c;所以要使用远程的Git则需要SSH的配置。 下面的步骤将完成 生成SSH密钥 并 添加公共密钥到GitHub上的帐户 先设置GitHub…

Apifox接口测试工具详细解析

最近发现一款接口测试工具--apifox&#xff0c;我我们很难将它描述为一款接口管理工具 或 接口自测试工具。 官方给了一个简单的公式&#xff0c;更能说明apifox可以做什么。 Apifox Postman Swagger Mock JMeter Apifox的特点&#xff1a; 接口文档定义&#xff1a; Apif…

接口测试学习第二天

1、全局变量 概念&#xff1a;在postman全局生效的变量&#xff0c;全局唯一。设置&#xff1a; 代码设置&#xff1a;pm.globals.set("glb_age",100)//示例&#xff1a; pm.globals.set("glb_age",100) 获取&#xff1a; 代码获取&#xff1a;var 接收值…

Java的内部类详解(成员内部类、静态内部类、局部内部类、匿名内部类)

Java知识点总结&#xff1a;想看的可以从这里进入 目录2.2.4、 内部类1、成员内部类2、静态内部类3、局部内部类4、匿名内部类2.2.4、 内部类 一个类定义在另一个类内&#xff0c;那么这个类就是一个内部类&#xff0c;比如&#xff1a;在类A中定义一个类B&#xff0c;B就是内…

英特尔锐炫秒杀RTX 3060,XeSS现已支持超过35款游戏!

一款显卡的性能可以达到什么程度&#xff1f;除了架构、规格等硬件因素&#xff0c;驱动的优化程度同样至关重要。Intel携带Arc锐炫回归独立显卡市场&#xff0c;作为“后起之秀”&#xff0c;驱动的优劣更是关键中的关键。Intel也正是这么做的。2022年6月&#xff0c;Intel正式…

2023 NFT防骗指南:六大骗局,3招带你远离…

网上流传着一句&#xff1a;币圈一天&#xff0c;人间一年。在刚刚过去的农历新年&#xff0c;一直低迷的加密领域迎来了“短暂性复苏”&#xff0c;加密市场总市值重回万亿美元。 同时复苏的还有NFT市场&#xff0c;据欧科云链OKLink链上数据显示&#xff0c;2023年1月份的NFT…

计算机网络-http协议版本对比

概述 HTTP 是基于 TCP/IP 协议的一个应用层协议&#xff0c;是现代互联网的一个基础协议。规定了客户端与服务端之间的通信格式以及所占用的服务端口80(HTTPS是443)。 超文本传输协议&#xff08;Hyper Text Transfer Protocol&#xff0c;HTTP&#xff09;是一个简单的请求-响…

【Flutter】Flutter Developer 101 入门小册 专栏指引

你好&#xff0c;我是小雨青年&#xff0c;一名程序员。 在2023年&#xff0c;我决定做这个Flutter专栏&#xff0c;从基础到部署&#xff0c;一站式解决大家对于Fulltter的学习需求。 目前本专栏的大概目录为本文最后所示&#xff0c;后续随着内容的不断更新&#xff0c;会逐…

2023年“华数杯”国际大学生数学建模B题赛题发布

ICM 问题B&#xff1a;社会稳定早期预警研究 背景 人类和所有的动物一样&#xff0c;都有寻求利益和避免伤害的本能。人类成为创造之主 的关键在于&#xff0c;他们比其他动物更善于避免伤害。危机总是潜伏着未来。人类发展的 历史是一部不断尝试超越危机的历史 (严耀军&#x…

鸿蒙开发学习|基础环境和开发工具

系列文章目录 第一章 HarmonyOS是什么 第二章 基础环境和开发工具 文章目录系列文章目录前言一、DevEco Studio工具简介二、DevEco Studio搭建开发流程1.运行环境要求2.下载和安装DevEco Studio三、安装HarmonyOS开发插件总结前言 HUAWEI DevEco Studio是基于IntelliJ IDEA C…

三阶魔方七步还原法公式备忘录

魔方公式备忘 转动符号图解 魔方七步公式&#xff1a; 1.底面十字还原 2.底角还原 3.中间层还原 上棱到左棱 U’L’U’LUFUF’ 上棱到右棱 URUR’U’F’U’F 4.顶面十字 循环做FRUR’U’F’直到出现十字 5.顶面还原&#xff08;小鱼公式&#xff09; 左手 L’U’LU’L’U’2L …

2、Maven——IDEA与eclipse(MyEclipse)创建工程的区别、Maven创建基本java工程

目录 一、IDEA与eclipse创建工程的区别 二、IDEA创建多个工程 1、 创建空工程&#xff1a;Empty Project 2、创建Module 三、Maven创建基本java工程 1、pom.xml 2、依赖坐标的使用 3、远程Maven仓库 4、Maven项目框架 &#xff08;1&#xff09; main目录 &#xff…

MySQL之主从复制集群搭建

简述 这篇文章主要记录使用docker compose搭建MySQL主从复制集群搭建&#xff0c;方便后续进行本地测试开发。 这篇文章主要介绍一主一从的搭建过程。 主从架构&#xff0c;可以缓解MySQL的数据存储以及访问的压力。 一. 主从复制原理 原理图如下&#xff1a; 步骤&#xf…

AVL平衡树(Java实现)

概念 AVL树可以定义为高度平衡二叉搜索树&#xff0c;其中每个节点与平衡因子相关联&#xff0c;该平衡因子通过从其左子树的子树中减去其右子树的高度来计算。AVL树是由GM Adelson - Velsky和EM Landis于1962年发明的。为了纪念其发明者&#xff0c;这树结构被命名为AVL。 定…

Android集成Unity

前言 随着前两年元宇宙的提出&#xff0c;虚拟现实开始在各大平台大展身手。各个平台都开始搭上了元宇宙的列车&#xff0c;Unity作为虚拟引擎中的热门&#xff0c;渲染效果和开发效率极其出色&#xff1b;Android作为移动开发的巨头之一也搭上了元宇宙这趟列车。今天&#xf…