[Android][iBeacon] Android Beacon Library パラっと解説 その2 [領域監視]

2015.03.13

こんにちは。こむろです。札幌はまだ雪が降っています。春の兆しで暖かくはなってきていますが、東京ほどではないですね。花粉がほとんどないのでとても快適です。

前回からの続きです。ひと通り、プロジェクトのセットアップが完了しました。ここからが本番になります。 今回は、Beaconの範囲内に入場したか退場したかを検知する、RegionMonitor 機能を実装します。

BeaconManager

まずは、Android Beacon Libraryで重要なクラスから見てみましょう。BeaconManagerクラスは、その名の通りBeaconの情報を管理するクラスになります。

役割

役割としては、Beacon検知処理の開始、終了や監視対象の設定、スキャン間隔の設定などの様々な設定から操作までを一挙に引き受けます。主な機能は以下のとおり。他にも色々ありますが、とりあえず下記を見ておけば実装はできます。

  • 領域監視(RegionMonitor)の開始、終了
  • 領域監視の対象設定
  • 距離測定(Ranging)の開始、終了
  • 距離測定の対象設定
  • スキャン間隔の設定

BeaconManagerは、staticメソッドで定義されているBeaconManager.getInstanceForApplication() を使い、シングルトンインスタンスとして扱います。決してコンストラクタでインスタンスを生成してはいけません。出来ませんが。 恐らくBluetoothというハードウェアを制御する関係上、複数のインスタンスで同時に操作をされると都合が悪いからと推測されます。

BeaconManagerをインスタンス化する

BeaconManagerをインスタンス化して管理する場合は、ActivityやApplicationといった、1つしか存在しないクラスの中に記述するのが正しいかと思います。Fragmentなど複数のインスタンスが生成されたりする可能性のある場所での管理は極力避けたほうが良いかと思います。今回は、サンプル通りActivityの中で管理します。

MyActivity.java

private BeaconManager beaconManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);
    if (savedInstanceState == null) {
        getFragmentManager().beginTransaction()
                .add(R.id.container, new PlaceholderFragment())
                .commit();
    }

    // staticメソッドで取得
    beaconManager = BeaconManager.getInstanceForApplication(this);
}

BeaconManagerのサービスと接続する

BeaconManagerに接続するためには、Interfaceを実装し、BeaconManagerへ設定してやる必要があります。それが、BeaconConsumer です。今回はActivityにそのまま実装していまいましょう。

MyActivity.java

public class MyActivity extends Activity implements BeaconConsumer {
    // (Snip)
    
    @Override
    public void onBeaconServiceConnect() {

    }
}

BeaconManagerのサービス起動

BeaconManagerは、Bind可能なServiceとして実装してあるようです。サービスの開始、終了を明示的に行う必要があります。bind(), unbind()の引数には、BeaconConsumerIFを要求しています。今回はActivityにそのまま実装してしまったので、Activityのインスタンスを渡してあげればOKです。

@Override
protected void onPause() {
    super.onPause();
    beaconManager.unbind(this);
}

@Override
protected void onResume() {
    super.onResume();
    beaconManager.bind(this);
}

これでBeaconManagerサービスへの接続まで完了しました。

領域監視を設定

これでようやく、ビーコン情報を監視する準備が整いました。さっそく領域監視の設定を行いましょう。領域監視の詳しい説明はこちら をご参照ください。

領域監視の通知を受け取るには、BeaconManager#setMonitorNotifier() を呼び出す必要があります。引数には領域への入場・退場を検知した時に呼び出すために必要なMonitorNotifierというListenerを設定します。

@Override
public void onBeaconServiceConnect() {
    beaconManager.setMonitorNotifier(new MonitorNotifier() {
        @Override
        public void didEnterRegion(Region region) {
            // 領域への入場を検知
        }

        @Override
        public void didExitRegion(Region region) {
            // 領域からの退場を検知
        }

        @Override
        public void didDetermineStateForRegion(int i, Region region) {
            // 領域への入退場のステータス変化を検知
        }
    });
}

