请支持原创~~
系列博文:
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
},
}
但是至少我们能看到编译出来的源码是什么样子,如果后期使用也是有参考,代码生成在outsoong.intermediatesframeworksbaseplatformprotoslinux_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 编码详解
最后
以上就是称心大神最近收集整理的关于Android protobuf 生成java 文件详解0. 前言1. 引子ActivityRecordPtoto2. 编译3. 原理4. 构造函数5. 反序列化6. 其他函数7. 举例的全部内容,更多相关Android内容请搜索靠谱客的其他文章。
发表评论 取消回复