Android Grpc 最佳实践
前言:最近老听说rpc,就知道可以代替之前的HTTP框架,像调用本地方法一样请求接口,目前公司内部很多部门也都接入了rpc,下面看一下Android端我是怎么接入的。(其实也是搬砖,很多地方搬得不好)
1. 工欲善其事,必先利其器
Grpc 的基础就是利用proto文件生成Java代码,proto文件由接口提供方提供。Android Studio支持“傻瓜式”生成代码,不用安装工具以及打一大长串的命令。
首先 Preferences -> Plugins 搜索Proto,安装下面这个东西,这个插件提供编辑功能,有代码补全和导航的功能。
2. 开始配置Gradle
首先 在 project的build.gradle 的 buildscript 的dependencies 下面添加
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
然后将proto文件拷贝到模块中,我这里放到和Java平级
然后在 模块的build.gradle 添加
plugins {
id 'com.android.library'
id 'com.google.protobuf' // proto
}
然后指定proto文件位置
sourceSets {
main {
java {
srcDir 'src/main/java'
}
proto {
srcDir 'src/main/proto' // 模块下的proto文件夹
include '**/*.proto'
}
}
}
然后和android标签同级,添加以下代码
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.12.0' } // 相当于proto编译器
plugins {
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.33.0' } // Grpc单独的编译器
javalite {
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0' // 官方推荐的方法,Android 适用javalite,相较于java插件,生成的代码更轻量化
}
}
generateProtoTasks {
all().each { task ->
task.builtins {
java { option 'lite' }
}
task.plugins {
grpc { // Options added to --grpc_out
option 'lite' }
}
}
}
}
然后再添加这些依赖
implementation 'io.grpc:grpc-okhttp:1.33.0'
implementation 'io.grpc:grpc-protobuf-lite:1.33.0'
implementation 'io.grpc:grpc-stub:1.33.0'
生成的grpc代码里会引用这些库里的代码
上面这些配置都是自己参考各路大神一点点摸索出来的,可能有不足的地方。
配置好了,然后开始看看proro文件吧
3. 开始整proto
先说一下刚开始安装的插件的用法,在 Preferences->Languages&Frameworks -> Protocol Buffers ,将自己的proto文件的全路径添加到配置里,这样就能导航了,编辑的时候也不会提示错误,如果在自己的proto文件中使用到了其他库里的proto,我的建议是都去下载下来,然后copy到自己的工程目录中。可能是我不会用,否则即使指定了proto的目录,编译时也会提示找不到。
下面以Jaeger (关于Jaeger在我另外一篇文章有提到)官方提供的proto为例,例如下面的model.proto
syntax="proto3";
package jaeger.api_v2;
import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
option go_package = "model";
option java_package = "io.jaegertracing.api_v2"; // 生成的Java代码包名
enum ValueType {
STRING = 0;
BOOL = 1;
INT64 = 2;
FLOAT64 = 3;
BINARY = 4;
};
message KeyValue {
option (gogoproto.equal) = true;
option (gogoproto.compare) = true;
string key = 1;
ValueType v_type = 2;
string v_str = 3;
bool v_bool = 4;
int64 v_int64 = 5;
double v_float64 = 6;
bytes v_binary = 7;
}
message Log {
google.protobuf.Timestamp timestamp = 1 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
repeated KeyValue fields = 2 [
(gogoproto.nullable) = false
];
}
......
在这个proto中引用了 三个其他的proto文件,这些文件我们在github上可以直接搜到,然后下载下来copy到自己的proto文件夹中(按理说不应该这么做,一种办法是使用命令编译,指定依赖文件,引入的Gradle插件应该也支持,但是不知道怎么用o(╥﹏╥)o),下载下来的proto不用更改任何东西,最多改个名,然后把自己的proto文件里的import改一下,比如上面的可以直接改成“import "gogo.proto";”
把前面的路径删了就行,如果这些第三方proto还引用了其他的,则再按照这个方法,直到所有proto文件都在自己工程里,这样实在太累了。例如我的工程,对我有用的只有两个proto,我却得引用这么多,谁能有个好办法,麻烦告诉我。
4. 开始编译
在 build.gradle添加’com.google.protobuf‘ 就有了generateDebugProto命令,可以在下面的路径里找到
双击运行,然后切换到Android目录,就能看到生成的代码了
个人感觉可以把生成的代码拷贝出来(如果proto不经常变的情况),把插件禁用掉,避免每次都生成。
5. 开始使用Grpc
一开始不知道哪个类里有grpc请求,后来发现类名就是proto文件里的grpc service 字段后加个Grpc后缀,比如下面
service CollectorService {
rpc PostSpans(PostSpansRequest) returns (PostSpansResponse) {
option (google.api.http) = {
post: "/api/v2/spans"
body: "*"
};
}
}
生成的Grpc请求代码就在CollectorServiceGrpc.java中,PostSpans 就是发起请求的方法,PostSpansRequest,就是要传输的数据,生成的Java代码如下:
public com.sunshuo.grpc.jaeger.Collector.PostSpansResponse postSpans(com.sunshuo.grpc.jaeger.Collector.PostSpansRequest request) {
return blockingUnaryCall(
getChannel(), getPostSpansMethod(), getCallOptions(), request);
}
}
Grpc的用法也是固定的
ManagedChannelBuilder mChannelBuilder = ManagedChannelBuilder.forAddress(ip, port); // grpc服务ip地址,port端口号
//或者 ManagedChannelBuilder.forTarget(url) url 服务器地址
CollectorServiceGrpc.CollectorServiceBlockingStub mBlockingStub = CollectorServiceGrpc.newBlockingStub(mChannel); // 生成一个远端服务在client的存根,看名称应该是阻塞调用
Collector.PostSpansRequest request = Collector.PostSpansRequest.newBuilder().setBatch(ConvertUtil.convertBatch(spans, process)).build(); // 构建请求的Bean
Collector.PostSpansResponse response = mBlockingStub.postSpans(request); //开始请求,并拿到response
6. 总结
一旦生成了Grpc代码,调用过程是非常简单的。我在使用的时候遇到各种UNAVAILABLE,因为由于一些原因我把生成的代码里的某些JavaBen换成了其他的。最好不要改生成的代码,除非有十足的把握。