Protocol Buffer For Android

什么是protocal buffer?

protocal buffer 以下简称protobuf是google 的一种数据交换的格式,它独立于语言,独立于平台。(作用类似json、xml等,但是更安全,更快)
简要说明一下流程:

文章分两个部分 我先讲讲protobuf的语法规则与介绍,再讲讲如何使用(已经使用在项目中,如不想了解介绍可以直接跳到使用部分,一般.proto文件服务端会提供好)

介绍

如果你用 android studio 在plugin中安装了 protobuf android插件。那么android studio 将可以识别.proto文件(.proto文件就是一种描述文件)
效果如下图所示:


Paste_Image.png

syntax :是指定编译的格式、我们可以使用 “proto2” “proto3” 具体区别我也没有深究 默认使用“proto2”
packge 指定生成的java文件所在包
option java_package 指定生成的java文件所在的完整包名

  • message是消息定义的关键字,等同于C++中的struct/class,或是Java中的class。
  • Request为消息的名字,等同于结构体名或类名。
  • required前缀表示该字段为必要字段,既在序列化和反序列化之前该字段必须已经被赋值。与此同时,在Protocol Buffer中还存在另外两个类似的关键字,optionalrepeated,带有这两种限定符的消息字段则没有required字段这样的限制。相比于optionalrepeated主要用于表示数组字段。具体的使用方式在后面的用例中均会一一列出。
  • int64string分别表示长整型和字符串型的消息字段,在Protocol Buffer中存在一张类型对照表,既Protocol Buffer中的数据类型与其他编程语言(C++/Java)中所用类型的对照。该对照表中还将给出在不同的数据场景下,哪种类型更为高效。该对照表将在后面给出。
  • tokensign分别表示消息字段名,等同于Java中的域变量名,或是C++中的成员变量名。
  • 标签数字12则表示不同的字段在序列化后的二进制数据中的布局位置。在该例中,sign字段编码后的数据一定位于token之后。需要注意的是该值在同一message中不能重复。另外,对于Protocol Buffer而言,标签值为1到15的字段在编码时可以得到优化,既标签值和类型信息仅占有一个byte,标签范围是16到2047的将占有两个bytes,而Protocol Buffer可以支持的字段数量则为2的29次方减一。有鉴于此,我们在设计消息结构时,可以尽可能考虑让repeated类型的字段标签位于1到15之间,这样便可以有效的节省编码后的字节数量。

定义一个含有枚举字段
Protocol Buffer消息。

//在定义Protocol Buffer的消息时,可以使用和C++/Java代码同样的方式添加注释。

enum UserStatus {
  OFFLINE = 0;  //表示处于离线状态的用户     
  ONLINE = 1;   //表示处于在线状态的用户    
 }
message UserInfo {         
  required int64 acctID = 1;
  required string name = 2; 
  required UserStatus status = 3;
}

这里将给出以上消息定义的关键性说明(仅包括上一小节中没有描述的)。

  • enum是枚举类型定义的关键字,等同于C++/Java中的enum。
  1. UserStatus为枚举的名字。
  2. 和C++/Java中的枚举不同的是,枚举值之间的分隔符是分号,而不是逗号。
  3. OFFLINE/ONLINE为枚举值。
  4. 0和1表示枚举值所对应的实际整型值,和C/C++一样,可以为枚举值指定任意整型值,而无需总是从0开始定义。如
enum OperationCode {
    LOGON_REQ_CODE = 101;
    LOGOUT_REQ_CODE = 102;
    RETRIEVE_BUDDIES_REQ_CODE = 103;
    LOGON_RESP_CODE = 1001;
    LOGOUT_RESP_CODE = 1002;
    RETRIEVE_BUDDIES_RESP_CODE = 1003;
}

定义含有嵌套消息字段的Protocol Buffer消息。
我们可以在同一个.proto文件中定义多个message,这样便可以很容易的实现嵌套消息的定义。如:

enum UserStatus {
    OFFLINE = 0;  
    ONLINE = 1;
}
message UserInfo {
    required int64 acctID = 1;
    required string name = 2;**   
    required UserStatus status = 3;**   
   }
message LogonRespMessage {**     
    required LoginResult logonResult = 1;**       
    required UserInfo userInfo = 2;** 
}

这里将给出以上消息定义的关键性说明(仅包括上两小节中没有描述的)。

  • LogonRespMessage消息的定义中包含另外一个消息类型作为其字段,如UserInfo userInfo。
  1. 上例中的UserInfo和LogonRespMessage被定义在同一个.proto文件中,那么我们是否可以包含在其他.proto文件中定义的message呢?Protocol Buffer提供了另外一个关键字import,这样我们便可以将很多通用的message定义在同一个.proto文件中,而其他消息定义文件可以通过import的方式将该文件中定义的消息包含进来,如:
    import "myproject/CommonMessages.proto"**

