【譯】Flutter :Parse JSON in the background

牛肉羹一碗發表於2019-08-21

原文為Flutter官方的Cookbook:Parse JSON in the background

通常,Dart app 會在一個執行緒內完成所有工作。許多情況下,這個模型簡化了編碼,速度足夠快而不會導致應用程式效能差或動畫斷斷續續,通常稱為“jank”。

然而,你可能需要執行一些耗時的操作,例如:解析一個非常大的Json檔案。如果這個過程耗時超過16毫秒,使用者就會感覺卡頓、不流暢。

為了避免這種情況,你需要在後臺執行類似於這種耗時的操作。對於Android,這就意味著在不同執行緒排程工作。而在Flutter,你可以使用Isolate

該方法有以下步驟:

  1. 新增http依賴包
  2. 使用http包傳送網路請求
  3. 將結果解析成List<Photo>
  4. 移到isolate完成

1.新增http依賴包

pubspec.yaml檔案新增依賴包

dependencies:
  http: <latest_version>
複製程式碼

2.傳送網路請求

本例中,使用http.get()方法從JSONPlaceholder REST API中獲取一個包含5000個照片物件列表的Json大文件。

Future<http.Response> fetchPhotos(http.Client client) async {
  return client.get('https://jsonplaceholder.typicode.com/photos');
}
複製程式碼

3.解析Json

建立Photo

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

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

  factory Photo.fromJson(Map<String, dynamic> json) {
    return Photo(
      id: json['id'] as int,
      title: json['title'] as String,
      thumbnailUrl: json['thumbnailUrl'] as String,
    );
  }
}
複製程式碼

將請求結果轉換成陣列

現在,使用以下說明更新fetchPhotos()函式,以便它返回一個Future<List<Photo>>:

  1. 建立一個parsePhotos()函式,該函式將請求結果轉換為List<Photo>
  2. fetchPhotos()函式中呼叫parsePhotos()函式
// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
  final parsed = json.decode(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('https://jsonplaceholder.typicode.com/photos');

  return parsePhotos(response.body);
}
複製程式碼

4.轉移到isolate中完成

如果你在一臺很慢的裝置中執行fetchPhotos()函式,你可能會注意到,當應用在解析和轉換JSON時會有點卡。這就是‘jank’,而且你會想要擺脫它。

你可以通過呼叫Flutter提供的computed()函式在後臺的isolate進行解析和轉換來擺脫‘jank’。computed()會在後臺執行耗時操作並將結果返回。在這個例子中,執行的是parsePhotos()函式。

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

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

5.注意事項

Isolate通過來回傳遞資訊進行通訊,這些資訊可以是原始值,例如nullnum,bool,double,或者string,也可以是簡單的物件就像這個例子中的List<Photo>

如果你嘗試在isolate之間傳遞更復雜的物件,比如Futurehttp.Response,你可能會遇到錯誤。

6.尾記

這是今天在翻官網看到,感覺還不錯。不過現在翻完,感覺簡單了點,但感覺挺實用的,就像Android中的AsynTask之類的。但都辛苦翻完了,就當一篇小記錄吧。才疏學淺,沒有什麼深度,還請多指教。
還有這個jank實在不會翻,有些地方還能領悟,有些地方就完全不懂了。

7.完整程式碼

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('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 = json.decode(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({this.albumId, this.id, this.title, this.url, 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, 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, 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);
      },
    );
  }
}
複製程式碼

效果圖

相關文章