https://github.com/markrjr/Salut/blob/master/README.md の2016/05/12時点の日本語訳です。
Salut
SalutはAndroidのWiFi Direct APIのラッパーです。Salutを使用する前に下記のドキュメントをざっと読んでおくことをおすすめします。
Recommended Reading
このライブラリはAPI 16 (Android 4.1 Jelly Bean)以上をサポートします。技術的にはWiFi DirectはAndroid 4.0から使用できますが、4.1以上の方が信頼性が高いです。
注意
このライブラリは現在ベータ状態であり、APIや機能は変更される可能性があります。
なぜこの名前?どんな意味が?
Salutはフランス語の挨拶で、helloやgoodbyeみたいなものです。なぜこの名前かというと、iOSで使われているAppleの同じような技術にBonjourという名前が付いているからです。
Dependencies
このライブラリは以下のライブラリに依存します。
**LoganSquareはアプリにも含める必要があります。**そのために、プロジェクトのbuild.gradeを修正します。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
apt 'com.bluelinelabs:logansquare-compiler:1.3.4'
compile 'com.bluelinelabs:logansquare:1.3.4'
}
インストール
JitPackを使ってインストールするのが簡単です。
使い方
Sample Activity
利用手順
まず、次のパーミッションをAndroidManifest.xmlに追加します。
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- AndroidではINTERNETパーミッションがないとソケットをオープンできません -->
Andorid 6.0以上では、上記のパーミッションは自動で付与されます。(詳しくはこちら)
次に、データを受信したいクラスにSalutDataCallback
を実装します。このコールバック処理は呼び出し元と同じスレッドで発生します。
そして、SalutDataReceiver
とSalutServiceData
オブジェクトを作成します。
SalutDataReceiver dataReceiver = new SalutDataReceiver(myActivity, myActivity);
SalutServiceData serviceData = new SalutServiceData("sas", 50489, superAwesomeUser.name);
SalutDataReceiver
は二つの引数を取ります(Activity activity, SalutDataCallback dataCallback)
。上記の例では、ActivityがSalutDataCallback
を実装しています。なのでmActivity
を二度渡しています。ここにActivityを渡すとSalutが自動的にBroadcastReceiverを登録・解除するようになります。
SalutServiceData
はサービス名、ポート、インスタンス名を受け取ります。サービス名はユーザーに表示される読みやすい文字列です。暗号的な文字列にはしないことをおすすめします。Android 5.0未満をサポートする場合、サービス名と表示名は短めの文字列を使用してください。これはシステム自体の制限です。
最後に、Salut
インスタンスを作ります。
Salut network = new Salut(dataReceiver, serviceData, new SalutCallback() {
@Override
public void call() {
Log.e(TAG, "Sorry, but this device does not support WiFi Direct.");
}
});
ホストとして動作するかそうでないかを保持する変数を用意しておくのは良い方法です。isRunningAsHost
という論理型がフレームワークで提供されており、ホストとして動作しているかどうかを判断するときに使われます。しかしこれはグループオーナーとして接続しているかどうかやサーバーが動作しているかどうかとは関係ありません。
Working with services
インスタンスを作成したら、WiFi direct サービスを作成したり探したりできます。
ホスト側
network.startNetworkService(new SalutDeviceCallback() {
@Override
public void call(SalutDevice device) {
Log.d(TAG, device.readableName + " has connected!");
}
});
デバイスが接続して登録されると、コールバックが呼び出されます。registeredClients
で登録されている全てのデバイスにアクセスできます。
クライアント側
サービスを探すにはいくつかの方法があります。Salutは同じタイプのサービスにのみ接続します。
network.discoverNetworkServices(new SalutDeviceCallback() {
@Override
public void call(SalutDevice device) {
Log.d(TAG, "A device has connected with the name " + device.deviceName);
}
}, false);
//または
network.discoverNetworkServices(new SalutCallback() {
@Override
public void call() {
Log.d(TAG, "All I know is that a device has connected.");
}
}, true);
どちらのメソッドにも、論理型の引数を渡します。これはコールバックが繰り返し呼び出されるかどうかを指定します。もしtrueだった場合は、デバイスが見つかる度にコールバックを呼び出します。falseなら、最初のデバイスを発見した時に一度だけコールバックを呼び出します。true,falseどちらを渡したかに関係なく、stopServiceDiscovery()
を呼び出すまでデバイスを探し続けます。
もう一つ、discoverNetworkServicesWithTimeout()
メソッドがあります。これは名前が示すとおり、指定した時間だけデバイスを探して自動的にstopServiceDiscovery()
を呼びます。foundDevices
フィールドで見つかった全てのデバイスを参照できます。
network.discoverNetworkServicesWithTimeout(new SalutCallback() {
@Override
public void call() {
Log.d(TAG, "Look at all these devices! " + network.foundDevices.toString());
}
}, new SalutCallback() {
@Override
public void call() {
Log.d(TAG, "Bummer, we didn't find anyone. ");
}
}, 5000);
デバイスがホストを見つけたら、registerWithHost()
を呼び出します。
network.registerWithHost(possibleHost, new SalutCallback() {
@Override
public void call() {
Log.d(TAG, "We're now registered.");
}
}, new SalutCallback() {
@Override
public void call() {
Log.d(TAG, "We failed to register.");
}
});
このメソッドは実際にWiFi Directでデバイス同士を接続します。フレームワークは通常のソケットでデータを交換します。デバイスはunregisterClient
がクライアント側で呼び出されるか、stopNetworkService
がホスト側で呼び出されるまで接続し続けます。
データを作る
LoganSquare がライブラリの中でデータのシリアライゼーションを行います。LoganSquareは単純な文字列をシリアライズできないので、ラッパーオブジェクトを作成する必要があります。
@JsonObject
public class Message{
/*
* Annotate a field that you want sent with the @JsonField marker.
*/
@JsonField
public String description;
/*
* Note that since this field isn't annotated as a
* @JsonField, LoganSquare will ignore it when parsing
* and serializing this class.
*/
public int nonJsonField;
}
データを送る
クライアントがホストに登録されると、クライアントにデータを送ることができます。失敗した場合のコールバックも指定します。
全てのデバイスにデータを送るには、以下のようにします。
Message myMessage = new Message();
myMessage.description = "See you on the other side!";
network.sendToAllDevices(myMessage, new SalutCallback() {
@Override
public void call() {
Log.e(TAG, "Oh no! The data failed to send.");
}
});
ホストだけが全てのデバイスのアドレスを知っているので、上記メソッドを呼び出せます。将来的にクライアントからも同じように全てのデバイスにデータを送れるようになる予定です。今のところはまずデータをホストに送ってから上記メソッドでその他のデバイスに伝達するという方法になります。
特定のデバイスにデータを送るには、以下のようにします。
Message myMessage = new Message();
myMessage.description = "See you on the other side!";
network.sendToDevice(deviceToSendTo, myMessage, new SalutCallback() {
@Override
public void call() {
Log.e(TAG, "Oh no! The data failed to send.");
}
});
network.sendToHost(myMessage, new SalutCallback() {
@Override
public void call() {
Log.e(TAG, "Oh no! The data failed to send.");
}
});
データを受け取る
SalutDataCallbackインターフェースを実装するクラスはonDataReceived(Object data)
をオーバーライドします。
データはシリアライズされた文字列で送られ、このメソッドではString
として送られます。LoganSquareで実際のオブジェクトに解析する必要があります。
データは文字列で受け取ります。オブジェクトを解析する処理は次のようになります。
@Override
public void onDataReceived(Object data) {
Log.d(TAG, "Received network data.");
try
{
Message newMessage = LoganSquare.parse((String)data, Message.class);
Log.d(TAG, newMessage.description); //See you on the other side!
//Do other stuff with data.
}
catch (IOException ex)
{
Log.e(TAG, "Failed to parse network data.");
}
}
クリーニング
@Override
public void onDestroy() {
super.onDestroy();
if(MyApp.isHost)
network.stopNetworkService(true);
else
network.unregisterClient(true);
}
冒頭で説明した、ホストとして動作するかそうでないかを保持する変数を使用しています。
ホスト
**ホスト側ではstopNetworkService
を呼ばなければいけません。**WiFiを無効化するかどうかを指定する論理型の引数を渡します。
クライアント
**クライアントサイドではunregisterClient
を呼ばなければいけません。**WiFiを無効化するかどうかを指定する論理型の引数を渡します。解除処理の成功・失敗時のコールバックを指定することもできます。
ライセンス
MIT
あとがき
ここまでがんばって訳して書いてみたんですけど、いざ使ってみるとWi-Fi DirectのAPIは意味不明に動かないことが多くてつらいです。同じコードが1回目は動くのに2回目が動かなかったり、Wi-Fiを一度オフにしてオンにすると直った…と思ったら次は動かなかったり……
実用的に使えるようになるまでにはもう少しかかるのかな?と思いました。