网络与项目实战

异步多线程结合

isolate进程间的区别
  • isolate除了拥有线程,还有独立的内存空间,但这个内存空间是局部的,主要存储自己创造的对象或者数据;传递数据的时候需要借助进程间的通信
  • isolate并不像一个完整的进程那样,拥有虚拟内存且操作系统会分配一个很大的内存空间。
  • 开辟一个新的进程,计算机操作系统会分配一个独立内存,拥有独立的堆、栈等;而isolate是一个轻量级的,并不会去开辟堆、栈等

下面我们来学习异步多线程相结合的用法

  • 案例一:使用箭头函数
// 忽略当前文件的警告
// ignore_for_file: avoid_print
import 'package:flutter/foundation.dart';

void main() => isoLoadfunc();

void isoLoadfunc() {
  Future(() => compute(func, 123).then((value) => print('1结束')));
  Future(() => compute(func, 123).then((value) => print('2结束')));
  Future(() => compute(func, 123).then((value) => print('3结束')));
  Future(() => compute(func, 123).then((value) => print('4结束')));
  Future(() => compute(func, 123).then((value) => print('5结束')));
}

func(int message) {}
查看日志

打印结果是随机的,说明是多线程异步执行;其中.then的处理是在子线程中做的。

  • 案例二:使用花括号
// ignore_for_file: avoid_print
import 'package:flutter/foundation.dart';

void main() => isoLoadfunc();

void isoLoadfunc() {
  Future(() {
    compute(func, 123);
  }).then((value) => print('1结束'));
  Future(() {
    compute(func, 123);
  }).then((value) => print('2结束'));
  Future(() {
    compute(func, 123);
  }).then((value) => print('3结束'));
  Future(() {
    compute(func, 123);
  }).then((value) => print('4结束'));
  Future(() {
    compute(func, 123);
  }).then((value) => print('5结束'));
}

func(int message) {}
查看日志

这里为什么又变成同步执行呢?
注意: 箭头函数默认包含return,所以上面需要添加return

// ignore_for_file: avoid_print
import 'package:flutter/foundation.dart';

void main() => isoLoadfunc();

void isoLoadfunc() {
  Future(() {
    return compute(func, 123);
  }).then((value) => print('1结束'));
  Future(() {
    return compute(func, 123);
  }).then((value) => print('2结束'));
  Future(() {
    return compute(func, 123);
  }).then((value) => print('3结束'));
  Future(() {
    return compute(func, 123);
  }).then((value) => print('4结束'));
  Future(() {
    return compute(func, 123);
  }).then((value) => print('5结束'));
}

func(int message) {}
查看日志

不加return的话,.then是在主线程中处理的;添加return.then是在子线程中处理的;其中Future中添加compute子线程是同步添加的。

  • 案例三:异步任务微任务的结合
void main() {
  Future x = Future(() {
    print('异步任务1');
    scheduleMicrotask(() {
      print('微任务1');
    });
  });
  x.then((value) {
    print('微任务2');
  });
}
查看日志

由日志可以看出,.then是与Future();是一体的。

  • 案例四:下面验证.thenFuture();是一体的
void main() {
  Future x = Future(() {
    print('异步任务1');
    scheduleMicrotask(() {
      print('微任务1');
    });
  });
  x.then((value) {
    print('微任务2');
  });
  x.whenComplete(() {
    print('完毕');
  });
}
查看日志

Future();.then. whenComplete是链式调用,可以看成是一体的。

  • 案例五:Timer开启异步任务
void main() {
  Timer.run(() {
    print('异步任务');
  });
  print('来了');
}
查看日志

下面打开之前的wechat_demo工程,切到chat_page.dart页面,添加如下代码

  @override
  void initState() {
    super.initState();

    int _count = 0;
    // 添加异步任务
    Timer.periodic(Duration(seconds: 1), (timer) {
      _count++;
      print(_count);
      if (_count == 99) {
        timer.cancel();
      }
    });
......

添加完上面代码,滑动聊天页面并没有卡顿,说明FlutterTimer优化的很好,不会卡住主线程。切换页面也没有问题,是因为我们之前保存了页面状态,如果我们把保存页面状态的参数改为bool get wantKeepAlive => false;,切换页面就会发生内存泄漏

  • 添加页面销毁方法,查看切换页面的时候,wechatPage页面是否销毁
@override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    print('chatPage销毁了');
  }
查看日志

由日志可以看出wechatPage页面销毁了,但是Timer并没有销毁,依然在打印。所以要在页面销毁的时候,销毁定时器

Timer _timer;

  @override
  void dispose() {
    // TODO: implement dispose
    print('chatPage销毁了');
    if (_timer != null && _timer.isActive) {
      _timer.cancel();
    }
    // super.dispose();要放在最后面
    super.dispose();
  }

  @override
  void initState() {
    super.initState();

    int _count = 0;
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      _count++;
      print(_count);
      if (_count == 99) {
        timer.cancel();
      }
    });
