Android和Java的轻巧Wire协议缓冲器

时间:2023-03-09 08:03:42
Android和Java的轻巧Wire协议缓冲器

Wire协议缓冲器 一个人必须有一个代码! -奥马尔小 由于我们的团队和项目增长,数据的种类和数量也随之增加。 成功将您简单的数据模型转换为复杂的! 无论您的应用程序将数据存储到磁盘或网络传送信号,该数据的结构和解释应该是清楚的。 消费者最好的工作,

Wire协议缓冲器

“一个人必须有一个代码!” -奥马尔小

由于我们的团队和项目增长,数据的种类和数量也随之增加。成功将您简单的数据模型转换为复杂的!无论您的应用程序将数据存储到磁盘或网络传送信号,该数据的结构和解释应该是清楚的。消费者最好的工作,他们了解的数据!

架构描述和文档数据模型。如果你有数据,你应该有一个架构。

协议缓冲器

谷歌的协议缓冲器都围绕一个伟大的模式语言:

  • 它的跨平台和语言无关。无论您使用的编程语言,你就可以使用原架构与应用程序

  • 原始模式是向后兼容的,面向未来的。作为您的应用程序失去旧的功能和获得新的可以发展你的架构。

  • 它的重点。原架构描述你的数据模型。而已。

下面是一个示例消息定义:


syntax = "proto2";

package squareup.dinosaurs;

option java_package = "com.squareup.dinosaurs";

import "squareup/geology/period.proto";

message Dinosaur {
// Common name of this dinosaur, like "Stegosaurus".
optional string name = 1; // URLs with images of this dinosaur.
repeated string picture_urls = 2; optional squareup.geology.Period period = 5;
}

这里是一个枚举定义:


syntax = "proto2";

package squareup.geology;

option java_package = "com.squareup.geology";

enum Period {
// 145.5 million years ago — 66.0 million years ago.
CRETACEOUS = 1; // 201.3 million years ago — 145.0 million years ago.
JURASSIC = 2; // 252.17 million years ago — 201.3 million years ago.
TRIASSIC = 3;
}

这种模式语言是Protocol Buffers的'最大的特点。你甚至可以用它纯粹是为了文档的目的,如描述一个JSON API。

协议缓冲器还定义了符合模式的消息的紧凑二进制编码。这种编码快速编码,快速解码,小到传输,和小存储。二进制编码使用数字标签从架构,像5时间以上。

例如,我们的编码这个恐龙:


{
name: "Stegosaurus",
period: JURASSIC
}

编码值仅仅是15个字节:


Hex  Description
0a tag: name(1), field encoding: LENGTH_DELIMITED(2). 1 << 3 | 2
0b "Stegosaurus".length()
53 'S'
74 't'
65 'e'
67 'g'
6f 'o'
73 's'
61 'a'
75 'u'
72 'r'
75 'u'
73 's'
28 tag: period(5), field encoding: VARINT(0). 5 << 3 | 0
02 JURASSIC(2)

为什么网?

该协议缓冲器模式语言和二进制编码都是由谷歌定义。Wire是从广场的独立实现,它是专门针对Android和Java设计的。

对于在架构中定义的每个消息的类型,电线产生一个不可变模型类和它的建造者。生成的代码看起来像你手工编写代码:它的记录,格式化和简单。电线的API,应该感到在家里谁喜欢程序员有效的Java。

尽管如此,也有一些有趣的设计决策线:

  • 电线消息宣布公众最终场,而不是通常的getter方法。这减少了生成的代码和代码执行。更少的代码是Android程序特别有利。

  • 电线避免的情况下映射。声明为字段picture_urls在架构产生一个Java领域 picture_urls,而不是传统的pictureUrls骆驼情况。虽然名字起初感到尴尬,它每次使用是梦幻般的grep或更复杂的搜索工具。,Java源代码和数据模式之间航行时没有更多的映射。它还提供了一个善意提醒调用代码,原消息是有点特殊。

  • 基本类型总是装箱。如果字段不存在,它的价值是。这是用于自然可选字段,如恐龙,其周期是未知的。字段也可以为空,由于模式演变:如果明天我们添加一个食肉动物布尔给我们的消息定义,今天的数据不会有该字段的值。

下面是在紧凑型生成的代码恐龙上述定义的消息:

通过Wire协议缓冲编译器生成的代码//,不编辑。
//源文件:squareup /恐龙/ dinosaur.proto在9:1
package com.squareup.dinosaurs;

