flutter中的多线程

2022-09-20 16:46:35 浏览数 (1)

今天我们来学习下flutter中的多线程Isolate的用法。

下面我们会通过如何解析JSON数据来学习isolate的使用,json解析在app中是非常常见的。如果json数据小,在main isolate解析是没有任何问题的,如果数据过大的时候,就会阻塞UI(表现为卡顿和丢帧),所以这时候就会用到Isolate。

这里有两个概念worker isolate和main isolate,相当于多线程了,但不是真正的多线,dart是单线程的。

在本文中,我们将学习通过两种方式解析 JSON,即isolate的两种使用方式:

  • 使用compute()函数
  • 通过spawning an isolate来完成并在完成后调用Isolate.exit()通知worker isolate。

首先我们先定一个需要解析的json格式:

代码语言:javascript复制
{
  "results": [
    {
      "title": "Flutter Tutorial: Stopwatch App with Custom UI and Animations",
      "url": "https://font.happystudy.ink/posts/1",
      "date": "Dec 6, 2021",
      "contentType": "video"
    },
    {
      "title": "Responsive layouts in Flutter: Split View and Drawer Navigation",
      "url": "https://font.happystudy.ink/posts/2",
      "date": "Nov 26, 2021",
      "contentType": "article"
    },
    {
      "title": "How to create a Flutter GridView with content-sized items",
      "url": "https://font.happystudy.ink/posts/3",
      "date": "Nov 24, 2021",
      "contentType": "article"
    }
  ]
}

这个例子中仅仅展示了3条数据,但实际中可能会有很多数据,假如超过了1M。我们先定义一个解析的类SearchResult.

代码语言:javascript复制
class SearchResult {
  SearchResult({
    required this.title,
    required this.url,
    required this.date,
  });
  final String title;
  final String url;
  final String date;

  factory SearchResult.fromJson(Map<String, dynamic> data) {
    return SearchResult(
      title: data['title'],
      url: data['url'],
      date: data['date'],
    );
  }
}

使用 isolates解析数据

我们先定义一个解析用的类,如下

代码语言:javascript复制
import 'dart:convert';

class SearchResultsParser {
  List<SearchResult> _decodeAndParseJson(String encodedJson) {
    final jsonData = jsonDecode(encodedJson);
    final resultsJson = jsonData['results'] as List<dynamic>;
    return resultsJson.map((json) => SearchResult.fromJson(json)).toList();
  }
}

**_decodeAndParseJson()**方法现在不是异步的,但是如果现在数据量很大时,这个解析方法将耗费很长时间。

那么我们现在如何让这个函数在后台运行,不阻塞我们的UI呢?

现在先用我们的第一种方法compute():

代码语言:javascript复制
import 'dart:convert';
import 'package:flutter/foundation.dart';

class SearchResultsParser {
  Future<List<SearchResult>> parseInBackground(String encodedJson) {
    // compute spawns an isolate, runs a callback on that isolate, and returns a Future with the result
    return compute(_decodeAndParseJson, encodedJson);
  }

  List<SearchResult> _decodeAndParseJson(String encodedJson) {
    final jsonData = jsonDecode(encodedJson);
    final resultsJson = jsonData['results'] as List<dynamic>;
    return resultsJson.map((json) => SearchResult.fromJson(json)).toList();
  }
}

我们使用了compute()方法,这样就将我们的解析方法放到了worker isolate, 现在再运行我们的程序,就不会出现卡顿的现象了!

compute是dart中为我们封装好的快速使用的方法。下面我们再试试另外一种更加灵活的使用方式。

使用Isolate.exit()快速实现

compute虽然使用简单,但有一些问题,Flutter 2.8以前compute耗时会长一些,所以compute会比实际解析耗时会长那么一点点。

我们现在学习如何自己使用 isolate API:

代码语言:javascript复制
class SearchResultsParser {
  // 1. pass the encoded json as a constructor argument
  SearchResultsParser(this.encodedJson);
  final String encodedJson;

  // 2. public method that does the parsing in the background
  Future<List<SearchResult>> parseInBackground() async {
    // create a port
    final p = ReceivePort();
    // spawn the isolate and wait for it to complete
    await Isolate.spawn(_decodeAndParseJson, p.sendPort);
    // get and return the result data
    return await p.first;
  }

  // 3. json parsing
  Future<void> _decodeAndParseJson(SendPort p) async {
    // decode and parse the json
    final jsonData = jsonDecode(encodedJson);
    final resultsJson = jsonData['results'] as List<dynamic>;
    final results = resultsJson.map((json) => SearchResult.fromJson(json)).toList();
    // return the result data via Isolate.exit()
    Isolate.exit(p, results);
  }
}

关键代码第2步和第3步,

  • 我们使用Isolate.spawn()显式地创建一个新的Isolate,注意是异步的,使用了await
  • _decodeAndParseJson()使用Isolate.exit() ,通知解析完成并返回了结果results
  • Isolate.spawn()只能接受一个参数,所以encodedJson只能通过构造函数来传递示例变量。

对比这两种方法,我们可以看出compute更加简便快捷。

什么情况下我们需要让我们的代码在background

我们可以通过以下方法来测试下:

  • 以profile模式在低配置的设备上运行
  • 调整数据的大小,看看我们的UI是否卡顿或者丢帧

这样做会耗费很多时间,一般来说如果解析json数据超过10KB,我们就需要使用background了,毕竟1行代码就可以搞定。

代码语言:javascript复制
compute(_decodeAndParseJson, encodedJson)

扩展:networking的代码需要 worker isolate吗

到目前为止,我们只是把json解析的代码放到了worker isolate,那么 networking的代码需要放入吗?

代码语言:javascript复制
import 'package:http/http.dart' as http;

class APIClient {
  Future<List<SearchResult>> downloadAndParseJson() async {
    // get the data from the network
    final response = await http
        .get(Uri.parse('https://codewithandrea.com/search/search.json'));
    if (response.statusCode == 200) {
      // on success, parse the JSON in the response body
      final parser = SearchResultsParser();
      return parser.parseInBackground(response.body);
    } else {
      // on failure, throw an exception
      throw Exception('Failed to load json');
    }
  }
}

其实,nerworkingfile IO已经是在一个**separate task runner.**当IO操作完成的时候,就会返回结果到main isolate。

也就是说我们能够安全的使用flutter中IO操作相关的API,dart已经都给我们封装好了。

结论

使用worker isolate, 我们使用compute就能快速实现,在flutter2.8(Dart 2.15)我们compute的速度已经得到优化,简单场景无需我们再自定义使用Isolate了。

相关概念介绍

如果你想学习更多关于Isolate的东西,推荐以下阅读:

  • Concurrency in Dart(https://dart.dev/guides/language/concurrency)

也有一些优秀的外国视频可以参考:

  • Async vs Isolates | Decoding Flutter https://youtu.be/5AxWC49ZMzs
  • Isolates and Event Loops - Flutter in Focus https://youtu.be/vl_AaCgudcY

关于 Flutter 2.8 and Dart 2.15 最新公告:

  • What’s New in Flutter 2.8 https://medium.com/flutter/whats-new-in-flutter-2-8-d085b763d181
  • Announcing Dart 2.15 https://medium.com/dartlang/dart-2-15-7e7a598e508a

StackOverflow 多线程的解释 how Dart manages IO operations under the hood:

What thread / isolate does flutter run IO operations on? https://stackoverflow.com/questions/56906744/what-thread-isolate-does-flutter-run-io-operations-on

-------------------------少年别走,交个朋友--------------------------

0 人点赞