Introducing Wire Protocol Buffers

最近做了个项目用到socket,数据格式为protobuf,之前用了google的protobuf-java,感觉文件很大

找了找,结果发现了squareup的这个库,生成的文件比google的小很多,用起来很简单,没有做翻译,以下是原文!!!

What is Wire?

Wire is a new, open-source implementation of Google’s Protocol Buffers. It’s meant for Android devices but can be used on anything that runs Java language code.

At Square, we use Protocol Buffers extensively. Protocol Buffers are a language- and platform-neutral schema for describing and transmitting data. Developers can use the same schemas across diverse environments, including the environments we care about at Square, such as Java and Ruby servers and Android and iOS devices.

Protocol Buffer .proto source files are human-readable and can contain comments, so it’s easy to document your schema right where it is defined. Protocol Buffers define a compact binary wire format that allows schemas to evolve without breaking existing clients. Typically, a code generator is used that reads .proto files, and emits source code in the language of your choice. This approach helps to speed development since the resulting code is expressed in the same language as the rest of your application, allowing tools such as IDE autocompletion to do their job fully.

Wire Features

As we began to run into limitations of the standard Protocol Buffer implementation in our Android apps, we made a wish list of the features we wanted for a future implementation:

  • Messages should contain a minimum number of generated methods
  • Messages should be clean, developer-friendly data objects:
    • They should be highly readable
    • They should be deeply immutable
    • They should have meaningful equalshashCode, and toString methods
    • They should support the chained Builder pattern
    • They should inherit documentation from the .proto source files
    • Protocol Buffer enums should map onto Java enums
  • Ideally, everything should be buildable using Java-based tools

Before we decided to build a new library we looked at several alternatives, including the recent NanoAndroid Protocol Buffer library. While Nano Protocol Buffers generate very few methods, they didn’t meet our other goals. Ultimately, we decided to create our own library from scratch, built on Square’s ProtoParser and JavaWriter libraries.

It’s handy to be able to use generated Protocol Buffer classes as full-fledged data objects in your app. By including equalshashCode, and toString methods, messages can participate in Java collections. Since the generated code is clean and compact, stepping into it in the debugger is not a problem. And because comments in your .proto files are copied into the generated Java source code, the documentation for your messages and fields is right there in your IDE.

Reducing the Method Count

In the past, Android developers attempting to use Protocol Buffers have paid a steep price. The standard Protocol Buffer implementation (protoc) generates at least nine methods for each optional or required field in your schema (variants of getsethas, and clear), and at least eighteen methods for repeated fields!

Having all this flexibility is great in non-constrained environments — whatever method you need is probably just a few auto-completed keystrokes away. But in Android environments the Dalvik bytecode format imposes a hard limit of 64K methods in a single application. For the Square Register app, generated Protocol Buffer code was taking up a large chunk of our method space. By switching to Wire, we removed almost 10,000 methods from our application and gained a lot of breathing room to add new features.

Wire Example

Consider the classic Person protocol buffer definition:

message Person {
  // The customer's full name.
  required string name = 1;
  // The customer's ID number.
  required int32 id = 2;
  // Email address for the customer.
  optional string email = 3;

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

  message PhoneNumber {
    // The user's phone number.
    required string number = 1;
    // The type of phone stored here.
    optional PhoneType type = 2 [default = HOME];
  }

  // A list of the user's phone numbers.
  repeated PhoneNumber phone = 4;
}

The Person class Wire generates is below (the complete generated code is
here):

public final class Person extends Message {
  /** The customer's full name. */
  @ProtoField(tag = 1, type = STRING, label = REQUIRED)
  public final String name;

  /** The customer's ID number. */
  @ProtoField(tag = 2, type = INT32, label = REQUIRED)
  public final Integer id;

  /** Email address for the customer. */
  @ProtoField(tag = 3, type = STRING)
  public final String email;

  /**  A list of the user's phone numbers. */
  @ProtoField(tag = 4, label = REPEATED)
  public final List<PhoneNumber> phone;

  private Person(Builder builder) {
    super(builder);
    this.name = builder.name;
    this.id = builder.id;
    this.email = builder.email;
    this.phone = immutableCopyOf(builder.phone);
  }

  @Override public boolean equals(Object other) {
    if (!(other instanceof Person)) return false;
    Person o = (Person) other;
    return equals(name, o.name)
        && equals(id, o.id)
        && equals(email, o.email)
        && equals(phone, o.phone);
  }

  @Override public int hashCode() {
    int result = hashCode;
    if (result == 0) {
      result = name != null ? name.hashCode() : 0;
      result = result * 37 + (id != null ? id.hashCode() : 0);
      result = result * 37 + (email != null ? email.hashCode() : 0);
      result = result * 37 + (phone != null ? phone.hashCode() : 0);
      hashCode = result;
    }
    return result;
  }

  public static final class Builder extends Message.Builder<Person> {
    // not shown
  }
}

How it Works

An instance of a message class can only be created by a corresponding nested Builder class. Wire generates a single method per field in each builder in order to support chaining:

Person person = new Person.Builder()
    .name("Omar")
    .id(1234)
    .email("omar@wire.com")
    .phone(Arrays.asList(new PhoneNumber.Builder()
        .number("410-555-0909")
        .type(PhoneType.MOBILE)
        .build()))
    .build();

Wire reduces the number of generated methods by using a public final field for each message field. Arrays are wrapped, so message instances are deeply immutable. Each field is annotated with a @ProtoField annotation providing metadata that Wire needs to perform serialization and deserialization:

@ProtoField(tag = 1, type = STRING, label = REQUIRED)
public final String name;

Use these fields directly to access your data:

if (person.phone != null) {
  for (PhoneNumber phone : person.phone)
    if (phone.type == PhoneType.MOBILE) {
      sendSms(person.name, phone.number, message);
      break;
    }
  }
}

The code to serialize and deserialize the Person we created above looks like this:

byte[] data = person.toByteArray();
Wire wire = new Wire();
Person newPerson = wire.parseFrom(data, Person.class);

Some features, such as serialization, deserialization, and the toString method are implemented using reflection. Wire caches reflection information about each message class the first time it is encountered for better performance.

In standard Protocol Buffers, you would call person.hasEmail() to see if an email address has been set. In Wire, you simply check if person.email == null. For repeated fields such asphone, Wire also requires your app to get or set a List of PhoneNumber instances all at once, which saves a lot of methods.

Wire supports additional features such as extensions and unknown fields. At present, it lacks support for some advanced features including custom options, services, and runtime introspection of schemas. These can be added in the future as use cases for them on constrained devices emerge.

Wire deliberately does not support the obsolete ‘groups’ feature.

Try it out!

We encourage you to try Wire, contribute to the code, and let us know how it works in your apps!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值