......

请思考一个问题:我们什么时候使用异步?什么时候使用多线程?

  • AppBar里面添加一个耗时操作
GestureDetector(
  onTap:() {
    Future(() {
      print('开始');
      // 耗时操作
      for(int i = 0; i < 1000000; i ++) {}
        print('结束了');
      });
    },
    child: Container(
      child: Icon(Icons.add),
    ),
)
添加耗时操作
  • 上面的耗时操作必须放入子线程
GestureDetector(
  onTap:() {
    Future(() {
      return compute(func, 123);
    });
   },
  child: Container(
    child: Icon(Icons.add),
  ),
)

// 注意:该方法写在class _ChatPageState外面
func (int message) {
  print('开始');
  // 耗时操作
  for(int i = 0; i < 1000000; i ++) {}
  print('结束了');
}

三方库Dio下载

打开future_demo工程,我们来使用Dio下载文件

  • 配置Dio三方库
引入dio三方库

Pub get引入之后,External Libraries -> Dart Packages目录下能看到dio-4.0.4库。

成功引入
  • 使用Dio下载文件
import 'package:dio/dio.dart';

void main() {
  // 发送网络请求
  // 1. 创建dio对象
  final dio = Dio();
  // 2. 下载数据
  var downloadUrl =
      'https://edu-files-1251502357.cos.ap-shanghai.myqcloud.com/CourseTeacher_2.8.1.13_DailyBuild.dmg';
  // 3. 开始下载,保存目录
  // 这里如果没有123目录,会自己创建
  dio.download(downloadUrl, '/Users/wn/Desktop/123/TX.dmg',
      // 下载进度
      onReceiveProgress: showDownloadProgress)
      .then((value) => print(value))
      .whenComplete(() => print('下载结束'))
      .catchError((e) => print(e));
}

void showDownloadProgress(int count, int total) {
  print('count:$count  total:$total');
  if (total != -1) {
    print((count / total * 100).toStringAsFixed(0)+'%');
  }
}
正在下载

下面把下载逻辑抽取出来

import 'package:dio/dio.dart';

void main() {
  // 发送网络请求
  // 1. 创建dio对象
  final dio = Dio();
  // 2. 下载数据
  var downloadUrl =
      'https://edu-files-1251502357.cos.ap-shanghai.myqcloud.com/CourseTeacher_2.8.1.13_DailyBuild.dmg';
  // 3. 保存目录
  String savePath = '/Users/wangning/Desktop/123/TX.dmg';
  // 4. 开始下载
  download(dio, downloadUrl, savePath);
}

// 下载方式一
void download(Dio dio, String url, savePath) {
  dio.download(url, savePath,
      onReceiveProgress: showDownloadProgress)
      .then((value) => print(value))
      .whenComplete(() => print('下载结束'))
      .catchError((e) => print(e));
}

// 下载方式二
void download1(Dio dio, String url) {
  // 手机端保存路径,沙盒路径
  String iOSPath = Directory.systemTemp.path+'/TX.dmg';
  print(iOSPath);
  dio.download(url, (header) {
    // 回调的方式,返回保存路径
    return iOSPath;
  }, onReceiveProgress: showDownloadProgress)
      .whenComplete(() => print('下载结束'))
      .catchError((e) => print(e));
}

void showDownloadProgress(int count, int total) {
  print('count:$count  total:$total');
  if (total != -1) {
    print((count / total * 100).toStringAsFixed(0)+'%');
  }
}

保存沙盒路径

flutter: /Users/wn/Library/Developer/CoreSimulator/Devices/141ABB45-E8A8-461C-86EF-9F1A3493DD0C/data/Containers/Data/Application/A18A7F52-FB54-4C7A-8F07-D74DB71703FF/tmp/TX.dmg

封装网络请求与切换项目请求库

我们在wechat_demo工程的chat_page.dart文件中进行网络请求,代码如下

