AndroidのgRPCクライアントをKotlinで実装しました
このあいだの記事では、Go 言語による gRPC サーバーの実装を完了させました。
また、 Go 言語によるクライアント実装についても触れました。
実運用としてクライアント(データの受け取り手)となるものは、Web の他に、
Android や iOS などのスマートフォンであることが多いかと思います。
というわけで、今回は、Android の gRPC クライアントを Kotlin で実装してみました。
1. 参考
以下が特に参考になりました。
- 公式のチュートリアル: https://grpc.io/docs/tutorials/basic/android.html
- Android の実装例: https://github.com/grpc/grpc-java/tree/v1.18.0/examples/android/helloworld
- protobuf-gradle-plugin の README: https://github.com/google/protobuf-gradle-plugin
また、出来上がりがこちらですので、
このあとの説明は、このコードを見ながらだとわかりやすいと思います。
2. proto を ビルドするための gradle の設定
最近は build.gradle
を Groovy でなく Kotlin で書くことも流行っている気配なので Kotlin で書きました。
……が、そこまで大きなメリットがあるわけではなく、
少数派の書き方となってしまい情報が得づらくなるので、Android 開発に慣れていなければあまりオススメできません。
基本的には app ディレクトリ以下の build.gradle.kts
を編集すれば良く、
grpc-java リポジトリにある実装例の build.gradle
を、Kotlin 向けに書き直して以下のようにしました。
import com.google.protobuf.gradle.* import org.jetbrains.kotlin.config.KotlinCompilerVersion plugins { id("com.android.application") kotlin("android") kotlin("android.extensions") id("com.google.protobuf") version "0.8.8" } val grpcVersion = "1.18.0" val protocVersion = "3.6.1" android { compileSdkVersion(28) defaultConfig { applicationId = "net.hatone.hello_grpc" minSdkVersion(21) targetSdkVersion(28) versionCode = 1 versionName = "1.0" testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" } buildTypes { getByName("debug") { isMinifyEnabled = false } getByName("release") { isMinifyEnabled = true proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") } } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } } dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) implementation(kotlin("stdlib-jdk8", KotlinCompilerVersion.VERSION)) implementation("com.android.support:appcompat-v7:28.0.0") implementation("com.android.support.constraint:constraint-layout:1.1.3") // Test testImplementation("junit:junit:4.12") androidTestImplementation("com.android.support.test:runner:1.0.2") androidTestImplementation("com.android.support.test.espresso:espresso-core:3.0.2") // gRPC implementation("io.grpc:grpc-okhttp:$grpcVersion") implementation("io.grpc:grpc-protobuf-lite:$grpcVersion") implementation("io.grpc:grpc-stub:$grpcVersion") implementation("javax.annotation:javax.annotation-api:1.3.2") } protobuf { protoc { artifact = "com.google.protobuf:protoc:$protocVersion" } plugins { id("javalite") { artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0" } id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion" } } generateProtoTasks { all().forEach { it.plugins { id("javalite") id("grpc") { option("lite") } } } } }
https://github.com/nekonenene/grpc_android_client/blob/master/app/build.gradle.kts
ポイントは、plugins
ブロックにある id("com.google.protobuf") version "0.8.8"
です。
これにより、 protobuf-gradle-plugin が依存関係となり、
protobuf
ブロックの使用が可能になっています。
なお、『API 'variant.getJavaCompile()' is obsolete and has been replaced with 'variant.getJavaCompileProvider()'.』という Warning がビルド時に出ますが、
おそらくこれは protobuf-gradle-plugin の問題によるもので、近い将来解決されるでしょう。
proto ファイルは app/src/main/proto
ディレクトリ以下に置きます。
それ以外のディレクトリに置きたい場合は、 sourceSets
ブロックに定義を書きましょう。
(参考: https://github.com/google/protobuf-gradle-plugin#customizing-source-directories )
ビルドに成功すると、以下のように generatedJava フォルダの下に、
proto ファイルの定義に従って Java のクラスやインターフェイスが作られたことが確認できます。
generatedJava は便宜上のフォルダで、実際は
GreeterGrpc.java
は app/build/generated/source/proto/debug/grpc/io/grpc/examples/hello/
ディレクトリ下、
HelloProto.java
などは app/build/generated/source/proto/debug/javalite/io/grpc/examples/hello/
ディレクトリ下に作られていました。
3. gRPC 通信をおこなう
これもまた、grpc-java リポジトリにある実装例の HelloworldActivity.java
を Kotlin 向けに書き直せばいけます。
……が、このままだとさすがに汚いので、このコードで言うところの GrpcTask クラスは別ファイルに分け、
また、callback を設定し、Activity と疎結合になるようにしました。
やっていることとしては非常に単純で、
val channel = ManagedChannelBuilder .forAddress("grpc-test.hatone.net", 443) .useTransportSecurity() .build() val stub = GreeterGrpc.newBlockingStub(channel) val request = HelloRequest.newBuilder() .setName("Nanako") .setAge(75) .build() val reply: HelloReply = stub.sayHello(request) reply.message
このような書き方で簡単にレスポンスを取得できてしまいます。
GreeterGrpc クラスなどが自動生成されてくれるおかげで楽ができるのです。
SSL/TLS 通信をおこなうので、ManagedChannel 作成の際に
usePlaintext()
でなく useTransportSecurity()
メソッドを使うようになっている点にご注意ください。
また、インターネット通信をおこなうので、 AndroidManifest.xml
に
<uses-permission android:name="android.permission.INTERNET" />
の1行を追加しておくことを忘れないようにしてください。
4. おわりに
もうちょっと書くことがある気もするんですが、ブログにしてみると意外とこんなものでした。
proto ファイルは今回は gRPC サーバー側の hello.proto
を単純にコピーしてきましたが、
同一である必要があるので、別リポジトリを作って管理し、
submodule として各々が最新を引っ張ってくる作りにしておくと良いと思います。
久しぶりに Android アプリのコードを書いたので何度か悩みましたが、
それなりにきれいな実装に出来たと思うのでよかったです。
今回は AsyncTask を使いましたが、 RxJava(RxKotlin)も使ってみたいですね!