3つのメソッドが定義されます。

public void didEnterRegion(Region region):このメソッドは設定した領域への入場を検知した際に実行されます。引数にあるregion には入場を検知した領域の情報が入力されています。

public void didExitRegion(Region region):このメソッドは設定した領域からの退場を検知した際に実行されます。引数にあるregion には退場を検知した領域の情報が入力されています。

public void didDetermineStateForRegion(int i, Region region):このメソッドは設定した領域への入退場のステータスの変化を検知した際に実行されます。引数にあるiはステータスを、region には入退場を検知した領域の情報が入力されています。そのため、Logを仕込むとdidDetermineStateForRegion → didEnterRegion or didExitRegion という順序で実行されているのが確認できます。

ここで注意すべきなのは、Regionの情報はどのビーコン情報の領域に入場したかという個別の情報は持ちません。これについては、今は説明をしません。もう少し先に進んだ段階で説明します。

監視対象を設定して監視を開始

ようやくビーコン情報を監視できる準備が整いました。どのビーコンを対象にしたいかを設定して監視を開始しましょう。

beaconManager.setMonitorNotifier(new MonitorNotifier() {
    @Override
    public void didEnterRegion(Region region) {
        // 領域への入場を検知
        Log.d("Beacon", "ENTER Region.");
    }

    @Override
    public void didExitRegion(Region region) {
        // 領域からの退場を検知
        Log.d("Beacon", "EXIT Region. ");
    }

    @Override
    public void didDetermineStateForRegion(int i, Region region) {
        // 領域への入退場のステータス変化を検知
        Log.d("MyActivity", "DetermineState: " + i);
    }
});

try {
    // ビーコン情報の監視を開始
    beaconManager.startMonitoringBeaconsInRegion(new Region("unique-id-001", null, null, null));
} catch (RemoteException e) {
    e.printStackTrace();
}

領域監視を開始するメソッドはBeaconManager#startMonitoringBeaconsInRegion() になります。引数には監視する領域を表現するRegion というクラスを指定します。Regionには4つほど引数を指定する必要があります。

監視対象。Region

今回は、iBeaconを対象にするので「UUID」「major」「minor」を指定します。これらはそれぞれIdentifierというクラスに変換し、第2引数から第4引数にかけて指定します。 uniqueIdという文字列を第1引数で指定しますが、これはBeaconManagerが監視する対象を判別するために利用するIDです。同じuniqueIdで指定された場合は、すでにそのIDが監視対象として存在する場合は監視対象のデータの更新が実行されます。このIDが異なれば全て別の監視対象として扱われます。

今回は各Identifierの指定は全てnullです。「UUID」「major」「minor」を全て指定しない形での監視対象設定となるので、iBeaconであればどれでも監視の対象になります。ここはiOSと大きく異るところです。 iOSでは最低でも「UUID」の指定は強制されます。これにより周囲にあるiBeaconを全て監視対象にするといった、ワイルドカード指定が出来ません。

アプリを起動させる

アプリを立ち上げてみましょう。ずらずらとBLEスキャンを実行しているログが出てくると思います。iBeaconの領域内に入ればdidEnterRegion()が実行され、ログが出力されるはずです!

03-13 20:20:00.469  28315-28315/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ stopLeScan()
03-13 20:20:00.469  28315-28315/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ startLeScan(): null
03-13 20:20:00.469  28315-28328/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ onClientRegistered() - status=0 clientIf=7
03-13 20:20:01.579  28315-28315/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ stopLeScan()
03-13 20:20:01.579  28315-28315/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ startLeScan(): null
03-13 20:20:01.579  28315-28327/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ onClientRegistered() - status=0 clientIf=7

待てど暮らせどRegionにENTERする様子がありません。実はひとつiBeaconを認識するには設定が足りていません。 次回はiBeaconを認識させるためのキーワード「BeaconParser」について書いていきます。

今回までのソースコードはこちらです。beacon_002というブランチが該当します。

github - BlogSampleAndroidBeaconLibrary

参照