8
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

WiFi DirectライブラリSalut

Last updated at Posted at 2016-05-12

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を実装します。このコールバック処理は呼び出し元と同じスレッドで発生します。
そして、SalutDataReceiverSalutServiceDataオブジェクトを作成します。

    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を一度オフにしてオンにすると直った…と思ったら次は動かなかったり……

実用的に使えるようになるまでにはもう少しかかるのかな?と思いました。

8
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?