我是靠谱客的博主 称心大神,最近开发中收集的这篇文章主要介绍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 protobuf 生成java 文件详解0. 前言1. 引子ActivityRecordPtoto2. 编译3. 原理4. 构造函数5. 反序列化6. 其他函数7. 举例所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

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

评论列表共有 0 条评论

立即
投稿
返回
顶部