import com.squareup.geology.Period;
import com.squareup.wire.Message;
import com.squareup.wire.ProtoAdapter;
import com.squareup.wire.WireField;
import java.util.List;
import okio.ByteString; public final class Dinosaur extends Message<Dinosaur, Dinosaur.Builder> {
public static final ProtoAdapter<Dinosaur> ADAPTER = ProtoAdapter.newMessageAdapter(Dinosaur.class); private static final long serialVersionUID = 0L; public static final String DEFAULT_NAME = ""; public static final Period DEFAULT_PERIOD = Period.CRETACEOUS; /**
* Common name of this dinosaur, like "Stegosaurus".
*/
@WireField(
tag = 1,
adapter = "com.squareup.wire.ProtoAdapter#STRING"
)
public final String name; /**
* URLs with images of this dinosaur.
*/
@WireField(
tag = 2,
adapter = "com.squareup.wire.ProtoAdapter#STRING",
label = WireField.Label.REPEATED
)
public final List<String> picture_urls; @WireField(
tag = 5,
adapter = "com.squareup.geology.Period#ADAPTER"
)
public final Period period; public Dinosaur(String name, List<String> picture_urls, Period period) {
this(name, picture_urls, period, ByteString.EMPTY);
} public Dinosaur(String name, List<String> picture_urls, Period period, ByteString unknownFields) {
super(unknownFields);
this.name = name;
this.picture_urls = immutableCopyOf("picture_urls", picture_urls);
this.period = period;
} @Override
public Builder newBuilder() {
Builder builder = new Builder();
builder.name = name;
builder.picture_urls = copyOf("picture_urls", picture_urls);
builder.period = period;
builder.addUnknownFields(unknownFields());
return builder;
} @Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof Dinosaur)) return false;
Dinosaur o = (Dinosaur) other;
return equals(unknownFields(), o.unknownFields())
&& equals(name, o.name)
&& equals(picture_urls, o.picture_urls)
&& equals(period, o.period);
} @Override
public int hashCode() {
int result = super.hashCode;
if (result == 0) {
result = unknownFields().hashCode();
result = result * 37 + (name != null ? name.hashCode() : 0);
result = result * 37 + (picture_urls != null ? picture_urls.hashCode() : 1);
result = result * 37 + (period != null ? period.hashCode() : 0);
super.hashCode = result;
}
return result;
} public static final class Builder extends com.squareup.wire.Message.Builder<Dinosaur, Builder> {
public String name; public List<String> picture_urls; public Period period; public Builder() {
picture_urls = newMutableList();
} /**
* Common name of this dinosaur, like "Stegosaurus".
*/
public Builder name(String name) {
this.name = name;
return this;
} /**
* URLs with images of this dinosaur.
*/
public Builder picture_urls(List<String> picture_urls) {
checkElementsNotNull(picture_urls);
this.picture_urls = picture_urls;
return this;
} public Builder period(Period period) {
this.period = period;
return this;
} @Override
public Dinosaur build() {
return new Dinosaur(name, picture_urls, period, buildUnknownFields());
}
}
}

在Java代码来创建和访问原车型是紧凑和可读性:


Dinosaur stegosaurus = new Dinosaur.Builder()
.name("Stegosaurus")
.period(Period.JURASSIC)
.build(); System.out.println("My favorite dinosaur existed in the " + stegosaurus.period + " period.");

每种类型都有一个对应ProtoAdapter能够编码一个信息字节和解码字节回一个消息。


Dinosaur stegosaurus = ...
byte[] stegosaurusBytes = Dinosaur.ADAPTER.encode(stegosaurus); byte[] tyrannosaurusBytes = ...
Dinosaur tyrannosaurus = Dinosaur.ADAPTER.decode(tyrannosaurusBytes);

当访问一个字段,使用Wire.get()与相应的默认更换空值:


Period period = Wire.get(stegosaurus.period, Dinosaur.DEFAULT_PERIOD);

这相当于如下:


Period period = stegosaurus.period != null ? stegosaurus.period : Dinosaur.DEFAULT_PERIOD;

生成代码带线

资讯的编译器通过一个Maven插件可用。把.proto在项目的源代码 的src / main /原目录中,然后用插件生成的.java文件。该插件将生成的Java代码会自动添加到项目的源根。


<build>
<plugins>
<plugin>
<groupId>com.squareup.wire</groupId>
<artifactId>wire-maven-plugin</artifactId>
<version>${project.version}</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>generate-sources</goal>
</goals>
<configuration>
<protoFiles>
<param>squareup/dinosaurs/dinosaur.proto</param>
<param>squareup/geology/period.proto</param>
</protoFiles>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

导线可以读取.proto从本地文件系统,并从内的文件的.jar文件。

编译器可以随意删改模式根类型及其传递依赖的一个子集。Java服务和Android应用程序可以使用每一个更大的共享模式的子集:共享模式时,项目之间,这很有用。

如果你不使用Maven,编译器也有一个命令行界面。刚刚替补 丝编译器版本-JAR与- dependencies.jar同的路径罐子。


% java -jar wire-compiler-VERSION-jar-with-dependencies.jar \
--proto_path=src/main/proto \
--java_out=out \
squareup/dinosaurs/dinosaur.proto \
squareup/geology/period.proto
Writing com.squareup.dinosaurs.Dinosaur to out
Writing com.squareup.geology.Period to out

如果你使用Proguard的,那么你需要添加规则。最简单的方法是,告诉Proguard的不要碰线运行时库和产生的协议缓冲区(当然这些简单的规则将错过机会缩小和优化的代码):


-keep class com.squareup.wire.** { *; }
-keep class com.yourcompany.yourgeneratedcode.** { *; }

获得线

线运行包中包含了必须包含在使用线生成的代码的应用程序的运行时支持库。

使用Maven:


<dependency>
<groupId>com.squareup.wire</groupId>
<artifactId>wire-runtime</artifactId>
<version>2.1.1</version>
</dependency>