我是靠谱客的博主 称心大神,这篇文章主要介绍Android protobuf 生成java 文件详解0. 前言1. 引子ActivityRecordPtoto2. 编译3. 原理4. 构造函数5. 反序列化6. 其他函数7. 举例,现在分享给大家,希望可以做个参考。

 请支持原创~~

系列博文:

 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内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(114)

评论列表共有 0 条评论

立即
投稿
返回
顶部