irpas技术客

Android protobuf 生成java 文件详解_私房菜_proto生成java文件

irpas 767

?请支持原创~~

系列博文:

?Android protobuf 原理以及ProtoOutputStream、ProtoInputStream 使用(最全)

?Android protobuf 生成java 文件详解

Android protobuf 生成c++ 文件详解

android protobuf 在ProtoOutputStream和ProtoInputStream 中实现原理

Android protobuf 编码详解

?基于版本:Android R

0. 前言

上一篇 Android protobuf 原理??中简单分析了proto buf 的优缺点和实现原理,以及使用。对于 *.proto 文件的详细编译、生成原理以单独的博文呈现,这一篇主要分析android 中proto 文件编译成java 文件的过程和应用。

Android protobuf 原理??中已经阐述过proto 文件书写语法和一些option 注意点,这里暂时不在重复叙述,本文主要参考android framework 中的 windowsmanagerservice.proto 文件引入分析说明。

proto 文件路径:frameworks/base/core/proto/android/server/windowmanagerservice.proto

1. 引子ActivityRecordPtoto
package com.android.server.wm; option java_multiple_files = true; ... message ActivityRecordProto { optional string name = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; optional WindowTokenProto window_token = 2; ... optional bool reported_drawn = 12; optional bool reported_visible = 13; ... optional int32 proc_id = 29; optional bool translucent = 30; }

这个源文件位于windowmanagerservice.proto 文件中,详细路径见上文。

其中指定了option java_multiple_files,所以里面的message 都是以单独的文件产生。

对于嵌套的message 和内嵌的编译生成文件,后面会举例说明。

2. 编译

android 编译proto 使用的是protoc 命令,详细可以看build/make/core/definitions.mk:

define transform-proto-to-java @mkdir -p $(dir $@) @echo "Protoc: $@ <= $(PRIVATE_PROTO_SRC_FILES)" @rm -rf $(PRIVATE_PROTO_JAVA_OUTPUT_DIR) @mkdir -p $(PRIVATE_PROTO_JAVA_OUTPUT_DIR) $(hide) for f in $(PRIVATE_PROTO_SRC_FILES); do \ $(PROTOC) \ $(addprefix --proto_path=, $(PRIVATE_PROTO_INCLUDES)) \ $(PRIVATE_PROTO_JAVA_OUTPUT_OPTION)="$(PRIVATE_PROTO_JAVA_OUTPUT_PARAMS):$(PRIVATE_PROTO_JAVA_OUTPUT_DIR)" \ $(PRIVATE_PROTOC_FLAGS) \ $$f || exit 33; \ done $(SOONG_ZIP) -o $@ -C $(PRIVATE_PROTO_JAVA_OUTPUT_DIR) -D $(PRIVATE_PROTO_JAVA_OUTPUT_DIR) endef

详细的规则是定义在build/make/core/java_common.mk,感兴趣的同学可以自行查看,需要注意的是:

ifeq ($(LOCAL_PROTOC_OPTIMIZE_TYPE),micro) $(proto_java_srcjar): PRIVATE_PROTO_JAVA_OUTPUT_OPTION := --javamicro_out $(proto_java_srcjar): PRIVATE_PROTOC_FLAGS += --plugin=$(HOST_OUT_EXECUTABLES)/protoc-gen-javamicro $(proto_java_srcjar): $(HOST_OUT_EXECUTABLES)/protoc-gen-javamicro else ifeq ($(LOCAL_PROTOC_OPTIMIZE_TYPE),nano) $(proto_java_srcjar): PRIVATE_PROTO_JAVA_OUTPUT_OPTION := --javanano_out $(proto_java_srcjar): PRIVATE_PROTOC_FLAGS += --plugin=$(HOST_OUT_EXECUTABLES)/protoc-gen-javanano $(proto_java_srcjar): $(HOST_OUT_EXECUTABLES)/protoc-gen-javanano else ifeq ($(LOCAL_PROTOC_OPTIMIZE_TYPE),stream) $(proto_java_srcjar): PRIVATE_PROTO_JAVA_OUTPUT_OPTION := --javastream_out $(proto_java_srcjar): PRIVATE_PROTOC_FLAGS += --plugin=$(HOST_OUT_EXECUTABLES)/protoc-gen-javastream $(proto_java_srcjar): $(HOST_OUT_EXECUTABLES)/protoc-gen-javastream else $(proto_java_srcjar): PRIVATE_PROTO_JAVA_OUTPUT_OPTION := --java_out endif

这里会根据不同的LOCAL_PROTOC_OPTIMIZE_TYPE 指定不同的option,例如

默认情况是编译对应的proto java 文件,使用 --java_out 选项指定输出目录;配合java stream 需要使用?--javastream_out 选项指定输出目录,android framework 中的protobuf 指定的field 都是通过此选项编译成的 java 识别,field 值包含了value的类型、field id、是否为repeate;

当然,对于上面第二点,可以查看frameworks/base/Android.bp,android R 是在该bp 文件中执行指定了规则:

gensrcs { name: "framework-javastream-protos", depfile: true, tools: [ "aprotoc", "protoc-gen-javastream", "soong_zip", ], cmd: "mkdir -p $(genDir)/$(in) " + "&& $(location aprotoc) " + " --plugin=$(location protoc-gen-javastream) " + " --dependency_out=$(depfile) " + " --javastream_out=$(genDir)/$(in) " + " -Iexternal/protobuf/src " + " -I . " + " $(in) " + "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)", srcs: [ ":ipconnectivity-proto-src", "core/proto/**/*.proto", "libs/incident/**/*.proto", ], output_extension: "srcjar", }

