android|Protobuf的那些事

当大多数公司还在纠结于如何更好提高MobileAPI的性能时,有的公司已经开始抛弃http + json,开始走向tcp + protobuf的路线了。
那么什么是protobuf呢:protobuf是一种基于二进制的协议,它能够非常快速高效的序列化数据,考虑之前的xml,但是它的体积要比xml小的多得多。当然使用它还有其他非常之多的理由。我们考虑java的情况,传统的序列化方法就是实现Serialization,不过它带来非常之多的烦恼,比如版本兼容,这会带来非常之大的测试压力,且它的处理完的数据并不能跨语言的分享啊,python or c++都不能够使用,这是很头疼的。除此之外,我们序列化数据的时候我们会使用一些ad-hoc way,比如会把数据都放在一个字符串里面,考虑有四个整形数据的例子:"12:3:-23:67",它显然是足够简单的,但是却需要手写一些解析,编码代码,并且,还有一些运行时损耗。再后者,我们可能会想到使用xml,显然它是不太好的,不然也就没有json了。
综合以上,便是protobuf诞生的理由,现在我们要开始正式学习它了。


在使用之前,我们需要安装它的库,编译器啥的,转到github :https://github.com/google/protobuf,按照readme里面下载安装就行了,之后安装Java的版本,如果一切都正常的话 在终端里面输入protoc --version会看到当前protoc编译器的版本号。


【android|Protobuf的那些事】那再用java来使用protobuf的时候,我们需要三个步骤:
1:定义.proto文件,用来定义你的数据结构
2:使用protoc编译器进行编译,导出java代码

3:使用java api进行数据的读写


我这里考虑创建一个 AddressBook的例子

在这个类中我们存放联系簿,练习簿中有你记录的联系人以及他的个人信息,包括手机号码,姓名啥的
我们先粗略的看下proto文件内容:


android|Protobuf的那些事
文章图片








是不是似曾相识的感觉,他和c++,java代码还是很像的。我们逐行分析代码
第一行是指定版本号,如果没有,大概编译器会报如下错误:
[libprotobuf WARNING google/protobuf/compiler/parser.cc:547] No syntax specified for the proto file. Please use 'syntax = "proto2"; ' or 'syntax = "proto3"; ' to specify a syntax version. (Defaulted to proto2 syntax.
注意,它必须放在第一行哦,在下面是包名,他是用来防止Protocol Buffers name space里的名字冲突,所以还是不省略为好,再往下是生成java文件后的包名,很显然,第五行的便是类名了
如果你真的懒得写这些的话 关于他们缺省值你可以参考官网给出的解释:
android|Protobuf的那些事
文章图片


再往下,你可以看到很多关于message的定义,他是用来存放数据域的,数据域有自己的类型,protobuf支持的类型有bool, int32, float, double, and string,包括自定义类型(也为message)等。比如Person有自己的名字,id,email,和联系电话,观之name,是string类型的,前面还有 required修饰符,表示这个域必须有,后面还有 = 1,这个符号,这是用来在二进制编码里面唯一确认当前的域的,值得注意的是1-15要求更少得存储空间去存储这个标记数字,也就是说,如果你某些域用的很频繁,应该首先分配这些数字,这也是作为优化的一种方式。
往下面读,可以看到email是optional,也就是可选的,这个域可有可无,如果没有设置的话,系统会自动填充一个合理地值,比如对于bool 你可以是false,字符串给定一个空值,所以,如果你对编译器没有信心,你可以自己给定一个默认的值,考虑第二十行的做法
对于PhoneType,他是一个枚举类型,枚举类型里面也要给定标记数字(Tag number)
通常情况下,一个人不可能只拥有一个号码,所以phone应该是一个集合,这个集合可以是变化的,可为空,你可以想象为一个动态变化的数组,所以它的修饰词是repeated
总结下:
一个域由以下部分组成:
修饰词类型域名= 标记值
到目前为止,所有的基础内容已经介绍完,然后我们保存下文件,后缀名为.proto


切换到命令行模式:输入

protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto


-I指定源目的文件夹,一般是你的应用源码位置
--java_out 是生成代码的目的地 一般和src dir相同
后面一个参数是proto文件的位置


具体的例子:
protoc -I=./ --java_out=./ ./first.proto



便可以在你指定的目标文件夹下面看到生成的Java文件了 :)



The Protocol Buffer API
我们看下Person类:
// required string name = 1; public boolean hasName(); public String getName(); // required int32 id = 2; public boolean hasId(); public int getId(); // optional string email = 3; public boolean hasEmail(); public String getEmail(); // repeated .tutorial.Person.PhoneNumber phone = 4; public List getPhoneList(); public int getPhoneCount(); public PhoneNumber getPhone(int index);


