Edited at

ActionCableをAndroidからOkHttpで使ってみる

More than 1 year has passed since last update.


動機

RailsのWebアプリケーションを作る際に、ActionCableでお手軽にWebSocketを行いたい。

けど、今後の拡張性を考えると、Android/iOSともWebSocketでつなげる必要がある。

とりあえず、Androidで出来ればiOSでも出来るだろう。


先に結論

とりあえず、JSONのフォーマットさえちゃんとしたら、Androidでも動くっぽい。

ただ、 disable_request_forgery_protectionしてるので、本番では問題あるかも?

クライアント側でもある程度実装が必要っぽいので、本番では使わないかなぁ。

(素のWebSocketでの実装・その他の実装も比較する必要はありますが。)


前提


サーバサイド

Ruby: 2.3.1

Rails: 5.0.0.beta1 (バージョン上げようとしたらいくつかエラーが出たので断念。)

https://github.com/JunichiIto/campfire を一部改変して利用しました。(heroku readyなのがありがたい)


クライアント

Android: 4.2.2

okhttp: 3.4.2


サーバサイドの準備


サンプルから改変

https://github.com/JunichiIto/campfire をcloneしてくる。

.ruby-versionGemfileに記載されているRubyのバージョンを、2.3.1に変更。

config/environments/production.rb でrequest originが https?で始まるものに限定していたので、その指定すら無くすようにしました。

-  config.action_cable.allowed_request_origins = [ /https?:\/\/.*/ ]

+ # config.action_cable.allowed_request_origins = [ /https?:\/\/.*/ ]
+ config.action_cable.disable_request_forgery_protection = true

(これって、http headerとかで見ているのでしょうか?本来であれば、何かしら制限しておく必要がある?)


herokuにデプロイ

READMEに書いてある通り、herokuにアップロードする。

heroku open するとURLが分かるので、後でAndroidの実装に組み込む。


クライアントの実装


ライブラリのインポート

compile 'com.squareup.okhttp3:okhttp:3.4.2'

compile 'com.squareup.okhttp3:okhttp-ws:3.4.2'

(ただ、 https://github.com/square/okhttp/pull/2852 を見ると、次のバージョンとかでいろいろ変わるかも?)


送受信の実装

今回は、ActivityのonCreateで接続するようにしました。

結果はログに出るだけです。


MainActivity.java

package hm.orz.chaos114.websocketsample;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.ws.WebSocket;
import okhttp3.ws.WebSocketCall;
import okhttp3.ws.WebSocketListener;
import okio.Buffer;

public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Request request = new Request.Builder()
.url("wss://INPUT_YOUR_DOMEIN.herokuapp.com/cable") // INPUT_YOUR_DOMEINは、heroku openしたときのドメインを指定してください。
.build();

OkHttpClient client = new OkHttpClient.Builder()
.build();
WebSocketCall call = WebSocketCall.create(client, request);
call.enqueue(new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
Log.d(TAG, "onOpen");

try {
webSocket.sendMessage(RequestBody.create(WebSocket.TEXT, "{\"command\":\"subscribe\",\"identifier\":\"{\\\"channel\\\":\\\"RoomChannel\\\"}\"}"));
webSocket.sendMessage(RequestBody.create(WebSocket.TEXT, "{\"command\":\"message\",\"identifier\": \"{\\\"channel\\\":\\\"RoomChannel\\\"}\",\"data\":\"{\\\"message\\\":\\\"sample message!!!\\\",\\\"action\\\":\\\"speak\\\"}\"}"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public void onFailure(IOException e, Response response) {
Log.d(TAG, "onFailure " + response, e);
}

@Override
public void onMessage(ResponseBody message) throws IOException {
Log.d(TAG, "onMessage " + message.string());
}

@Override
public void onPong(Buffer payload) {
Log.d(TAG, "onPong");
}

@Override
public void onClose(int code, String reason) {
Log.d(TAG, "onClose");
}
});
}
}


起動すると、RoomChannelに接続し、sample message!!!というテキストを送信します。

heroku openしたブラウザを確認すると、メッセージが追加されていくはずです。


仕組みの考察メモ



  • ApplicationCable::Channel を継承したクラス名が、Channel名となる。

  • Channel名を指定して、subscribeコマンドを発行すると、受信可能状態になる。


  • ApplicationCable::Channel を継承したクラスのメソッドが、Action名になる。

  • Action名を指定して、messageコマンドを発行すると、メッセージを送信できる。

  • 部屋を分ける場合は、 https://github.com/rails/rails/tree/master/actioncable#passing-parameters-to-channel このへんを参考にしたら出来るかも?


参考

いろいろ実装したあとで、 https://github.com/hosopy/actioncable-client-java を見つけました。

コレ使えばよかったのかも。。