网络与项目实战

异步多线程结合

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> 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 get(Uri url, {Map headers}) {
    return http.get(url);
  }
}

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

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

下面我们把dio库进行封装
  • pubspec.yaml文件中配置dio: ^4.0.4,点击Pub get引入
  • 下面对dio网络请求进行封装

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 get(String url,
      {Map queryParameters}) async {
    return await _sendRequest(HttpMethod.GET, url,
        queryParameters: queryParameters);
  }

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

  // queryParameters请求参数,添加下划线表示私有方法
  static Future _sendRequest(HttpMethod method, String url,
      {Map 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封装类的使用

Future> 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((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,
......
运行效果

你可能感兴趣的:(网络与项目实战)