详解Android中使用Protocol Buffer

任何新事物的出现总有它的原因。为什么出现?能解决什么问题?怎么解决的·? 这是我们首先会问的问题。

(一 )什么是Protocol Buffer?

为什么有了XML, Json, 还需要Protocol Buffer?

XML可读性虽然最好,但是体积过于冗长,导致在解析和传输时,性能不够好。后来有了Json,在保持可读性的同时,改善了大小。但是在有些场合,json依旧太大,无法满足传输解析的需求,所以又有了Protobuffer。

优点:

1. Protobuffer的key可以简单认为是1,2,3,4这种数字,那肯定比字符串式的key要短的太多了。所以体积小,有利于传输和存储。

2. Protobuffer的解析是通过位运算。所以解析性能非常快。

缺点:

1. 体积小,解析性能好的代价,就是彻底牺牲了可读性, 对开发者不友好。

 

适用场景:

字节流模式的通信。 比如直接Socket通信的,那最适合使用。

 

(二)如何使用Protocol Buffer

在android中使用。

https://github.com/google/protobuf-gradle-plugin 有详细介绍。很多网上博客文章介绍的都已经过时了。本文按当下最新版本介绍。你在使用的时候,和官方里写的使用说明再合对一下,尤其是版本号。不然会有编译错误,曾吃过苦头。

到如今,Android Studio已经以插件的方式集成了proto buffer的编译工具。只要rebuild工程后,就会自动生成java文件放在debug目录下。也可以指定目录。

具体有下面几个地方需要修改:

在Moduld App 的gradle里添加

apply plugin: 'com.google.protobuf' //添加插件
protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.8.0'
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option "lite"
                }
            }
        }
    }
}
implementation 'com.google.protobuf:protobuf-lite:3.0.0' //注意是lite. 是轻量级的protobuf。 是为了解决方法数过多的问题,比如把get/set方法删除了,直接用public.

详解Android中使用Protocol Buffer_第1张图片

在工程gradle里添加:

classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.11'

这里记得都用最新版本号,不然会有很多莫名其妙的编译错误。版本号从上面那个链接里查看。

详解Android中使用Protocol Buffer_第2张图片
 

最后,在main文件夹下面创建一个proto文件夹,把proto文件放里面。系统默认会取这个里面的proto文件进行编译。或设置sourceset, 改变默认路径。

 

下面生成的java文件路径:

详解Android中使用Protocol Buffer_第3张图片

(三) Protocol Buffer原理

 

Protocol Buffer是字节流模式的。所以就是byte[]数组。 

简单的理解就是,把类序列化的结果就是一个byte[]数组, 而把类反序列化的过程就一个byte[]数组变成一个类实例。所以socket通信是不是特别合适呢?!

编码机制:

编码方式是: Base 128 varints 

https://blog.csdn.net/hellochenlu/article/details/51395988

详解Android中使用Protocol Buffer_第4张图片

详解Android中使用Protocol Buffer_第5张图片

消息结构:

Key-Value键值对组成。

详解Android中使用Protocol Buffer_第6张图片

详解Android中使用Protocol Buffer_第7张图片

详解Android中使用Protocol Buffer_第8张图片

详解Android中使用Protocol Buffer_第9张图片

有这么一个message体

message Person {
     int32 id = 1;
     string name = 2;
}

 int32 id = 1; "1"被称为tag, 参与Key的构建。同样类型int32也参与Key的构建。

key是这样表示的。int32 type | 1, 用5位表示key, 还有3位表示value。 所以这里有一个问题,当message体参数成员超过15后,用两个字节存储key。 自己在用的时候message体的参数个数尽量别超过16个,可以节省空间。

person序列化后在byte[]数组是这样的: int32 type | 1 value string | 2 length value

多个参数一个一个串在一起。String 有点特殊,中间还要加一个长度。

使用指南:

详解Android中使用Protocol Buffer_第10张图片

有三种版本的语法。目前最新版是syntax = "proto3",更加简洁,并且性能更好。更多细节参考官方文档。

syntax = "proto2";

package tutorial;

option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}
syntax = "proto3";

option java_package = "com.nero.testprotobuffer";
option java_outer_classname = "AddressBookProto";

message Person {
     string name = 1;
     int32 id = 2;
     string email = 3;

    enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
    }

    message PhoneNumber {
        string number = 1;
        PhoneType type = 2;
    }

    repeated PhoneNumber phones = 4;
}

message AddressBook {
    repeated Person people = 1;
}

编译后正式使用:

AddressBookProto.Person.Builder personBuilder = AddressBookProto.Person.newBuilder();
personBuilder.setName(“alan”);
personBuilder.setId(1);

AddressBookProto.Person person = personBuilder.build();



Byte[] personStreamData = person.toByteArray();


 AddressBookProto.Person person2 = AddressBookProto.Person.parseFrom(personStreamData)

 

你可能感兴趣的:(详解Android中使用Protocol Buffer)