同时 Person.Builder:
// required string name = 1; public boolean hasName(); public java.lang.String getName(); public Builder setName(String value); public Builder clearName(); // required int32 id = 2; public boolean hasId(); public int getId(); public Builder setId(int value); public Builder clearId(); // optional string email = 3; public boolean hasEmail(); public String getEmail(); public Builder setEmail(String value); public Builder clearEmail(); // repeated .tutorial.Person.PhoneNumber phone = 4; public List getPhoneList(); public int getPhoneCount(); public PhoneNumber getPhone(int index); public Builder setPhone(int index, PhoneNumber value); public Builder addPhone(PhoneNumber value); public Builder addAllPhone(Iterable value); public Builder clearPhone();


可以看到,Builder比Person有更“强的”方法。 每个 clear方法用于重置域到它的初始状态
重复域有count方法,用于返回这个域的大小



Enums and Nested Classes:

在Person中,我们定义了一个枚举类型,下面是枚举类型的Java代码:

public static enum PhoneType { MOBILE(0, 0), HOME(1, 1), WORK(2, 2), ; ... }



Builders vs. Messages:

builder使用方式:

Person john = Person.newBuilder() .setId(1234) .setName("John Doe") .setEmail("jdoe@example.com") .addPhone( Person.PhoneNumber.newBuilder() .setNumber("555-4321") .setType(Person.PhoneType.HOME)) .build();





Message使用方式: WRITE:

import com.example.tutorial.AddressBookProtos.AddressBook; import com.example.tutorial.AddressBookProtos.Person; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.PrintStream; class AddPerson { // This function fills in a Person message based on user input. static Person PromptForAddress(BufferedReader stdin, PrintStream stdout) throws IOException { Person.Builder person = Person.newBuilder(); stdout.print("Enter person ID: "); person.setId(Integer.valueOf(stdin.readLine())); stdout.print("Enter name: "); person.setName(stdin.readLine()); stdout.print("Enter email address (blank for none): "); String email = stdin.readLine(); if (email.length() > 0) { person.setEmail(email); }while (true) { stdout.print("Enter a phone number (or leave blank to finish): "); String number = stdin.readLine(); if (number.length() == 0) { break; }Person.PhoneNumber.Builder phoneNumber = Person.PhoneNumber.newBuilder().setNumber(number); stdout.print("Is this a mobile, home, or work phone? "); String type = stdin.readLine(); if (type.equals("mobile")) { phoneNumber.setType(Person.PhoneType.MOBILE); } else if (type.equals("home")) { phoneNumber.setType(Person.PhoneType.HOME); } else if (type.equals("work")) { phoneNumber.setType(Person.PhoneType.WORK); } else { stdout.println("Unknown phone type.Using default."); }person.addPhone(phoneNumber); }return person.build(); }// Main function:Reads the entire address book from a file, //adds one person based on user input, then writes it back out to the same //file. public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage:AddPerson ADDRESS_BOOK_FILE"); System.exit(-1); }AddressBook.Builder addressBook = AddressBook.newBuilder(); // Read the existing address book. try { addressBook.mergeFrom(new FileInputStream(args[0])); } catch (FileNotFoundException e) { System.out.println(args[0] + ": File not found.Creating a new file."); }// Add an address. addressBook.addPerson( PromptForAddress(new BufferedReader(new InputStreamReader(System.in)), System.out)); // Write the new address book back to disk. FileOutputStream output = new FileOutputStream(args[0]); addressBook.build().writeTo(output); output.close(); } }



READ:

import com.example.tutorial.AddressBookProtos.AddressBook; import com.example.tutorial.AddressBookProtos.Person; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintStream; class ListPeople { // Iterates though all people in the AddressBook and prints info about them. static void Print(AddressBook addressBook) { for (Person person: addressBook.getPersonList()) { System.out.println("Person ID: " + person.getId()); System.out.println("Name: " + person.getName()); if (person.hasEmail()) { System.out.println("E-mail address: " + person.getEmail()); }for (Person.PhoneNumber phoneNumber : person.getPhoneList()) { switch (phoneNumber.getType()) { case MOBILE: System.out.print("Mobile phone #: "); break; case HOME: System.out.print("Home phone #: "); break; case WORK: System.out.print("Work phone #: "); break; } System.out.println(phoneNumber.getNumber()); } } }// Main function:Reads the entire address book from a file and prints all //the information inside. public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage:ListPeople ADDRESS_BOOK_FILE"); System.exit(-1); }// Read the existing address book. AddressBook addressBook = AddressBook.parseFrom(new FileInputStream(args[0])); Print(addressBook); } }


Extending a Protocol Buffer: 如果以后你想升级Proto文件定义,请遵守一下条款:
1:不可以修改现有域的任何标识数字(tag number)
2:不可以添加删除任何required域
3:你可以删除optional,repeated域
4:你可以添加optional repeated域,但是你必须要用不同的tag number(之前删除域的tag number也不能使用!)


protobuf github 包下载: http://download.csdn.net/detail/u013022222/9405304
点我
转载请注明出处:
http://blog.csdn.net/u013022222/article/details/50521835

    推荐阅读