美文网首页Flutter
Flutter中使用Dio网络请求如何解析protobuf协议格

Flutter中使用Dio网络请求如何解析protobuf协议格

作者: gogoingmonkey | 来源:发表于2020-12-10 18:20 被阅读0次
    经过几天的搜索尝试,网上很多写关于Flutter中使用protobuf 的文章,但是点进去,几乎都是清一色的介绍怎么安装环境,然后最后一步就是在pubspec.ymal中添加protobuf: ^0.13.4依赖.或者是flutter下使用protobuf和socket与服务器通信的文章。但是现在做前段开发的估计大部分用户还是用的Dio库进行的网络请求,至少目前我未找到一篇让我接入有用的文档。也就有了我的摸索经历了。

    一、背景介绍

    1.市面上搜索不到满足我当前需求的flutter 中具体怎么使用PB协议文档。

    需求:后台未使用gcpb框架处理pb,flutter如何使用dio库去解析一个具体的网络请求,该接口返回的是pb格式数据
    

    2.使用PB协议的大部分公司使用了grpc框架配合使用(当时我在解决这个问题时,放弃过Dio解析pb协议的第二个选择验证方案),但是这个grpc 需要后台配合各端一起实现,因为 该方式的使用时直接制定Host 端口号就可以,如果后台没使用这个框架, 前端没法玩。

    3.咨询了下熟悉这块的大佬的到回复如下:
    目前官方来说不支持flutter中Dio数据解析成protobuf,就连json的处理也是官方优化后,出了插件辅助开发使用的,不过这个思路和方案闲鱼官方有实现,也有一定的思路参考,你可以借鉴一下,或者找一下看看闲鱼的开源版本是否发布了.

    https://blog.csdn.net/yunqiinsight/article/details/86700217
    

    二、结果

    问题已经解决,在flutter中使用现有的Dio 3.x版本,完成了PB协议解析正确解析

    三、问题分析及处理过程(走了一些弯路,可以直接看步骤四)

    1.我首先想到的是在flutter中去搜索protobuf使用

    按大部分文档提到的在.yaml文件中增加protobuf: ^0.13.4依赖,我直接搜索了protobuf: ^0.13.4在Flutter中使用(最新版本已经到1.1.0)

    https://blog.csdn.net/importing/article/details/91565614
    

    找到一个比较类似的讲使用socket在flutter 中与服务器通信,但是里面的Msg类和我现在通过配置环境,生成的 .pb.dart文件差距比较大。我现在生成的文件如下,因为使用公司接口,只能贴部分示例代码,我放到最后面贴出来.从这个文章中,我get 到一个有效信息,Msg.fromBuffer(XXXX) ,但是这个XXX和我现在的差的比较多,于是放弃。

    2.从protobuf文档入手,既然官方文档已经支持Dart了,官网是否有使用实例
    https://developers.google.com/protocol-buffers/docs
    

    里面有个实例,但是他是从一个File中读写,与我期望的还是有差距,这里也get到部分思路,仍然要使用XXX.fromBuffer()方式 把字节流转为XXX对象,File的readAsBytesSync方法 返回的是一个Uint8List类型数据。
    Uint8List:Dart中无符号的Byte(类比java中的Byte)

    //使用如下方式 把字节流转为XXX对象
    XXX.formBuffer(File().readAsBytesSync());
    
    
    3.我现在就要知道使用Dio 库如何把返回的结果转换为Uint8List就完美了

    因为我们原生项目已经有可以验证测试的接口,我开始断点Android项目中的返回,发现返回的是一个Byte类型的数组,
    但是我现在使用Charles抓取Flutter 网络请求,发现数据确实有返回,但是是二进制流数据,我也看不到具体返回了什么内容。

    我开始了以下尝试:

    var response = await dio.post(url, data: params, options: options)
         
      PBRAdversityRsp pbdata = PBRAdversityRsp.fromBuffer(response);  
     PBRAdversityRsp pbdata = PBRAdversityRsp.fromBuffer(response.data);  
    
     PBRAdversityRsp pbdata = PBRAdversityRsp.fromJson(response);  
     PBRAdversityRsp pbdata = PBRAdversityRsp.fromJson(response.data);  
    
    

    上面就是使用Dio网络请求,返回的结果,response的类型是:Response<dynamic> ,我尝试了上面的四种方式,PBRAdversityRsp是我从.proto文件生成的对象文件,里面提供了fromBuffer 和fromJson方法 传入的参数无论是response还是 response.data 都会提示转换类型失败如下:

    
    
    4.出现上面的场景,免不了百度一堆类型转换的,再回过头对比现有的Dio返回Json数据的场景想想

    如果后台返回的是Json数据格式,可以使用json.encode(response.data)直接解析为对象 ,现在后台返回格式是PB协议,类似的应该也要用PB的一个方法解析现在的返回???但是不知道怎么用 。结合1.2的实例,我更加坚信了应该是用XXX.fromBuffer(Dio返回的数据)。但是现在要怎么把Response<dynamic> 或者String 类型转为Uint8List数据格式,同时我断点也看到这个Response 有部分中文,但是有部分特殊符号。

    �������.��
    ."操作成功*
    
    06619644572SHB-L0134517-94.10:
    
    第三个广告位 2b93a1178c424a01a9b304d8bdca5344Phttps://stg.iobs.pingan.com.cn/download/peimcadmin-sf-dev/160697497779818317.png"[Ghttps://paface-stg.pingan.com:10205/happy/login.html#/login?qrCode=true](Ghttps://paface-stg.pingan.com:10205/happy/login.html#/login?qrCode=true)
    
    
    5.然后又进入字节编码的坑中,折腾很久发现好像也解决不了问题
    6.然后进入了我最崩溃的一步:Dio官网Issue中#371:

    这个问题让我get到别人遇到过类似问题,虽然他是想传入参数,我是想返回解析。当时提问题时间大概是19年7月,他的代码写法也让我比较崩溃

    https://github.com/flutterchina/dio
    
    Uint8List response = await dio.post(url, data: params, options: options); 
    

    我这样写直接就会报错,而且他说道,跟踪代码发现dio转换器总是返回String格式,他虽然说的transferRequest方法, 我也看,发现他说的对,Dio 确实没有处理。我心里有点凉了,觉得就是Dio库没有兼容支持。我就对应看返回方法,代码比较多,我挑重点说

     @override
      Future transformResponse(
          RequestOptions options, ResponseBody response) async {
    ///重点1:
        if (options.responseType == ResponseType.stream) {
          return response;
        }
        ````
        var stream =
        response.stream.transform<Uint8List>(StreamTransformer.fromHandlers(
          handleData: (data, sink) {
            sink.add(data);
            if (showDownloadProgress) {
              received += data.length;
              options.onReceiveProgress(received, length);
            }
          },
        ));
        // let's keep references to the data chunks and concatenate them later
        final  chunks = <Uint8List>[];
        var finalSize = 0;
        StreamSubscription subscription = stream.listen(
              (chunk) {
            finalSize += chunk.length;
            chunks.add(chunk);
          },
        ```````
    ///重点2:
        if (options.responseType == ResponseType.bytes) return responseBytes;
    
        String responseBody;
        if (options.responseDecoder != null) {
          responseBody = options.responseDecoder(
              responseBytes, options, response..stream = null);
        } else {
          responseBody = utf8.decode(responseBytes, allowMalformed: true);
        }
        if (responseBody != null &&
            responseBody.isNotEmpty &&
            options.responseType == ResponseType.json &&
            _isJsonMime(response.headers[Headers.contentTypeHeader]?.first)) {
          if (jsonDecodeCallback != null) {
            return jsonDecodeCallback(responseBody);
          } else {
            return json.decode(responseBody);
          }
        }
        return responseBody;
    

    上面代码按照提Issue的人的说法,确实只支持了Json去处理返回,PB确实没在源码中。

    7.我又在某个论坛搜索到以下资料:
    目前官方来说不支持flutter中Dio数据解析成protobuf,就连json的处理也是官方优化后,出了插件辅助开发使用的,不过这个思路和方案闲鱼官方有实现,也有一定的思路参考,你可以借鉴一下,或者找一下看看闲鱼的开源版本是否发布了.
    

    上面的一系列,因为在2020.12.10这个时间节点,网上没有一篇讲Flutter 中使用Dio来解析PB协议的。我也差点多次放弃

    最后越想越气,打算看看Dio是不是可以像安卓一样,重写,反射不用他的,实现这个PB解析,然后一步一步断点看流程,

    四、问题解决

    1.首先确定网络请求的头中 有content-type = protobuf

    我对比了下原生之所以能请求到,和我Dart中的小区别点事它的请求头中有指定类型,于是我在我的flutter 中也加入了
    dio的options中指定了,期望能返回和PB格式相关的类型,不要Response,再次失败。

    2.注意上面代码中的 重点1 重点2:

    有个类型,网络传输回来 首先拿到的就是流数据,只是Dio最后返回了Response ,我看到重点1处代码,直接在Dio请求的option中指定了stream类型,断点 再看异步请求后的返回数据,类型不再是Response类型,在重点2处,有个responseBytes类型,这个就是我想要的。

     Dio dio = new Dio();                                                           
     Options options = Options(headers: {                                           
       HttpHeaders.acceptHeader: "*",                                               
       HttpHeaders.contentTypeHeader:"application/x-protobuf",                      
       HttpHeaders.cookieHeader:"PAMO_SESSION=42fa969316d6481b818b1f17139dcebc_v2", 
     },                                                                             
       responseType: ResponseType.bytes,                                            
     );                                                                             
    

    可以看到现在返回的response是一个数组,里面放的int类型的值。本以为大功告成,最后打印发现我数据全是空的,我理解是数据解析失败了


    屏幕快照 2020-12-11 18.10.07.png
    3.又一个弯路:对比原生

    原生能解析,Flutter不能解析,拿到的response.data的数据是273个,和原生的数据数据量一致,然后我断点看了具体发现原生很多负数,因为是Byte解析的,但是Flutter中拿到的是int ,会不会就是这个问题导致解析失败???我开始研究怎么转换,说来也怪,折腾了好久才找到以下方式直接就可以转换:

    Int8List.fromList(pbResultResponse.data)
    

    让人绝望的是还是拿不到解析后的数据

    4.对比原生代码,发现原生代码还有一个基础类,这个是和后台定义的,首先数据要解析为这个基础,再从基础中取data字段,再转换为我们新生成的.pb .dart文件。终于解析成功。
    原始的 .proto文件

    这个文件一般是 后台及各端一起定义好,比较简单的:

    syntax = "proto3";
    option java_package = "XXX.pb.smartCard";
    option java_outer_classname = "PBAdversitVO";
    option java_multiple_files = true;
    
    
    message PBAdversityReq{
       int64 timestamp =1;//时间戳
    }
    
    
    //智能工卡广告位
    message PBAdversityItem{
        string adversityName = 1;//广告名称
        string adversityId = 2;//广告ID
        string bannerUrl = 3;//banner图
        string termUrl = 4;//入口连接
    }
    
    //返回结果数据
    message PBRAdversityRsp{
        int64 code          = 1; // 状态码
        string message      = 2; // 返回信息、错误提示等
        repeated PBAdversityItem    adversityList     = 3; // 返回的广告list
    }
    
    
    .proto文件转为.pb.dart文件

    转换成.pb.dart文件后,这个文件变得比较复杂,但是结构还是比较清晰:

    ///
    //  Generated code. Do not modify.
    //  source: Adversity.proto
    //
    // @dart = 2.3
    // ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
    
    import 'dart:core' as $core;
    
    import 'package:fixnum/fixnum.dart' as $fixnum;
    import 'package:protobuf/protobuf.dart' as $pb;
    
    class PBAdversityReq extends $pb.GeneratedMessage {
      static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'PBAdversityReq', createEmptyInstance: create)
        ..aInt64(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'timestamp')
        ..hasRequiredFields = false
      ;
    
      PBAdversityReq._() : super();
      factory PBAdversityReq() => create();
      factory PBAdversityReq.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
      factory PBAdversityReq.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
      @$core.Deprecated(
      'Using this can add significant overhead to your binary. '
      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
      'Will be removed in next major version')
      PBAdversityReq clone() => PBAdversityReq()..mergeFromMessage(this);
      @$core.Deprecated(
      'Using this can add significant overhead to your binary. '
      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
      'Will be removed in next major version')
      PBAdversityReq copyWith(void Function(PBAdversityReq) updates) => super.copyWith((message) => updates(message as PBAdversityReq)); // ignore: deprecated_member_use
      $pb.BuilderInfo get info_ => _i;
      @$core.pragma('dart2js:noInline')
      static PBAdversityReq create() => PBAdversityReq._();
      PBAdversityReq createEmptyInstance() => create();
      static $pb.PbList<PBAdversityReq> createRepeated() => $pb.PbList<PBAdversityReq>();
      @$core.pragma('dart2js:noInline')
      static PBAdversityReq getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PBAdversityReq>(create);
      static PBAdversityReq _defaultInstance;
    
      @$pb.TagNumber(1)
      $fixnum.Int64 get timestamp => $_getI64(0);
      @$pb.TagNumber(1)
      set timestamp($fixnum.Int64 v) { $_setInt64(0, v); }
      @$pb.TagNumber(1)
      $core.bool hasTimestamp() => $_has(0);
      @$pb.TagNumber(1)
      void clearTimestamp() => clearField(1);
    }
    
    class PBAdversityItem extends $pb.GeneratedMessage {
      static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'PBAdversityItem', createEmptyInstance: create)
        ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'adversityName', protoName: 'adversityName')
        ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'adversityId', protoName: 'adversityId')
        ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'bannerUrl', protoName: 'bannerUrl')
        ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'termUrl', protoName: 'termUrl')
        ..hasRequiredFields = false
      ;
    
      PBAdversityItem._() : super();
      factory PBAdversityItem() => create();
      factory PBAdversityItem.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
      factory PBAdversityItem.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
      @$core.Deprecated(
      'Using this can add significant overhead to your binary. '
      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
      'Will be removed in next major version')
      PBAdversityItem clone() => PBAdversityItem()..mergeFromMessage(this);
      @$core.Deprecated(
      'Using this can add significant overhead to your binary. '
      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
      'Will be removed in next major version')
      PBAdversityItem copyWith(void Function(PBAdversityItem) updates) => super.copyWith((message) => updates(message as PBAdversityItem)); // ignore: deprecated_member_use
      $pb.BuilderInfo get info_ => _i;
      @$core.pragma('dart2js:noInline')
      static PBAdversityItem create() => PBAdversityItem._();
      PBAdversityItem createEmptyInstance() => create();
      static $pb.PbList<PBAdversityItem> createRepeated() => $pb.PbList<PBAdversityItem>();
      @$core.pragma('dart2js:noInline')
      static PBAdversityItem getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PBAdversityItem>(create);
      static PBAdversityItem _defaultInstance;
    
      @$pb.TagNumber(1)
      $core.String get adversityName => $_getSZ(0);
      @$pb.TagNumber(1)
      set adversityName($core.String v) { $_setString(0, v); }
      @$pb.TagNumber(1)
      $core.bool hasAdversityName() => $_has(0);
      @$pb.TagNumber(1)
      void clearAdversityName() => clearField(1);
    
      @$pb.TagNumber(2)
      $core.String get adversityId => $_getSZ(1);
      @$pb.TagNumber(2)
      set adversityId($core.String v) { $_setString(1, v); }
      @$pb.TagNumber(2)
      $core.bool hasAdversityId() => $_has(1);
      @$pb.TagNumber(2)
      void clearAdversityId() => clearField(2);
    
      @$pb.TagNumber(3)
      $core.String get bannerUrl => $_getSZ(2);
      @$pb.TagNumber(3)
      set bannerUrl($core.String v) { $_setString(2, v); }
      @$pb.TagNumber(3)
      $core.bool hasBannerUrl() => $_has(2);
      @$pb.TagNumber(3)
      void clearBannerUrl() => clearField(3);
    
      @$pb.TagNumber(4)
      $core.String get termUrl => $_getSZ(3);
      @$pb.TagNumber(4)
      set termUrl($core.String v) { $_setString(3, v); }
      @$pb.TagNumber(4)
      $core.bool hasTermUrl() => $_has(3);
      @$pb.TagNumber(4)
      void clearTermUrl() => clearField(4);
    }
    
    class PBRAdversityRsp extends $pb.GeneratedMessage {
      static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'PBRAdversityRsp', createEmptyInstance: create)
        ..aInt64(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'code')
        ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'message')
        ..pc<PBAdversityItem>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'adversityList', $pb.PbFieldType.PM, protoName: 'adversityList', subBuilder: PBAdversityItem.create)
        ..hasRequiredFields = false
      ;
    
      PBRAdversityRsp._() : super();
      factory PBRAdversityRsp() => create();
      factory PBRAdversityRsp.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
      factory PBRAdversityRsp.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
      @$core.Deprecated(
      'Using this can add significant overhead to your binary. '
      'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
      'Will be removed in next major version')
      PBRAdversityRsp clone() => PBRAdversityRsp()..mergeFromMessage(this);
      @$core.Deprecated(
      'Using this can add significant overhead to your binary. '
      'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
      'Will be removed in next major version')
      PBRAdversityRsp copyWith(void Function(PBRAdversityRsp) updates) => super.copyWith((message) => updates(message as PBRAdversityRsp)); // ignore: deprecated_member_use
      $pb.BuilderInfo get info_ => _i;
      @$core.pragma('dart2js:noInline')
      static PBRAdversityRsp create() => PBRAdversityRsp._();
      PBRAdversityRsp createEmptyInstance() => create();
      static $pb.PbList<PBRAdversityRsp> createRepeated() => $pb.PbList<PBRAdversityRsp>();
      @$core.pragma('dart2js:noInline')
      static PBRAdversityRsp getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PBRAdversityRsp>(create);
      static PBRAdversityRsp _defaultInstance;
    
      @$pb.TagNumber(1)
      $fixnum.Int64 get code => $_getI64(0);
      @$pb.TagNumber(1)
      set code($fixnum.Int64 v) { $_setInt64(0, v); }
      @$pb.TagNumber(1)
      $core.bool hasCode() => $_has(0);
      @$pb.TagNumber(1)
      void clearCode() => clearField(1);
    
      @$pb.TagNumber(2)
      $core.String get message => $_getSZ(1);
      @$pb.TagNumber(2)
      set message($core.String v) { $_setString(1, v); }
      @$pb.TagNumber(2)
      $core.bool hasMessage() => $_has(1);
      @$pb.TagNumber(2)
      void clearMessage() => clearField(2);
    
      @$pb.TagNumber(3)
      $core.List<PBAdversityItem> get adversityList => $_getList(2);
    }
    
    

    我现在要接受到网络请求返回的数据 需要转为PBRAdversityRsp对象来接受!

    https://github.com/flutterchina/dio/issues/371

    相关文章

      网友评论

        本文标题:Flutter中使用Dio网络请求如何解析protobuf协议格

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