この記事は NewsPicks Advent Calendar の14日目の記事です。
NewsPicks で Android エンジニア兼、サーバサイドエンジニア兼、小さなチームのリーダをしている (何でも屋) の kozmats といいます。
弊社は基本的に自由に副業が可という事もあり、自分もいくつか友人のプロジェクトを手伝ったり、個人的に仕事を請け負ったりする事があるのですが、その中の一つに Android で WebSocket どうしても扱わないといけない事がありました。過去 WebSocket を使うアプリを実装した事がなく少し実装までの調査に手間取ってしまった為、備忘録の意味も込めて記事にしてみました。
そもそも WebSocket とは…?アプリエンジニアが日々アプリを作っていく中で、REST や GraphQL は扱った事があるが、(使用しているライブラリの中で実は WebSocket が使われていた…なんて事はあるかもしれませんが) 実は WebSocket を言葉としては知っているけども、アプリに実際に組み込んだ事は無い。という人が多いのではないでしょうか?(偏見かな。)
色々細かい仕様は RFC なりを読んでもらえれば…とは思うのですが、マサカリを投げつけられる事を恐れずざっくり言うとその名の通り通常の GET , POST のように Sever - Client 間で Request を行なって Response を貰い1つのシーケンスが終了。というものではなく、 「HTTPで(厳密にはハンドシェイクだけなはずだけど) 双方向通信 を行なえるようにしたもの」という認識で良いと思います。
WebScoket を使う事で、比較的低コストでハンドシェイクを維持したまま、双方向通信を実現することができる。(別に双方向ではなくとも、サーバサイドからアプリに対してPush型の通信を行なうことができる)という訳です。
Android で WebSocket…?仕組みはまぁ。ざっくり分った。それじゃぁ実際に Android にどう組込むか。REST なら Retrofit 使えば良いし、GraphQL なら Apollo Client を使えばいける。別に通信部分を丸々0から作れば動かせるとは思うけど、それは余りに車輪の再開発感が強い。何かいいライブラリとかは無いものか…。できれば RxJava(Kotlin) か LiveData か Coroutine を組合せて使いたい。。と思い幾つか調べた結果、以下の3つ候補を見付けました。
1、2 の候補調べていた時は、結構面倒なコードを書かないとさくっと REST のように実装する事は難しいかな…と思っていたのですが、3 の Scarlet を見て思いました。 これは勝った。 と。 何故なら Retrofit inspired と謳っているだけあり、かなり Retrofit に似たイメージで実装を行なえ、Retfofit同様に RxJava2 や coroutines を使えたり、JSON Parser として Jackson や moshi (試してないですが Protocol Buffersも)使えるなどかなり Android エンジニアフレンドリーな作りになっている印象を持ったからです。
とりあえず Scarlet で書いてみる今回、自分が RxJava に慣れているという事もあり、Scarlet + moshi + OkHttp + RxJava2(RxKotlin) の構成で書いてみました。実際の業務でも、Scarlet の部分が Retrofit になっている構成でアプリを作られている方も多いのではないでしょうか。
build.gradle へ追加既に Retrofit + RxJava2 + moshi で動作している環境であれば、以下の項目を加えるだけです。
// Scarlet
implementation "com.tinder.scarlet:scarlet:${versions.scarlet}"
implementation "com.tinder.scarlet:message-adapter-moshi:${versions.scarlet}"
implementation "com.tinder.scarlet:websocket-okhttp:${versions.scarlet}"
implementation "com.tinder.scarlet:stream-adapter-rxjava2:${versions.scarlet}"
※ 0からアプリを実装する場合は RxJava, Moshi を使う場合は、それぞれのライブラリに対応する implementation を記述する必要はあります。
Scarlet client の準備
Retrofit をインスパイアしているだけあり、この辺りのコードもそっくりでこれだけです。
val scarlet = Scarlet.Builder()
.webSocketFactory(http.client.newWebSocketFactory(okHttpClient.newWebSocketFactory("wss://xxxxx")) // WebSocket のエンドポイントを指定
.addMessageAdapterFactory(MoshiMessageAdapter.Factory(moshi = parser.client)) // JSON Parser として moshi を指定
.addStreamAdapterFactory(RxJava2StreamAdapterFactory()) // RxJava2 を扱えるように
.build()
実際には、Kodein や koin もしくは Dagger2 を使って OkHttp Client を Inject して使っています。
送受信する JSON に対応するクラスを定義する
仮に WebSocket のサーバサイドから Ticket に関する JSON が返却されるとした場合、対応するクラスはこんなイメージでしょうか。
typealias TicketId = Long
data class Ticket(
val id: TicketId,
val description: String
もし、クライアントサイドから WebSocket に何かを投げ付ける必要がある場合、その投げつける内容に対応したクラスも定義しておきます。
data class Command(
val cmd: String // 何らかのコマンドをサーバサイドに投げつける想定
Service を定義
Service という名前、 所謂 Android の Service と被ってしまい Android 的には使い辛い単語なのですが、これも Retrofit をインスパイアした為でしょう。Retfoti のように実際に WebSocket でやり取りする I/F を Service と称して記述します
interface HogeService {
@Receive
fun observeWebSocketEvent(): Flowable<WebSocket.Event>
@Send
fun sendCommand(command: Command)
@Receive
fun observeTicker(): Flowable<Ticker>
ここで初めて、Retfit には存在しなかった、 @Receive
@Send
というアノテーションが出現してきました。 そのままなので分りやすいとは思います。RxJava2 を扱えるようにした場合、Receive の返却が Flowable 型になっているという部分がポイントです。これにより Backpressure で受信するデータの流量を制御する事が可能になります。すばらしい。
実際にWebSocketのイベントをSubscribeしてみる
実際の使い方は、ここでも Retrofit に良く似ています。 WebSocket.Event
を受信する observeWebSocketEvent
は WebSocket のイベント全て受け付けてしまうので、使用時に、 filter してやる必要があります。 (例では、接続完了のイベントだけに filter をしている)
val service = scarlet.create<HogeService>() // Service 生成
// WebSocket サーバへの接続完了イベントを購読
service.observeWebSocketEvent()
.filter { it is WebSocket.Event.OnConnectionOpened<*> }
.subscribe({
// Connect 完了を通知
service.sendCommand(Command(cmd = "connected"))
service.observeTicker()
.subscribe({ ticker ->
// Ticket 情報受信時の処理
実際に、これだけのコードで WebSocket の実装を Android アプリに組込むことが可能になります。Socket 通信を生々しく実装するより 1000倍簡単。素晴しい。Tinder ありがとう。
Android エンジニアが WebScoket を (比較的)簡単に扱う方法をざっくり説明してみました。実際には MQTT などへ対応させる Update も行なおうとしている旨が記載されており、これから増えてくるであろう Android を組み込み機器に使った IoT 製品にも応用できるのではないでしょうか。