Flutter 異步編程(Future、async、await)
在Android開發中,異步編程是必不可少的,比如網絡請求、IO操作等很多都是異步操作,而在Android原生中,有主線程和工作線程的概念,耗時操作都是要放到工作線程中的,ui要在主線程中更新,因此,原生Android開發中對線程的處理是必不可少的,幸運的是,一些第三方庫例如Rxjava、RxAndroid讓我們的線程切換起來十分的方便。
但是Flutter是基於Datt語言實現的,而Dart中的代碼是在一個線程中運行的,因此,Flutter也是單線程模型的。
參考資料可以看官方說明:Dart異步編程
這裏不禁有一個問題,Flutter爲啥要用Dart呢?單線程會不會比多線程的效率低呢?
先不管效率的問題了,實際運行起來速度還是很快的。
也就是說,我們在Flutter中對異步的處理並不是像原生Android那樣是多個線程去處理的。
那麼,Flutter中是怎麼處理異步操作的呢?
Flutter給我們提供了Future對象以及async和await關鍵字來支持異步編程。
我們首先來看看 Future
Future是一個抽象類,我們常用的方法如下
Future對象表示異步操作的結果,我們通常通過then()來處理返回的結果
async用於標明函數是一個異步函數,其返回值類型是Future類型
await用來等待耗時操作的返回結果,這個操作會阻塞到後面的代碼
Fluture中的網絡請求
網絡請求是非常典型的異步任務,下面我們就來結合網絡請求來看看Flutter中的異步是如何使用的。
網絡請求的方式有很多,這裏我就直接用目前比較好用的DIO網絡請求庫 了,你也可以使用官方文檔中的網絡請求 ,都是可以的。
下面我們來簡單用一用網絡請求。
這裏我使用的聚合上的一個接口
接口地址:http://v.juhe.cn/toutiao/index?type=keji&key=4c52313fc9247e5b4176aed5ddd56ad7
關於DIO如何使用這裏就不講了,Github上文檔很詳細,使用起來也很簡單。
下面我們直接用:
首先要先導包
import 'package:dio/dio.dart';
請求接口獲取數據的方法
/**
* 請求接口獲取數據
*/
Future<Response> getData() async {
String url = "http://v.juhe.cn/toutiao/index";
String key = "4c52313fc9247e5b4176aed5ddd56ad7";
String type = "keji";
print("開始請求數據");
Response response =
await Dio().get(url, queryParameters: {"type": type, "key": key});
print("請求完成");
return response;
}
注意一下幾點:
- 網絡請求是耗時操作
- 要使用async來標明getData這個函數是一個異步函數
- await 用於等待請求返回的結果,此時會阻塞掉後面的代碼,只有當請求結束後面的代碼纔會執行
- async標註的函數其返回值類型是Future
然後我們就可以在main函數中來接收網絡請求後的結果了:
main() {
getData().then((result) {
print("接口返回的數據是:${result}");
}).whenComplete((){
print("異步任務處理完成");
}).catchError((){
print("出現異常了");
});
print("我是在請求數據後面的代碼呦!");
}
我們來看看執行的結果:
這樣一來,我們就完成了Flutter中的異步操作了,可以看到,相對於原生Android來講,Flutter中的異步是非常簡單的。
Flutter FutureBuilder
FutureBuilder 實際上就是對Future進行封裝的一個Widget。我們先來看看他的構造方法
const FutureBuilder({
Key key,
this.future,
this.initialData,
@required this.builder
})
其中,
future接收Future<T>
類型的值,實際上就是我們的異步函數,通常情況下都是網絡請求函數
initialData 表示在異步函數執行完成之前可以給快照進行使用,簡單理解就是初始數據,應該不是很常用
builder:接收一個AsyncWidgetBuilder<T>
類型的值,看源碼
/// Signature for strategies that build widgets based on asynchronous
/// interaction.
///
/// See also:
///
/// * [StreamBuilder], which delegates to an [AsyncWidgetBuilder] to build
/// itself based on a snapshot from interacting with a [Stream].
/// * [FutureBuilder], which delegates to an [AsyncWidgetBuilder] to build
/// itself based on a snapshot from interacting with a [Future].
typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);
AsyncWidgetBuilder爲構建器提供了一個AsyncSnapshot對象,我們再來看看AsyncSnapshot的源碼
下圖是AsyncSnapshot中的屬性和方法
AsyncSnapshot中封裝了connectionState(連接狀態)、data(實際上就是future執行後返回的數據)以及error(實際上就是future錯誤時返回的錯誤信息)
data和error比較好理解,我們主要來看看connectionState
connectionState是一個enum 類型的值,其源碼如下
enum ConnectionState {
/// Not currently connected to any asynchronous computation.
///
/// For example, a [FutureBuilder] whose [FutureBuilder.future] is null.
none,
/// Connected to an asynchronous computation and awaiting interaction.
waiting,
/// Connected to an active asynchronous computation.
///
/// For example, a [Stream] that has returned at least one value, but is not
/// yet done.
active,
/// Connected to a terminated asynchronous computation.
done,
}
- none :當前未連接到任何異步計算。
- waiting : 連接成功等待交互
- active :正在交互中,可以理解爲正在返回數據
- done :交互完成,可以理解爲數據返回完成,此時如果是正確的返回則data就有數據了
搞清楚這些,我們就可以開心的使用FutureBuilder了。
Flutter 請求網絡數據時顯示加載中
一個很常見的需求,在首次進入頁面時,此時數據還需要從網絡上獲取,我們希望在網絡請求完成之前顯示一個加載頁面,請求完成之後再顯示數據。此時,我們就可以使用FutureBuilder來完成了
首先是接口請求函數,爲了更明顯的能看到加載控件的顯示,這裏的異步請求函數中我延時3秒後再請求數據,代碼如下
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_async/widget/loading.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '新聞列表',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: '新聞列表'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: _getNews(),
builder: (BuildContext context, AsyncSnapshot<Response> snapshot) {
/*表示數據成功返回*/
if (snapshot.hasData) {
Response response = snapshot.data;
return Text("${response.data.toString()}");
} else {
return LoadingWidget();
}
},
));
}
}
/**
* 請求接口獲取數據
*/
Future<Response> _getNews() async {
await Future.delayed(Duration(seconds: 3), () {
print("延時三秒後請求數據");
});
String url = "http://v.juhe.cn/toutiao/index";
String key = "4c52313fc9247e5b4176aed5ddd56ad7";
String type = "keji";
print("開始請求數據");
Response response =
await Dio().get(url, queryParameters: {"type": type, "key": key});
print("請求完成");
return response;
}