在這篇文章中,將向你們分享在Flutter中:html
這些Flutter開發的實用技能。數據庫
Dart有一個單線程執行模型,支持Isolate
(一種在另外一個線程上運行Dart代碼的方法),一個事件循環和異步編程。除非你本身建立一個 Isolate
,不然你的 Dart 代碼永遠運行在主UI 線程,並由 event loop 驅動。Flutter 的 event loop 和 iOS 中的 main loop 類似:Looper
是附加在主線程上的。編程
Dart 的單線程模型,並不意味着你寫的代碼必定要做爲阻塞操做的方式運行,從而卡住 UI。相反,可使用 Dart 語言提供的異步工具,例如 async / await
,來實現異步操做。json
舉個例子,你可使用 async / await
來讓 Dart 幫你作一些繁重的工做,編寫網絡請求代碼而不會掛起 UI:安全
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
複製代碼
上面作法等價於Android中的
runOnUiThread
。 以上代碼片斷的完整部分能夠在課程源碼中查找。bash
一旦 await
的網絡請求完成,經過調用 setState()
來更新 UI,這會觸發 widget 子樹的重建,並更新相關數據。網絡
下面的例子展現了異步加載數據,並用 ListView
展現出來:app
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
}));
}
Widget getRow(int i) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row ${widgets[i]["title"]}")
);
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
複製代碼
因爲 Flutter 是單線程而且跑着一個 event loop(就像 Node.js),所以你沒必要擔憂線程管理或生成後臺線程。若是你正在作 I/O 操做,如訪問磁盤或網絡請求,能夠安全地使用 async / await
來完成。若是你須要作讓 CPU 執行繁忙的計算密集型任務,你須要使用 Isolate
來避免阻塞 event loop。less
在Android中,當你想訪問一個網絡資源時,你一般會建立一個AsyncTask,當你須要一個耗時的後臺任務時,你一般須要IntentService,在Flutter中則不須要這麼繁瑣。異步
對於 I/O 操做,經過關鍵字 async
把方法聲明爲異步方法,而後經過await
關鍵字等待該異步方法執行完成:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
複製代碼
在Android中,當你繼承AsyncTask時,一般會覆蓋3個方法,OnPreExecute、doInBackground和onPostExecute。 在Flutter中沒有這種模式的等價物,由於你只需await函數執行完成,而Dart的事件循環將負責其他的事情。
以上就是對諸如網絡請求、數據庫訪問等,I/O 操做的典型作法。
然而,有時候你須要處理大量的數據,這會致使你的 UI 掛起。在 Flutter 中,使用 Isolate
來發揮多核心 CPU 的優點來處理那些長期運行或是計算密集型的任務。
Isolate
是分離的運行線程,而且不和主線程的內存堆共享內存。這意味着你不能訪問主線程中的變量,或者使用 setState()
來更新 UI。正如它們的名字同樣,Isolate 不能共享內存。
下面的例子展現了一個簡單的Isolate是如何把數據返回給主線程來更新 UI 的:
import 'dart:isolate';
...
loadData() async {
// 打開ReceivePort以接收傳入的消息
ReceivePort receivePort = ReceivePort();
//建立並生成與當前Isolate共享相同代碼的Isolate
await Isolate.spawn(dataLoader, receivePort.sendPort);
// 流的第一個元素
SendPort sendPort = await receivePort.first;
// 流的第一個元素被收到後監聽會關閉,因此須要新打開一個ReceivePort以接收傳入的消息
ReceivePort response = ReceivePort();
//經過此發送端口向其對應的「ReceivePort」①發送異步[消息],這個「消息」指的是發送的參數②。
sendPort.send(
["https://jsonplaceholder.typicode.com/posts", response.sendPort]);
// 獲取端口發送來的數據③
List msg = await response.first;
setState(() {
widgets = msg;
});
}
// isolate的入口函數,該函數會在新的Isolate中調用,Isolate.spawn的message參數會做爲調用它時的惟一參數
static dataLoader(SendPort sendPort) async {
// 打開ReceivePort①以接收傳入的消息
ReceivePort port = ReceivePort();
// 通知其餘的isolates,本isolate 所監聽的端口
sendPort.send(port.sendPort);
// 獲取其餘端口發送的異步消息 msg② -> ["https://jsonplaceholder.typicode.com/posts", response.sendPort]
await for (var msg in port) {
//等價於List msg= await port.first;
String data = msg[0];
SendPort replyTo = msg[1];
String dataURL = data;
http.Response response = await http.get(dataURL);
// 其對應的「ReceivePort」發送解析出來的JSON數據③
replyTo.send(json.decode(response.body));
}
}
複製代碼
以上代碼片斷的完整部分能夠在課程源碼中查找。
這裏,dataLoader()
是一個運行於本身獨立執行線程上的 Isolate
。在 Isolate
裏,你能夠執行 CPU 密集型任務(例如解析一個龐大的 json,解析json也是很耗時的哦),或是計算密集型的數學操做,如加密或信號處理等。
你能夠運行下面的完整例子:
import 'dart:convert';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
if (widgets.length == 0) {
return true;
}
return false;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
} else {
return getListView();
}
}
getProgressDialog() {
return Center(child: CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: getBody());
}
ListView getListView() => ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row ${widgets[i]["title"]}"));
}
loadData() async {
// 打開ReceivePort以接收傳入的消息
ReceivePort receivePort = ReceivePort();
//建立並生成與當前Isolate共享相同代碼的Isolate
await Isolate.spawn(dataLoader, receivePort.sendPort);
// 流的第一個元素
SendPort sendPort = await receivePort.first;
// 流的第一個元素被收到後監聽會關閉,因此須要新打開一個ReceivePort以接收傳入的消息
ReceivePort response = ReceivePort();
//經過此發送端口向其對應的「ReceivePort」①發送異步[消息],這個「消息」指的是發送的參數②。
sendPort.send(
["https://jsonplaceholder.typicode.com/posts", response.sendPort]);
// 獲取端口發送來的數據③
List msg = await response.first;
setState(() {
widgets = msg;
});
}
// isolate的入口函數,該函數會在新的Isolate中調用,Isolate.spawn的message參數會做爲調用它時的惟一參數
static dataLoader(SendPort sendPort) async {
// 打開ReceivePort①以接收傳入的消息
ReceivePort port = ReceivePort();
// 通知其餘的isolates,本isolate 所監聽的端口
sendPort.send(port.sendPort);
// 獲取其餘端口發送的異步消息 msg② -> ["https://jsonplaceholder.typicode.com/posts", response.sendPort]
await for (var msg in port) {
//等價於List msg= await port.first;
String data = msg[0];
SendPort replyTo = msg[1];
String dataURL = data;
http.Response response = await http.get(dataURL);
// 其對應的「ReceivePort」發送解析出來的JSON數據③
replyTo.send(json.decode(response.body));
}
}
}
複製代碼
關於Flutter的更多異步編程知識,能夠學習《Flutter從入門到進階-實戰攜程網App》。
在 Flutter 中,使用流行的 http package 作網絡請求很是簡單。它把你可能須要本身作的網絡請求操做抽象了出來,讓發起請求變得簡單。
要使用 http 包,在 pubspec.yaml
中添加以下依賴:
dependencies:
...
http: ^0.12.0+1
複製代碼
發起網絡請求,在 http.get()
這個 async
方法中使用 await
:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
[...]
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
複製代碼
以上代碼片斷的完整部分能夠在課程源碼中查找。
一旦得到結果後,你能夠經過調用setState來告訴Flutter更新其狀態,setState將使用網絡調用的結果更新UI。
關於網絡請求的更多內容和實戰技巧可學習《基於Http實現網絡操做》部分的課程。
那麼,在Flutter也有與之對應的widget叫ProgressIndicator
。經過一個布爾 flag 來控制是否展現進度。在任務開始時,告訴 Flutter 更新狀態,並在結束後隱藏。
在下面的例子中,build 函數被拆分紅三個函數。若是 showLoadingDialog()
是 true
(當 widgets.length == 0
時),則渲染 ProgressIndicator
。不然,當數據從網絡請求中返回時,渲染 ListView
:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
return widgets.length == 0;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
} else {
return getListView();
}
}
getProgressDialog() {
return Center(child: CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: getBody());
}
ListView getListView() => ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return Padding(padding: EdgeInsets.all(10.0), child: Text("Row ${widgets[i]["title"]}"));
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
複製代碼