限定符(required/optional/repeated)的基本规则

  • 在每个消息中必须至少留有一个required类型的字段。
  1. 每个消息中可以包含0个或多个optional类型的字段。
  2. repeated表示的字段可以包含0个或多个数据。需要说明的是,这一点有别于C++/Java中的数组,因为后两者中的数组必须包含至少一个元素。
  3. 如果打算在原有消息协议中添加新的字段,同时还要保证老版本的程序能够正常读取或写入,那么对于新添加的字段必须是optional或repeated。道理非常简单,老版本程序无法读取或写入新增的required限定符的字段。

** Protocol Buffer消息升级原则。**

在实际的开发中会存在这样一种应用场景,既消息格式因为某些需求的变化而不得不进行必要的升级,但是有些使用原有消息格式的应用程序暂时又不能被立刻升级,这便要求我们在升级消息格式时要遵守一定的规则,从而可以保证基于新老消息格式的新老程序同时运行。规则如下:

  • 不要修改已经存在字段的标签号。
  1. 任何新添加的字段必须是optional和repeated限定符,否则无法保证新老程序在互相传递消息时的消息兼容性。
  2. 在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用。
  3. int32、uint32、int64、uint64和bool等类型之间是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着如果想修改原有字段的类型时,为了保证兼容性,只能将其修改为与其原有类型兼容的类型,否则就将打破新老消息格式的兼容性。
  4. optional和repeated限定符也是相互兼容的。

Packages

