retrofitでGETやPOSTする毎日.そんなときはsocket.io使って双方向にリアルタイム通信しちゃおうか
はじめに
socket.ioというものがあります.
Androidで外部と通信するとき,ほとんどがRetrofitつかってAPIたたいてJson取得して〜みたいな流れだと思います.
クライアントがサーバに対してなにか要求してそれに反応してサーバが値を返したり,何らかの処理をする場合はそれでじゅうぶん.
アプリ開発する中でそれが一番よくやることだと思います.
先日のハッカソンでは,違う実装の要求があり,socket.ioというライブラリ?概念?を用いて実装しました.
参加したハッカソンについては記事を書きました.
batch.hatenablog.com
どういうユースケースでsocket.ioをつかうのか
僕がsocket.ioをどのようなユースケースに対応するために用いたのかを簡単に説明したいと思います.
登場人物はクライアント(Android),クラウドサーバ,クライアント(Webアプリ)です.僕たちがやりたかったのは,ざっくり説明すると,Android端末内のセンサを使い,どのような動きをしたかという操作情報をクラウドサーバを経由してWebアプリに通知して,その操作情報に応じてWebアプリ側の処理を動的に変えるというもの.
なので,Webアプリ側ではクラウドサーバから送られてくるAndroid端末の操作情報をサーバのようにずっと待っている状態です.
クライアントだけどサーバみたいにずっとリクエスト待っているってあんまやらないですよね?こういうときにsocket.io使うみたいです.
実装してみた僕の勝手な解釈としては,クライアントでもサーバのようにずっとリクエストを待ち続けて,リクエストが来たときに反応して何かしら処理をしたいときにsocket.ioを使うといった感じです.
一般的な例でいうと,チャットアプリなんかでこういうのが使われるらしいです.
なので,図で見ると全然双方向な感じもリアルタイムな感じも伝わりませんが,チャットアプリなど想像するとユースケースがわかりやすいかなと思います.
ちなみに,socket.ioと別にwebsocketとかってやつがいてまたそれは別です.
github.com
別というか,socket.ioがwebsocketのめんどくさいとこをいい感じにラップしてくれてるみたいらしい.
HTTPのGETとかいろいろめんどくさいことをRetrofitがいい感じに簡単にやってくれてるみたいな感覚.
一応,websocketもHTTPのプロトコル使って動いてほにゃらららって感じらしい.
詳しいことはわかりませんが,Androidでsocket.ioを使うやり方を解説していきたいと思います.
socket.ioつかう
Step1 build.gradle(Module: app)にかく
implementation "io.socket:socket.io-client:1.0.0"
Step2 アクセス先の設定
Activityにかいてゆく
まず,アクセス先のサーバのベースURLてきなものをグローバル変数でセットしてあげます.
今回はAWS使ってたのでそれっぽく例を書いときます.
private val socket = IO.socket("http://ec2-XXXXXXXXX.amazonaws.com")
Step3 コネクションはる
そして,onCreateでコネクト(コネクション張る的な感じ)してあげます.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) socket.connect() }
Step4 情報送信
そして次に,Activityにあるとあるボタンを押したときに,socket.ioでなにかしらリクエストするコードを書いていきます.
ここでちょっと,socket.ioのお勉強しなければなりません.
socket.ioではRetrofitなどのようにエンドポイントがサーバに用意されていてそこに向かってリクエスト投げるという感じのことはしません.
毎回アクセスしに行くのは,さきほどsocketに設定したURLに向かってします.
その代わりに,サーバ側ではキーみたいなものを用意して,そのキーに向かってアクセスが来たとき何かしらの処理をするという風な感じで待ち構えています.
そして,socket.ioではemitとonという概念があります.
emitとonは一つのペアの関係をもっています.
サーバ側がAというキーでemitで待っているときはクライアントは同じAというキーでonでアクセスしにいきます.
emitで待っているサーバに対してemitでアクセスしにいくことはしません.emitとonがペアです.
以下で,pingというキーでemitで待機しているサーバに対してボタンを押されたときにonでアクセスしにいく例を紹介します.
実装したコードはこんな感じ.
class MainActivity : AppCompatActivity() { private val socket = IO.socket("http://ec2-XXXXXXXXX.amazonaws.com") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_connect) connectButton.setOnClickListener { GlobalScope.launch { connection() } } } private fun connection() { val TAG = "batchSocketIO" socket .on(io.socket.client.Socket.EVENT_CONNECT) { Log.d(TAG, "CONNECT!") } .on("ping") { try { val json = it[0] as JSONObject } catch (e: Exception) { Log.d(TAG, e.toString()) } } .on(io.socket.client.Socket.EVENT_DISCONNECT) { Log.d(TAG, "DISCONNECT!") } } }
connection関数の中の, .on(io.socket.client.Socket.EVENT_CONNECT)はお作法的なものです.
その次にpingというキーで待っているemitにonでアクセスしにいきます.今回の場合,結果としてpongというStringが返ってくるようになっており,それをval json = it[0] as JSONObjectで受け取ってます.
最後の.on(io.socket.client.Socket.EVENT_DISCONNECT)もお作法的なものだと思っています.詳しくはわかっていません.
番外編 JSONデータ渡したいとき
今回,Android側でどのような動きをしたのかという情報をJsonにしてサーバに送って,サーバがそれを受け流しWebアプリに通知するということをしました.
そのときにAndroid側で実装したものを共有したいと思います.
val body = JSONObject( """{ |"hoge":"hogehoge", |"direction":$direction |}""".trimMargin() ) socket .emit(endpoint, body, Emitter.Listener { })
JSONObjectでまず,送信したい情報を定義します.
そして,socketでサーバ側が今度はonで待っているのでemitでjsonbodyを混ぜて送信しています.
これがWebアプリ側に送信されてdirectionの向きに応じてWebアプリの処理が動的に変わるというわけです.
以上,にわかながら得た知見を詰めて書いてみました.
一応,ハッカソンで実装したリポジトリ貼っておきます.覗いてみてください.(設計などは全く気にしないで書いてるのでヤバいです)
github.com
なにかご指摘ある場合はぜひぜひTwitterでもブログのコメントでもお願いします.
ミニマリストエンジニアbatchでした.
twitter.com