Flutter 入門與實戰(三十):Dio之戛然而止

甜蜜的約會

程序員小明今天很開心,由於今天是他和女友的戀愛一週年記念日。衆所周知,程序員要找女友是很難的,小明目前是他們辦公室惟一脫單的程序員,成爲了衆多程序員豔羨的對象。小明今天的工做效率也很高,鍵盤敲得都要飛起來了,整個辦公室都響着他快樂的鍵盤敲擊聲。到了下班時間,小明早已提交好代碼,飛奔下樓奔赴約會了——固然,他沒有忘記買花。git

女友見了小明也是很是開心,這一天的安排倆人也早就計劃好了。一對小年輕回憶起這一年的經歷,甜言蜜語說個不停——倆人引發了很多揹着雙肩包、穿着格子衫的人羨慕的眼光。程序員

dating.jpg

電話響了

到了晚上9點多,倆人的浪漫晚餐結束的時候,小明的女友說:「我們回去吧!」,小明心照不宣,正準備牽女友手往外走的時候,電話響了!電話響了!電話響了!小明內心預感不妙,收回剛要伸出的手,從口袋裏掏出了手機。看到手機上顯示的名字,他要崩潰了!那是他們領導打來的電話。 「小明,趕忙回公司,你今天提交的代碼出Bug 了!」 「呃,很緊急嗎?」 「緊急啊!不緊急用得着打電話給你嗎?」 「那……那我立刻回去。」 小明無奈地看了一眼女友。 「領導讓我回去改 Bug!」 「不能明天再去嗎?咱們正在約會唉!」女友一臉不高興。 「不行哦!咱們程序員發現 Bug 要立刻改!」 小明說完,背起他的雙肩包趕忙往公司趕去,留下女友一我的呆呆地站在那裏。小明沒有聽見女友的一句話:「我以爲咱們不合適……」github

言歸正傳

上述的故事在咱們的平常生活很常見,改 Bug 嘛,那不是咱們程序員賴以生存的根基麼?實際上,一件事情被打斷常常發生,在網絡請求裏也同樣。好比說,剛進入一個頁面,網絡請求還在進行,而後用戶又退出這個頁面了。那這時候的請求其實沒什麼意義,若是是簡單的請求還好,但若是是下載文件、進行一系列請求的時候,若是可以取消請求就行了。安全

Dio 提供了取消令牌(CancelToken)機制用於取消還沒有完成的請求。每一個請求均可以攜帶一個 CancelToken 對象,當調用 CancelToken 的 cancel 方法時,就會通知該請求中止當前的請求,從而達到中斷請求的目的。這就比如是小明正在約會的時候,被領導的電話叫去改 Bug 同樣,他的約會至關於泡湯了!markdown

CancelToken 的使用

咱們先看一個簡單的示例,首先在咱們的 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

屏幕錄製2021-07-17 下午3.59.57.gif

實際應用

假設咱們退出頁面前要取消未完成的請求,就可使用 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();
  }
}
複製代碼

業務流程以下:

  • 進入界面後,點擊底部的電話圖標按鈕開始請求
  • 點擊返回界面時檢查_token 是否爲空,不爲空則調用 cancel 方法取消請求。cancel 方法能夠接收一個可選的參數,用於表名取消的緣由。

若是網絡情況不太好而你的手速有足夠快的話(打王者的技巧派上用場了),就能夠看到返回後會顯示一個提醒,說明咱們的請求被取消了。

這裏須要注意,因爲咱們在網絡請求的 then 回調調用了 setState 方法,這個方法在 dispose後是不能調用的,不然可能致使內存泄露。所以在調用前咱們判斷了一下 mounted 是否爲 true,若是是則表示沒有被 dispose,能夠安全地調用 setState 方法。

image.png

咱們下一篇來研究一下 Dio 這塊的源碼,看看 CancelToken 的實現機制。

後記

忠告各位程序員,重要的話說三遍:約會請記得關機!約會請記得關機!約會請記得關機!

相關文章
相關標籤/搜索