Android+JavaサーバでgRPCを使った通信をためす

# はじめに

この記事は、今携わっているProjectでサーバとクライアント間の通信をgRPCを使って行おうということになり、AndroidへgRPCを実装するチュートリアルをやりながら色々と調べたことを書いたものになります。

# そもそもgRPCとは？

[gRPCって何？](https://qiita.com/oohira/items/63b5ccb2bf1a913659d6) の概要でも述べられている通り、Googleが開発した通信プロトコルのことです。 [Remote procedure call](https://ja.wikipedia.org/wiki/%E9%81%A0%E9%9A%94%E6%89%8B%E7%B6%9A%E3%81%8D%E5%91%BC%E5%87%BA%E3%81%97) というクライアントからサーバのメソッドを直接呼ぶことでサーバとクライアントが通信を行え、またその逆もできるという技術の実現のために開発されました。

従来のREST APIではJsonで情報をやりとりすることが主でしたが、サーバとクライアントで別々の実装を行うため、実装コストが大きくなってしまっていました。

しかし、gRPCは`.proto`ファイルにAPI仕様を定義するだけで様々なプログラミング言語の実装を生成することができるのでサーバ、クライアント双方で実装コストの減少に繋がります。

また、[Protocol Buffers](https://developers.google.com/protocol-buffers/docs/overview) によってRESTより早い通信が可能になります。[^1] [^2]


# 早速チュートリアルを試してみる
## 環境

- Mac OS Sierra (10.12.6)
- JDK 1.8.0_131 (1.7以上なら大丈夫っぽい)
- Git
- Android SDK
- Android端末 (Nexus5 Android 6.0.1)

## Hello World

まずは [公式のクイックスタートガイド](https://grpc.io/docs/quickstart/android.html) を見ながらHello 〇〇(任意の入力文字列)を返すアプリを動かすとこまでやってみます。

### サンプルのダウンロード

Githubリポジトリからサンプルコードを`git clone`でダウンロードします。

```shell
$ # 2017/12/22 最新のバージョン1.8.0をダウンロード
$ git clone -b v1.8.0 https://github.com/grpc/grpc-java
$ # サンプルコードのあるディレクトリへ移動
$ cd grpc-java/examples
```

### gRPCアプリケーションの実行

`grpc-java/examples` ディレクトリ内で以下を実行してサーバをコンパイルします。

```shell
$ ./gradlew installDist
```

そしてサーバを実行させておきます。

```shell
$ ./build/install/examples/bin/hello-world-server
```

Android端末をデバッグモードで接続しておき、adbで以下を実行しておきます。

```shell
adb reverse tcp:8080 tcp:50051
```

サーバを実行させたターミナルとは別のターミナルを開いて、クライアントをコンパイル、実行します。

```shell
$ cd android/helloworld
$ ./gradlew installDebug
```

成功すれば端末の方にアプリがインストールされ、以下のような画面が表示されるはずです。

![Screenshot_20171223-183811.png](https://qiita-image-store.s3.amazonaws.com/0/123264/fe8650bd-da85-dd90-11b6-9f9ac11b7760.png)

Hostに `localhost` 、Portに `8080` (adbで50051とリバースしたので)を入力し、messageに適当に送信したい文字を入力(例えば `world` )を入力し、以下のようにレスポンスが帰ってきたらサーバとの通信成功です！

![Screenshot_20171223-184944.png](https://qiita-image-store.s3.amazonaws.com/0/123264/b361861c-b0fb-10ea-ed77-a5975d63cbfc.png)


# コードについて

とりあえず動きはしましたが、では、実際コードの中身はどうなっているんでしょうか。
次は `.proto` ファイル、サーバのコード、クライアント(Android)のコードについて見てみます。

## `helloworld.proto` と生成コード

```proto
// メソッドの定義
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// リクエストのデータ構造の定義
message HelloRequest {
  string name = 1;
}

// レスポンスのデータ構造の定義
message HelloReply {
  string message = 1;
}
```
`helloworld.proto` ファイルには上でも書いたようにAPI仕様を記述します。
具体的にはメソッドと使用するデータのデータ構造を記述しておきます。

詳しい記述方法については [Protocol Buffers](https://developers.google.com/protocol-buffers/?hl=ja) の [Language Guide (proto3)](https://developers.google.com/protocol-buffers/docs/proto3?hl=ja) を参照してください。

この `helloworld.proto` ファイルを各言語ごとにコンパイルすることで言語に応じたgRPCのインターフェイスが生成されます。

## サーバのコード
サーバ側については [今更だけど gRPC を試してみた](https://qiita.com/chyiro/items/41020e0314414c57ec21) の記事で書かれているのでここでは割愛します。

## クライアント( Android )のコード

### `helloworld.proto`から生成されたコード

`helloworld.proto`から生成されたコードは `gradlew` でビルドされ、`app/build/generated`ディレクトリ内の以下のロケーションに配置されます。

![generated/source/proto/debug(/grpc/io.grpc.examples.helloworld & /javalite/io.grpc.examples.helloworld)](https://qiita-image-store.s3.amazonaws.com/0/123264/2e248960-6f95-eb68-6d4f-15b53d3be4f7.png)

### サンプルの実装コード
サンプルとして実装されているコードの方は以下のような感じです。(一部省略)

ちなみにOnClickListenerが実装されてないのでどうやってsendMessageメソッド呼んでるのかわからず探し回ってましたが、layoutのxmlから呼んでるようです。

```java:HelloworldActivity.java
public class HelloworldActivity extends AppCompatActivity {
    
    ...

    public void sendMessage(View view) {
        new GrpcTask().execute();
    }

    private class GrpcTask extends AsyncTask<Void, Void, String> {
        private String mHost;
        private String mMessage;
        private int mPort;
        private ManagedChannel mChannel;

        @Override
        protected void onPreExecute() {
            mHost = mHostEdit.getText().toString();
            mMessage = mMessageEdit.getText().toString();
            String portStr = mPortEdit.getText().toString();
            mPort = TextUtils.isEmpty(portStr) ? 0 : Integer.valueOf(portStr);
            mResultText.setText("");
        }

        @Override
        protected String doInBackground(Void... nothing) {
            try {
                // Channelを作成
                mChannel = ManagedChannelBuilder.forAddress(mHost, mPort)
                    .usePlaintext(true)
                    .build();
                
                // stubを作成
                GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(mChannel);

                // requestを作成、値を指定する
                HelloRequest message = HelloRequest.newBuilder().setName(mMessage).build();
                
                // sayHelloメソッドを実行、replyを受け取る
                HelloReply reply = stub.sayHello(message);

                return reply.getMessage();
            } catch (Exception e) {
                ...
            }
        }

        @Override
        protected void onPostExecute(String result) {
            try {
                mChannel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            mResultText.setText(result);
        }
    }
}
```

通信は非同期処理として実装する必要があるのでAsyncTaskを継承したGrpcTaskクラスを作成しています。

非同期処理を行なっているdoInBackgroundメソッド内を見てみると、

1. Channelの作成
2. stubの作成
3. requestを作成
4. 実行

という流れになっており、クラスのメソッドを呼び出すのと同じようにsayHelloメソッドにrequestを引数として指定するだけで、通信してreplyが返ってくるという処理を実現できているのがわかると思います。

# 終わりに

今回初めてgRPCを触ってみた感じとしては、RESTよりかなりシンプルな実装ができるように感じました。
通信部分をメソッドの形で直感的に書けるのはとてもありがたいと思いました。

今後projectの方で実装しながらさらに理解を深めていければと思います。

# 参考情報

- [gRPC](https://grpc.io/)
- [remote procedure call](https://ja.wikipedia.org/wiki/%E9%81%A0%E9%9A%94%E6%89%8B%E7%B6%9A%E3%81%8D%E5%91%BC%E5%87%BA%E3%81%97)
- [Protocol Buffers](https://developers.google.com/protocol-buffers/)
- [gRPCって何？](https://qiita.com/oohira/items/63b5ccb2bf1a913659d6#%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88)
- [今更だけど gRPC を試してみた](https://qiita.com/chyiro/items/41020e0314414c57ec21)
- [AndroidでgRPC+protobuf
](https://qiita.com/kobaken0029/items/8e2945f6b4ed6a2fa5ae)
- [ProtocolBuffersについて調べてみた
](https://qiita.com/aiueo4u/items/54dc5dd8c4772253634c)

----------------------------
[^1]: https://github.com/david-cao/gRPCBenchmarks
[^2]: https://google.github.io/flatbuffers/md__benchmarks.html