我们可以在.proto文件中定义包名,如: package ourproject.lyphone; 该包名在生成对应的C++文件时,将被替换为名字空间名称,既namespace ourproject { namespace lyphone。而在生成的Java代码文件中将

Options。

Protocol Buffer允许我们在.proto文件中定义一些常用的选项,这样可以指示Protocol Buffer编译器帮助我们生成更为匹配的目标语言代码。Protocol Buffer内置的选项被分为以下三个级别:

  1. 文件级别,这样的选项将影响当前文件中定义的所有消息和枚举。
  2. 消息级别,这样的选项仅影响某个消息及其包含的所有字段。
  3. 字段级别,这样的选项仅仅响应与其相关的字段。 下面将给出一些常用的Protocol Buffer选项。
  4. option java_package = "com.companyname.projectname"; java_package是文件级别的选项,通过指定该选项可以让生成Java代码的包名为该选项值,如上例中的Java代码包名为com.companyname.projectname。与此同时,生成的Java文件也将会自动存放到指定输出目录下的com/companyname/projectname子目录中。如果没有指定该选项,Java的包名则为package关键字指定的名称。该选项对于生成C++代码毫无影响。
  5. option java_outer_classname = "LYPhoneMessage"; java_outer_classname是文件级别的选项,主要功能是显示的指定生成Java代码的外部类名称。如果没有指定该选项,Java代码的外部类名称为当前文件的文件名部分,同时还要将文件名转换为驼峰格式,如:my_project.proto,那么该文件的默认外部类名称将为MyProject。该选项对于生成C++代码毫无影响。 注:主要是因为Java中要求同一个.java文件中只能包含一个Java外部类或外部接口,而C++则不存在此限制。因此在.proto文件中定义的消息均为指定外部类的内部类,这样才能将这些消息生成到同一个Java文件中。在实际的使用中,为了避免总是输入该外部类限定符,可以将该外部类静态引入到当前Java文件中,如:import static com.company.project.LYPhoneMessage.*。
  6. option optimize_for = LITE_RUNTIME; optimize_for是文件级别的选项,Protocol Buffer定义三种优化级别SPEED/CODE_SIZE/LITE_RUNTIME。缺省情况下是SPEED。 SPEED: 表示生成的代码运行效率高,但是由此生成的代码编译后会占用更多的空间。 CODE_SIZE: 和SPEED恰恰相反,代码运行效率较低,但是由此生成的代码编译后会占用更少的空间,通常用于资源有限的平台,如Mobile。 LITE_RUNTIME: 生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常少。这是以牺牲Protocol Buffer提供的反射功能为代价的。因此我们在C++中链接Protocol Buffer库时仅需链接libprotobuf-lite,而非libprotobuf。在Java中仅需包含protobuf-java-2.4.1-lite.jar,而非protobuf-java-2.4.1.jar。 注:对于LITE_MESSAGE选项而言,其生成的代码均将继承自MessageLite,而非Message。
  7. [pack = true]: 因为历史原因,对于数值型的repeated字段,如int32、int64等,在编码时并没有得到很好的优化,然而在新近版本的Protocol Buffer中,可通过添加[pack=true]的字段选项,以通知Protocol Buffer在为该类型的消息对象编码时更加高效。如: repeated int32 samples = 4 [packed=true]。 注:该选项仅适用于2.3.0以上的Protocol Buffer。
  8. [default = default_value]: optional类型的字段,如果在序列化时没有被设置,或者是老版本的消息中根本不存在该字段,那么在反序列化该类型的消息是,optional的字段将被赋予类型相关的缺省值,如bool被设置为false,int32被设置为0。Protocol Buffer也支持自定义的缺省值,如: optional int32 result_per_page = 3 [default = 10]。

类型对照表

proto Type Notes C++ Type Java Type
double double double
float float float
int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. int32 int
int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. int64 long
uint32 Uses variable-length encoding. uint32 int
uint64 Uses variable-length encoding. uint64 long
sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. int32 int
sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. int64 long
fixed32 Always four bytes. More efficient than uint32 if values are often greater than 228. uint32 int
fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 256. uint64 long
sfixed32 Always four bytes. int32 int
sfixed64 Always eight bytes. int64 long
bool bool boolean
string A string must always contain UTF-8 encoded or 7-bit ASCII text. string String
bytes May contain any arbitrary sequence of bytes. string ByteString

使用

为了方便大家使用,我将下面用到的文件都放到了csdn上面供大家下载(mac和windows的)
http://download.csdn.net/detail/qq_22605283/9722829

  • 生成java文件(普通版)

1.在git上或者下面谷歌的官网链接下载根据平台下载protoc的可运行文件,然后把 .proto文件放到同一个目录下(或者从上面链接下载)
2 cd 到该目录下用terminal 运行以下代码:
protoc --java_out ./ ./.proto
protoc:表示利用刚才下载的可运行程序打包
--java_out :是输出指令 第一个从参数是生成的java目录,第二个参数是指定编 译的.proto文件 ./
.proto表示当前目录下所有.proto文件
3把java文件拷贝到项目上
4 在项目的build.gradle的dependencies节点下加入代码:
compile 'com.google.protobuf:protobuf-java:3.0.0'
这行代码是引用android 使用上面生成的java代码需要的jar包

该方法生成的java文件方法会比较多,文件也比较大,会占用许多空间,所以当运用到实际项目的时候会采用轻量版

  • 生成java文件(lite版)

1.在git上或者下面谷歌的官网链接下载根据平台下载protoc的可运行文件,然后把 .proto文件放到同一个目录下(或者从上面链接下载)
2.在git上面下载proto-gen-javalite可运行文件放在上面的目录下
2 cd 到该目录下用terminal 运行以下代码:
protoc --javalite_out ./ ./*.proto
3把java文件拷贝到项目上
4 在项目的build.gradle的dependencies节点下加入代码:
compile 'com.google.protobuf:protobuf-lite:3.0.1'

注意有区别哦
还有你会发现虽然方法数少了,size也少了,可是这个java文件依旧占了很大的size。不要急,打开后你会发现其实有很大一部分的代码都是注释,所以当文件打包的时候或者混淆的时候实际会变得很小。举个实例:我的项目中生成的java文件将近700k,混淆完最后打包只占用了40k的大小。

  • 使用java代码

例如 我生成了一个Pb.java文件 ,如果原本的.proto带有一个message Request,那么这个Pb.java文件中就会有一个Request的内部类。 看例子代码:

    String appIdUTF8 = "";
    try {//注意格式转码
        appIdUTF8 = URLEncoder.encode("你好啊", "UTF-8");        
    } catch (UnsupportedEncodingException e) {
        Log.e(TAG, "err: " + e.getMessage());
    }
    //实例化一个PB对象
    Pb.Request request = Pb.Request.newBuilder().setId(appIdUTF8).set***.builder
    byte[] data = rq.toByteArray();//将PB对象转换成二进制流
    String stream =new String(data);//讲PB对象转换成String
...//解析 从网络上或者数据 比如我们已经连接上一个HttpURLConnection conn
    //可以被解析的参数有很多种,可以查看下面的图
    Pb.Request  rq =Pb.Request.parseFrom(conn.getInputStream())
    rq.getSid();//获得Pb对象的sid属性
转换格式

好了到这里已经介绍完了,当我使用pb时,都是服务器给定的.proto文件,所以不会动态的去生成java文件,一次生成一直使用,如果你的项目有需求动态更新.proto文件的时候并且生成新的java代码 请参考这个帖子:
http://www.tuicool.com/articles/ruIFvif

资料参考
https://developers.google.com/protocol-buffers/
http://www.cnblogs.com/stephen-liu74/archive/2013/01/02/2841485.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容