官方文档isolate应用-在后台处理 JSON 数据解析

436 阅读4分钟

Dart 应用通常只会在单线程中处理它们的工作。并且在大多数情况中,这种模式不但简化了代码而且速度也够快,基本不会出现像动画卡顿以及性能不足这种「不靠谱」的问题。

但是,当你需要进行一个非常复杂的计算时,例如解析一个巨大的 JSON 文档。如果这项工作耗时超过了 16 毫秒,那么你的用户就会感受到掉帧。

为了避免掉帧,像上面那样消耗性能的计算就应该放在后台处理。在 Android 平台上,这意味着你需要在不同的线程中进行调度工作。而在 Flutter 中,你可以使用一个单独的 Isolate。

使用步骤

添加 http 这个 package;

使用 http package 发起一个网络请求;

Make a network request using the http package.

将响应转换成一列照片;

将这个工作移交给一个单独的 isolate。

1. 添加 http 包

首先,在你的项目中添加 http 这个 package, http package 会让网络请求变的像从 JSON 端点获取数据一样简单。

dependencies:
  http: <latest_version>

2. 发起一个网络请求

在这个例子中,你将会使用 http.get() 方法通过 JSONPlaceholder REST API 获取到一个包含 5000 张图片对象的超大 JSON 文档。

Future<http.Response> fetchPhotos(http.Client client) async {
  return client.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
}

3. 解析并将 json 转换成一列图片

接下来,根据 获取网络数据 的说明,为了让接下来的数据处理更简单,你需要将 http.Response 转换成一列 Dart 对象。

创建一个 Photo 类

首先,创建一个包含图片数据的 Photo 类。还需要一个 fromJson 的工厂方法,使得通过 json 创建 Photo 变的更加方便。

class Photo {
  final int albumId;
  final int id;
  final String title;
  final String url;
  final String thumbnailUrl;

  Photo({
    required this.albumId,
    required this.id,
    required this.title,
    required this.url,
    required this.thumbnailUrl,
  });

  factory Photo.fromJson(Map<String, dynamic> json) {
    return Photo(
      albumId: json['albumId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      url: json['url'] as String,
      thumbnailUrl: json['thumbnailUrl'] as String,
    );
  }
}

将响应转换成一列图片

现在,为了让 fetchPhotos() 方法可以返回一个 Future<List>,我们需要以下两点更新:

创建一个可以将响应体转换成 List 的方法:parsePhotos()

在 fetchPhotos() 方法中使用 parsePhotos() 方法

// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();

  return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}

Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));

  // Use the compute function to run parsePhotos in a separate isolate.
  return parsePhotos(response.body);
}

4. 将这部分工作移交到单独的 isolate 中

如果你在一台很慢的手机上运行 fetchPhotos() 函数,你或许会注意到应用会有点卡顿,因为它需要解析并转换 json。显然这并不好,所以你要避免它。

那么我们究竟可以做什么呢?那就是通过 Flutter 提供的 compute() 方法将解析和转换的工作移交到一个后台 isolate 中。这个 compute() 函数可以在后台 isolate 中运行复杂的函数并返回结果。在这里,我们就需要将 parsePhotos() 方法放入后台。

Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client
  .get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));

  // Use the compute function to run parsePhotos in a separate isolate.
  return compute(parsePhotos, response.body);
}

使用 Isolates 需要注意的地方

Isolates 通过来回传递消息来交流。这些消息可以是任何值,它们可以是 null、num、bool、double 或者 String,哪怕是像这个例子中的 List 这样简单对象都没问题。

当你试图传递更复杂的对象时,你可能会遇到错误,例如在 isolates 之间的 Future 或者 http.Response。

完整样例

import 'dart:async';
import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));

  // Use the compute function to run parsePhotos in a separate isolate.
  return compute(parsePhotos, response.body);
}

// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();

  return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}

class Photo {
  final int albumId;
  final int id;
  final String title;
  final String url;
  final String thumbnailUrl;

  Photo({
    required this.albumId,
    required this.id,
    required this.title,
    required this.url,
    required this.thumbnailUrl,
  });

  factory Photo.fromJson(Map<String, dynamic> json) {
    return Photo(
      albumId: json['albumId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      url: json['url'] as String,
      thumbnailUrl: json['thumbnailUrl'] as String,
    );
  }
}

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final appTitle = 'Isolate Demo';

    return MaterialApp(
      title: appTitle,
      home: MyHomePage(title: appTitle),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;

  MyHomePage({Key? key, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: FutureBuilder<List<Photo>>(
        future: fetchPhotos(http.Client()),
        builder: (context, snapshot) {
          if (snapshot.hasError) print(snapshot.error);

          return snapshot.hasData
              ? PhotosList(photos: snapshot.data!)
              : Center(child: CircularProgressIndicator());
        },
      ),
    );
  }
}

class PhotosList extends StatelessWidget {
  final List<Photo> photos;

  PhotosList({Key? key, required this.photos}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
      ),
      itemCount: photos.length,
      itemBuilder: (context, index) {
        return Image.network(photos[index].thumbnailUrl);
      },
    );
  }
}

参考: flutter.cn/docs/cookbo…