具体的gensrcs 规则,这里不做过多的阐述,详细可以查看build/soong/genrule下。

ok 回到起点

以上面ActivityRecordProto 为例,该message 声明在windowsmanagerservice.proto 文件中,在编译的时候会将message ActivityRecordProto 编译称单独的final class,不允许出现子类。

对于android framework 中的proto 只是需要protoc-gen-javastream生成的java 文件,而protoc 编译生成的只作为host 库:

java_library_host { name: "platformprotos", srcs: [ ":ipconnectivity-proto-src", "cmds/am/proto/instrumentation_data.proto", "cmds/statsd/src/**/*.proto", "core/proto/**/*.proto", "libs/incident/proto/**/*.proto", ], proto: { include_dirs: ["external/protobuf/src"], type: "full", }, errorprone: { javacflags: ["-Xep:MissingOverride:OFF"], // b/72714520 }, }

但是至少我们能看到编译出来的源码是什么样子,如果后期使用也是有参考,代码生成在out\soong\.intermediates\frameworks\base\platformprotos\linux_glibc_common 下,其中包含?*.class 和 *.srcjar,其中 *.srcjar 是java 文件的打包,例如这里的ActivityRecordProto.java。

3. 原理

通过static 接口 newBuilder 获取Builder 对象,进行序列化的初始化操作,一般通过Builder 中对应field 的set 接口设置,并在最后通过build()产生一个新的 ActivityRecordProto 对象,通过调用toByteArray 接口进行最终的序列化。

通过static 接口 parseFrom,将序列化的 byte array,进行反序列化操作,并最终生成解析后的ActivityRecordProto 对象。

这里不具体列代码,参考:external/protobuf/java/core/src/main/java/com/google/protobuf/*.java

4. 构造函数
private ActivityRecordProto(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) { super(builder); } private ActivityRecordProto() { name_ = ""; frozenBounds_ = java.util.Collections.emptyList(); state_ = ""; } private ActivityRecordProto( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException {

构造函数都是私有的,所以,给外界提供获取对象的方式是:

private static final com.android.server.wm.ActivityRecordProto DEFAULT_INSTANCE; static { DEFAULT_INSTANCE = new com.android.server.wm.ActivityRecordProto(); } public static com.android.server.wm.ActivityRecordProto getDefaultInstance() { return DEFAULT_INSTANCE; }

一般都是直接通过newBuilder 创建Builder 对象,然后通过Builder 对象创建CodedOutputStream 进行序列化,并返回一个ActivityRecordProto 对象。

5. 反序列化

ActivityRecordProto 中提供了很多parseFrom 函数,支持不同类型的解析,最终都是调用PARSER.parseFrom,而PARSER 是一个static 静态对象:

@java.lang.Deprecated public static final com.google.protobuf.Parser<ActivityRecordProto> PARSER = new com.google.protobuf.AbstractParser<ActivityRecordProto>() { @java.lang.Override public ActivityRecordProto parsePartialFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return new ActivityRecordProto(input, extensionRegistry); } };

通过AbstractParser.parseFrom 在回调这里的实现parsePartialFrom,不过在AbstraceParser 中创建了CodedInputStream 对象用以反序列化操作。

6. 其他函数

Buillder 中提供辅助的接口比较多,例如:

public Builder mergeFrom(com.google.protobuf.Message other) {} public Builder mergeFrom(com.android.server.wm.ActivityRecordProto other) {}

还有针对field 的has、set、get、clear 接口。例如:

public boolean hasName() { return ((bitField0_ & 0x00000001) != 0); } public Builder setName(java.lang.String value) { if (value == null) { throw new NullPointerException(); } bitField0_ |= 0x00000001; name_ = value; onChanged(); return this; } public Builder clearName() { bitField0_ = (bitField0_ & ~0x00000001); name_ = getDefaultInstance().getName(); onChanged(); return this; } public java.lang.String getName() { java.lang.Object ref = name_; if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { name_ = s; } return s; } else { return (java.lang.String) ref; } }

ActivityRecordProto 类中也针对field 提供辅助接口:

public boolean hasName() { return ((bitField0_ & 0x00000001) != 0); } public java.lang.String getName() { java.lang.Object ref = name_; if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { name_ = s; } return s; } }

7. 举例

就以这里的ActivityRecordProto 为例,简化如下:

message ActivityRecordProto { optional int32 proc_id = 1; optional string name = 2; }

进行序列化和反序列化的代码大致如下:

import com.android.server.wm.ActivityRecordProto; import java.lang.String; private byte[] test { ActivityRecordProto.Builder builder = ActivityRecordProto.newBuilder(); builder.setProcId(1); builder.setName("test"); ActivityRecordProto proto = builder.build(); return proto.toByteArray(); } private void parse(byte[] datas) { ActivityRecordProto proto = ActivityRecordProto.parseFrom(datas); int procId = proto.getProcId(); String name = proto.getName(); }

?系列博文:

?Android protobuf 原理以及ProtoOutputStream、ProtoInputStream 使用(最全)

?Android protobuf 生成java 文件详解

Android protobuf 生成c++ 文件详解

android protobuf 在ProtoOutputStream和ProtoInputStream 中实现原理

Android protobuf 编码详解


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #proto生成java文件 #protobuf #使用最全0 #前言上一篇 #Android