程序員小明今天很開心,由於今天是他和女友的戀愛一週年記念日。衆所周知,程序員要找女友是很難的,小明目前是他們辦公室惟一脫單的程序員,成爲了衆多程序員豔羨的對象。小明今天的工做效率也很高,鍵盤敲得都要飛起來了,整個辦公室都響着他快樂的鍵盤敲擊聲。到了下班時間,小明早已提交好代碼,飛奔下樓奔赴約會了——固然,他沒有忘記買花。git
女友見了小明也是很是開心,這一天的安排倆人也早就計劃好了。一對小年輕回憶起這一年的經歷,甜言蜜語說個不停——倆人引發了很多揹着雙肩包、穿着格子衫的人羨慕的眼光。程序員
到了晚上9點多,倆人的浪漫晚餐結束的時候,小明的女友說:「我們回去吧!」,小明心照不宣,正準備牽女友手往外走的時候,電話響了!電話響了!電話響了!小明內心預感不妙,收回剛要伸出的手,從口袋裏掏出了手機。看到手機上顯示的名字,他要崩潰了!那是他們領導打來的電話。 「小明,趕忙回公司,你今天提交的代碼出Bug 了!」 「呃,很緊急嗎?」 「緊急啊!不緊急用得着打電話給你嗎?」 「那……那我立刻回去。」 小明無奈地看了一眼女友。 「領導讓我回去改 Bug!」 「不能明天再去嗎?咱們正在約會唉!」女友一臉不高興。 「不行哦!咱們程序員發現 Bug 要立刻改!」 小明說完,背起他的雙肩包趕忙往公司趕去,留下女友一我的呆呆地站在那裏。小明沒有聽見女友的一句話:「我以爲咱們不合適……」github
上述的故事在咱們的平常生活很常見,改 Bug 嘛,那不是咱們程序員賴以生存的根基麼?實際上,一件事情被打斷常常發生,在網絡請求裏也同樣。好比說,剛進入一個頁面,網絡請求還在進行,而後用戶又退出這個頁面了。那這時候的請求其實沒什麼意義,若是是簡單的請求還好,但若是是下載文件、進行一系列請求的時候,若是可以取消請求就行了。安全
Dio 提供了取消令牌(CancelToken)機制用於取消還沒有完成的請求。每一個請求均可以攜帶一個 CancelToken 對象,當調用 CancelToken 的 cancel 方法時,就會通知該請求中止當前的請求,從而達到中斷請求的目的。這就比如是小明正在約會的時候,被領導的電話叫去改 Bug 同樣,他的約會至關於泡湯了!markdown
咱們先看一個簡單的示例,首先在咱們的 HttpUtil 類的各種方法都加上了可選的參數 cancelToken,而且在檢測到取消後顯示對應的提示。網絡
static Future sendRequest(HttpMethod method, String url,
{Map<String, dynamic> queryParams,
dynamic data,
CancelToken cancelToken}) async {
try {
//...省略請求代碼
} on DioError catch (e) {
// 檢測錯誤是否是由於取消請求引發的,若是是打印取消提醒
if (CancelToken.isCancel(e)) {
EasyLoading.showInfo('領導喊你回去改 Bug 啦!');
} else {
EasyLoading.showError(e.message);
}
} on Exception catch (e) {
EasyLoading.showError(e.toString());
}
return null;
}
複製代碼
而後咱們模擬一個取消請求的狀況。咱們請求的是掘金的我的主頁的文章列表,首先建立了一個 CancelToken 對象,而後傳給對應的請求。發出請求後,咱們立刻調用了 cancel方法取消請求。這裏不能使用 async 和 await。由於 await 會等待請求完成,所以這裏使用的是 Future 的 then 方法(相似 Promise)。接收到響應後,咱們根據 CancelToken 對象的 isCancelled 屬性來判斷請求是否被取消,而且更新狀態變量_hasBug。app
void _bugHappened() {
CancelToken token = CancelToken();
JuejinService.listArticles('70787819648695', cancelToken: token)
.then((value) => {
setState(() {
_hasBug = token.isCancelled;
})
})
.onError((error, stackTrace) => {print(error.toString())});
token.cancel();
}
複製代碼
_hasBug 用於控制界面顯示,表示是否有 Bug,若是沒有 Bug 那小明能夠繼續約會,若是有 Bug 那小明得趕回公司改 Bug。咱們經過一個按鈕來觸發 bug 事件。async
class _AppointmentPageState extends State<AppointmentPage> {
bool _hasBug = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_hasBug ? '該死的Bug!' : '約會中...',
style: Theme.of(context).textTheme.headline4),
),
body: Container(
child: Center(
child: Image.asset(_hasBug ? 'images/bug.png' : 'images/dating.png'),
),
),
floatingActionButton: IconButton(
icon: Icon(Icons.call),
onPressed: () {
_bugHappened();
},
),
);
}
//...
}
複製代碼
運行結果以下圖所示,能夠看到點擊按鈕後出現了請求被取消事件——小明得回去改 Bug 了(小明心中一萬頭草泥馬飄過)!ide
假設咱們退出頁面前要取消未完成的請求,就可使用 CancelToken 來取消了。咱們能夠在 State生命週期函數的 deactivate 方法(該方法在 dispose 前會被調用)調用 CancelToken 的取消方法。爲了演示效果,咱們來一個請求比較慢的網站——Github。 因爲 CancelToken 對象在不一樣的方法使用,所以須要定義爲成員屬性,完整代碼以下。函數
class _AppointmentPageState extends State<AppointmentPage> {
bool _hasBug = false;
CancelToken _token;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_hasBug ? '該死的Bug!' : '約會中...',
style: Theme.of(context).textTheme.headline4),
),
body: Container(
child: Center(
child: Image.asset(_hasBug ? 'images/bug.png' : 'images/dating.png'),
),
),
floatingActionButton: IconButton(
icon: Icon(Icons.call),
onPressed: () {
_bugHappened();
},
),
);
}
void _bugHappened() {
_token = CancelToken();
HttpUtil.get('https://www.github.com', cancelToken: _token)
.then((value) => {
if (mounted)
{
setState(() {
_hasBug = _token.isCancelled;
})
}
})
.onError((error, stackTrace) => {});
}
@override
void deactivate() {
if (_token != null) {
_token.cancel('dispose');
}
super.deactivate();
}
}
複製代碼
業務流程以下:
若是網絡情況不太好而你的手速有足夠快的話(打王者的技巧派上用場了),就能夠看到返回後會顯示一個提醒,說明咱們的請求被取消了。
這裏須要注意,因爲咱們在網絡請求的 then 回調調用了 setState 方法,這個方法在 dispose後是不能調用的,不然可能致使內存泄露。所以在調用前咱們判斷了一下 mounted 是否爲 true,若是是則表示沒有被 dispose,能夠安全地調用 setState 方法。
咱們下一篇來研究一下 Dio 這塊的源碼,看看 CancelToken 的實現機制。
忠告各位程序員,重要的話說三遍:約會請記得關機!約會請記得關機!約會請記得關機!