在网络层使用ProtoBuf协议与后台通信时,但是在项目中,由于历史原因,前端调用处更多采用<key,value>这种键值对。故需要用ProtoBuf的反射来动态创建对象,屏蔽网络层协议不同,对接口调用代码的影响。
在我的上篇笔记ProtoBuf使用初体验-Android Studio配置及JSON互转中可知,Android推荐用protobuf-lite来编译使用proto文件。但这带来一个问题,最终生成的proto代码不再具有Descriptor等描述信息,导致无法进行反射。故最终使用protobuf-java工具来编译.proto文件。
Android Studio配置调整
在proto模块的build.gradle文件中更改配置
apply plugin: 'com.android.library'
apply plugin: 'com.google.protobuf'
...
android {
...
sourceSets {
main {
java {
srcDir 'src/main/java'
}
proto {
srcDir 'src/main/proto'
include '**/*.proto'
}
}
}
}
dependencies {
...
api 'com.google.protobuf:protobuf-java:3.6.0'
implementation 'com.google.protobuf:protoc:3.6.0'
api 'com.googlecode.protobuf-java-format:protobuf-java-format:1.2'
}
protobuf {
protoc {
// You still need protoc like in the non-Android case
artifact = 'com.google.protobuf:protoc:3.6.0'
}
generateProtoTasks {
all().each { task ->
task.builtins {
// In most cases you don't need the full Java output
// if you use the lite output.
remove java
}
task.builtins {
java{}
}
}
}
}
反射创建对象
public static Message buildMessage(String msgClassName, Map<String, String> fields){
Class cl = null;
try {
cl = Class.forName(msgClassName);
Method method = cl.getMethod("newBuilder"); // newBuilder 为静态变量,即使没有 message 的具体实例也可以 invoke!yes!
Object obj = method.invoke(null, new Object[]{});
Message.Builder msgBuilder = (Message.Builder)obj; // 得到 builder
return buildMessage(msgBuilder, fields);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
@SuppressWarnings("unchecked")
private static Message buildMessage(Message.Builder builder, Map<String, String> fields) {
Descriptors.Descriptor descriptor = builder.getDescriptorForType();
for (Map.Entry<String, String> entry : fields.entrySet()) {
if (entry.getValue() == null) {
continue;
}
Descriptors.FieldDescriptor field = getField(descriptor, entry.getKey());
if (field == null){
continue;
}
// if (entry.getValue() instanceof List<?>) {
// List<Object> values = (List<Object>) entry.getValue();
// for (Object value : values) {
// builder.addRepeatedField(field, buildValue(builder, field, value));
// }
//
// } else {
builder.setField(field, buildValue(builder, field, entry.getValue()));
// }
}
return builder.build();
}
@SuppressWarnings("unchecked")
private static Object buildValue(
Message.Builder parentBuilder, Descriptors.FieldDescriptor field, Object value) {
if (field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
if (field.isRepeated()) {}
Message.Builder fieldBuilder = parentBuilder.newBuilderForField(field);
return buildMessage(fieldBuilder, (Map<String, String>) value);
} else if (field.getType() == Descriptors.FieldDescriptor.Type.ENUM) {
return field.getEnumType().findValueByName((String) value);
} else {
switch (field.getJavaType()) {
case FLOAT: // float is a special case
return Float.valueOf(value.toString());
case INT:
return Integer.valueOf(value.toString());
case LONG:
return Long.valueOf(value.toString());
case DOUBLE:
return Double.valueOf(value.toString());
default:
return value;
}
}
}
private static Descriptors.FieldDescriptor getField(Descriptors.Descriptor descriptor, String name) {
return descriptor.findFieldByName(name);
}
数据类型
附上一张protobuf和java等数据结构类型的定义供参考

参考文档:
Protocol Buffers(Protobuf) 官方文档--Protobuf语言指南
一种java对象转换成protobuf对象通用方法
protobuf和json互转时应该注意的问题
protobuf java 反射
网友评论