Android GRPC 最佳实践

Android Grpc 最佳实践

前言:最近老听说rpc,就知道可以代替之前的HTTP框架,像调用本地方法一样请求接口,目前公司内部很多部门也都接入了rpc,下面看一下Android端我是怎么接入的。(其实也是搬砖,很多地方搬得不好)

1. 工欲善其事,必先利其器

Grpc 的基础就是利用proto文件生成Java代码,proto文件由接口提供方提供。Android Studio支持“傻瓜式”生成代码,不用安装工具以及打一大长串的命令。

首先 Preferences -> Plugins 搜索Proto,安装下面这个东西,这个插件提供编辑功能,有代码补全和导航的功能。


image-20201029193602128.png

2. 开始配置Gradle

首先 在 project的build.gradle 的 buildscript 的dependencies 下面添加

classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'

然后将proto文件拷贝到模块中,我这里放到和Java平级

image-20201029194246135.png

然后在 模块的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的目录,编译时也会提示找不到。


image-20201029200818132.png

下面以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,我却得引用这么多,谁能有个好办法,麻烦告诉我。


image-20201029204030640.png

4. 开始编译

在 build.gradle添加’com.google.protobuf‘ 就有了generateDebugProto命令,可以在下面的路径里找到

image-20201029204621066.png

双击运行,然后切换到Android目录,就能看到生成的代码了

image-20201029204855968.png

个人感觉可以把生成的代码拷贝出来(如果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换成了其他的。最好不要改生成的代码,除非有十足的把握。

你可能感兴趣的:(Android GRPC 最佳实践)