はじめに
この記事は、今携わっているProjectでサーバとクライアント間の通信をgRPCを使って行おうということになり、AndroidへgRPCを実装するチュートリアルをやりながら色々と調べたことを書いたものになります。
そもそもgRPCとは?
gRPCって何? の概要でも述べられている通り、Googleが開発した通信プロトコルのことです。 Remote procedure call というクライアントからサーバのメソッドを直接呼ぶことでサーバとクライアントが通信を行え、またその逆もできるという技術の実現のために開発されました。
従来のREST APIではJsonで情報をやりとりすることが主でしたが、サーバとクライアントで別々の実装を行うため、実装コストが大きくなってしまっていました。
しかし、gRPCは.proto
ファイルにAPI仕様を定義するだけで様々なプログラミング言語の実装を生成することができるのでサーバ、クライアント双方で実装コストの減少に繋がります。
また、Protocol Buffers によって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
まずは 公式のクイックスタートガイド を見ながらHello 〇〇(任意の入力文字列)を返すアプリを動かすとこまでやってみます。
サンプルのダウンロード
Githubリポジトリからサンプルコードをgit clone
でダウンロードします。
$ # 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
ディレクトリ内で以下を実行してサーバをコンパイルします。
$ ./gradlew installDist
そしてサーバを実行させておきます。
$ ./build/install/examples/bin/hello-world-server
Android端末をデバッグモードで接続しておき、adbで以下を実行しておきます。
adb reverse tcp:8080 tcp:50051
サーバを実行させたターミナルとは別のターミナルを開いて、クライアントをコンパイル、実行します。
$ cd android/helloworld
$ ./gradlew installDebug
成功すれば端末の方にアプリがインストールされ、以下のような画面が表示されるはずです。
Hostに localhost
、Portに 8080
(adbで50051とリバースしたので)を入力し、messageに適当に送信したい文字を入力(例えば world
)を入力し、以下のようにレスポンスが帰ってきたらサーバとの通信成功です!
コードについて
とりあえず動きはしましたが、では、実際コードの中身はどうなっているんでしょうか。
次は .proto
ファイル、サーバのコード、クライアント(Android)のコードについて見てみます。
helloworld.proto
と生成コード
// メソッドの定義
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// リクエストのデータ構造の定義
message HelloRequest {
string name = 1;
}
// レスポンスのデータ構造の定義
message HelloReply {
string message = 1;
}
helloworld.proto
ファイルには上でも書いたようにAPI仕様を記述します。
具体的にはメソッドと使用するデータのデータ構造を記述しておきます。
詳しい記述方法については Protocol Buffers の Language Guide (proto3) を参照してください。
この helloworld.proto
ファイルを各言語ごとにコンパイルすることで言語に応じたgRPCのインターフェイスが生成されます。
サーバのコード
サーバ側については 今更だけど gRPC を試してみた の記事で書かれているのでここでは割愛します。
クライアント( Android )のコード
helloworld.proto
から生成されたコード
helloworld.proto
から生成されたコードは gradlew
でビルドされ、app/build/generated
ディレクトリ内の以下のロケーションに配置されます。
サンプルの実装コード
サンプルとして実装されているコードの方は以下のような感じです。(一部省略)
ちなみにOnClickListenerが実装されてないのでどうやってsendMessageメソッド呼んでるのかわからず探し回ってましたが、layoutのxmlから呼んでるようです。
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メソッド内を見てみると、
- Channelの作成
- stubの作成
- requestを作成
- 実行
という流れになっており、クラスのメソッドを呼び出すのと同じようにsayHelloメソッドにrequestを引数として指定するだけで、通信してreplyが返ってくるという処理を実現できているのがわかると思います。
終わりに
今回初めてgRPCを触ってみた感じとしては、RESTよりかなりシンプルな実装ができるように感じました。
通信部分をメソッドの形で直感的に書けるのはとてもありがたいと思いました。
今後projectの方で実装しながらさらに理解を深めていければと思います。
参考情報
- gRPC
- remote procedure call
- Protocol Buffers
- gRPCって何?
- 今更だけど gRPC を試してみた
- AndroidでgRPC+protobuf
- ProtocolBuffersについて調べてみた