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 的实现机制。

后记

忠告各位程序员,重要的话说三遍:约会请记得关机!约会请记得关机!约会请记得关机!

相关文章
相关标签/搜索