美文网首页
网络与项目实战

网络与项目实战

作者: 浅墨入画 | 来源:发表于2021-12-14 21:57 被阅读0次

    异步多线程结合

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

    相关文章

      网友评论

          本文标题:网络与项目实战

          本文链接:https://www.haomeiwen.com/subject/jqjrfrtx.html