Future<List<Chat>> getDatas() async {
    _cancelConnect = false;
    final url =
        Uri.parse('http://rap2api.taobao.org/app/mock/256798/api/chat/list');
    //发送请求
    final response = await http.get(url);
    if (response.statusCode == 200) {
......

我们使用的是http中的方法http.get,如果以后我们需要更换网络请求库,就需要在很多地方修改,所以我们要把http中的方法进行封装,使用我们自己封装的方法。下面进行网络库的封装......

  • 新建tools目录,再创建网络请求文件http_manager.dart
import 'package:http/http.dart' as http;

class HttpManager {
// Url是Uri的子集,只是Url定义的更详细一些,我们一般常用的是Url
  Future<http.Response> get(Uri url, {Map<String, String> headers}) {
    return http.get(url);
  }
}

// 外面使用方式
HttpManager().get(url);

将来如果需要替换,只需要更改HttpManager中的方法即可。

下面我们把dio库进行封装
  • pubspec.yaml文件中配置dio: ^4.0.4,点击Pub get引入
  • 下面对dio网络请求进行封装
<!-- http_manager.dart文件 -->
import 'package:dio/dio.dart';

// 网络请求方法类型
enum HttpMethod { GET, POST }

class HttpManager {
  //创建Dio单例对象,防止每次发送网络请求都重新创建Dio()对象
  static Dio _dioInstance;
  static Dio _getDioInstance() {
    if (_dioInstance == null) {
      _dioInstance = Dio();
    }
    return _dioInstance;
  }

  //返回我们自己的Response
  static Future<Response> get(String url,
      {Map<String, dynamic> queryParameters}) async {
    return await _sendRequest(HttpMethod.GET, url,
        queryParameters: queryParameters);
  }

  static Future<Response> post(String url,
      {Map<String, dynamic> queryParameters, dynamic data}) async {
    return await _sendRequest(HttpMethod.POST, url,
        queryParameters: queryParameters, data: data);
  }

  // queryParameters请求参数,添加下划线表示私有方法
  static Future _sendRequest(HttpMethod method, String url,
      {Map<String, dynamic> queryParameters, dynamic data}) async {
    try {
      switch (method) {
        case HttpMethod.GET:
          return await HttpManager._getDioInstance()
              .get(url, queryParameters: queryParameters);
        case HttpMethod.POST:
          return await HttpManager._getDioInstance()
              .post(url, queryParameters: queryParameters, data: data);
        default:
          throw Exception('请求方式错误');
      }
    } on DioError catch (e) {
      print(e.message);
    } on Exception catch (e) {
      print(e.toString());
    }
    return null;
  }
}
  • HttpManager封装类的使用
<!-- chat_page.dart文件 -->
Future<List<Chat>> getDatas() async {
    _cancelConnect = false;
    //发送请求
    final response = await HttpManager.get(
        'http://rap2api.taobao.org/app/mock/256798/api/chat/list');

    if (response.statusCode == 200) {
      //获取响应数据,转成Map类型  不需要转换了!不是Json,直接是Map了
      // final responsBody = json.decode(response.data);

      //map 作为List的遍历方法。
      List chatList = response.data['chat_list']
          .map<Chat>((item) => Chat.formMap(item))
          .toList();
      return chatList;
    } else {
      throw Exception('statusCode:${response.statusCode}');
    }
  }

自定义searchCell

聊天页面chat_page.dart文件添加搜索框

滑动聊天页我们发现搜索框跟着页面一块滑动,说明搜索框是ListView里面的元素;最简单的思路就是添加一个Cell

  • 选中下面代码,按快捷键Cmd + option + M,弹出的输入框中输入_itemBuilderForRow,会把选中的代码抽成Widget小部件。
快捷键抽取小部件
  • 查看抽取出来的小部件
Widget _itemBuilderForRow(BuildContext context, int index) {
    // index==0 引用自定义的SearchCell
    if (index == 0) {
      return SearchCell(
        datas: _datas,
      );
    }
    //保证从模型数据正确取数据。从0开始!
    index--;

    return ListTile(
      title: Text(_datas[index].name),
      subtitle: Container(
        alignment: Alignment.bottomCenter,
        padding: EdgeInsets.only(
          right: 10,
        ),
        height: 25,
        child: Text(
          _datas[index].message,
          overflow: TextOverflow.ellipsis,
        ),
      ),
      leading: Container(
        width: 44,
        height: 44,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(6.0),
            image:
                DecorationImage(image: NetworkImage(_datas[index].imageUrl))),
      ),
    );
  }

// 查看引用小部件的代码
body: Container(
  child: Container(
    child: _datas.length == 0
      ? Center(
        child: Text('Loading...'),
      )
      : ListView.builder(
         // 因为顶部要多一个搜索Cell,所以_datas.length + 1
         itemCount: _datas.length + 1,
         itemBuilder: _itemBuilderForRow,
      ),
  ),
),
  • 新建search_cell.dart文件封装SearchCell搜索框
import 'package:flutter/material.dart';
import 'package:wechat/const.dart';

class SearchCell extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        print('点击了搜索框!');
      },
      child: Container(
        height: 44,
        color: WeChatThemeColor,
        padding: EdgeInsets.all(5),
        child: Stack(
          alignment: Alignment.center, //搜索图标文字上下居中
          children: [
            Container(
              // 设置圆角
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(6.0),
              ),
            ), //白底
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Image(
                    image: AssetImage('images/放大镜b.png'),
                    width: 15,
                    color: Colors.grey),
                Text(' 搜索', style: TextStyle(fontSize: 15, color: Colors.grey))
              ],
            ),
          ],
        ),
      ),
    );
  }
}
  • appBar底部添加黑色线条
return Scaffold(
      appBar: AppBar(
        // appBar下面添加黑色线条
        elevation: 0.0,
......
运行效果
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容