Android Grpc 最佳實踐
前言:最近老聽說rpc,就知道可以代替之前的HTTP框架,像調(diào)用本地方法一樣請求接口,目前公司內(nèi)部很多部門也都接入了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到自己的工程目錄中??赡苁俏也粫茫駝t即使指定了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不經(jīng)常變的情況),把插件禁用掉,避免每次都生成。
5. 開始使用Grpc
一開始不知道哪個類里有grpc請求,后來發(fā)現(xiàn)類名就是proto文件里的grpc service 字段后加個Grpc后綴,比如下面
service CollectorService {
rpc PostSpans(PostSpansRequest) returns (PostSpansResponse) {
option (google.api.http) = {
post: "/api/v2/spans"
body: "*"
};
}
}
生成的Grpc請求代碼就在CollectorServiceGrpc.java中,PostSpans 就是發(fā)起請求的方法,PostSpansRequest,就是要傳輸?shù)臄?shù)據(jù),生成的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的存根,看名稱應該是阻塞調(diào)用
Collector.PostSpansRequest request = Collector.PostSpansRequest.newBuilder().setBatch(ConvertUtil.convertBatch(spans, process)).build(); // 構建請求的Bean
Collector.PostSpansResponse response = mBlockingStub.postSpans(request); //開始請求,并拿到response
6. 總結
一旦生成了Grpc代碼,調(diào)用過程是非常簡單的。我在使用的時候遇到各種UNAVAILABLE,因為由于一些原因我把生成的代碼里的某些JavaBen換成了其他的。最好不要改生成的代碼,除非有十足的把握。