[Android][iBeacon] Android Beacon Library パラっと解説 その5 [バックグラウンド領域監視]

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。小室です。札幌は寒の戻りで冬に戻りました。
昨日(2015/03/24)は雪がちらついていました。

札幌駅近くの「蛯天」のかき揚げ丼。
IMG_20150324_124544

バックグラウンドで領域監視をしたい

前回まででひと通りのiBeaconを検出する機能を確認しました。今回はバックグラウンドで領域監視を行う機能についての実装を確認します。

AndroidはiOSと異なり比較的自由にバックグラウンド動作を実装することが出来ます。Serviceを実装するなどいくつか方法がありますが、今回はApplicationクラスを利用した方法について確認します。

Applicationクラスを定義する

バックグラウンドで動作させるためには、独自のApplicationクラスを実装する必要があります。今回は「BeaconApplication」という名前のクラスで実装していきます。

public class BeaconApplication extends Application {

    public static final String TAG = BeaconApplication.class.getSimpleName();

    private RegionBootstrap regionBootstrap;

    @Override
    public void onCreate() {
        super.onCreate();
    }
}

アプリケーション開始時に実行するため、AndroidManifest.xmlapplicationタグに作成したクラスを指定します。

AndroidManifest

AndroidManifest.xmlは以下のとおり。BLEを利用するので、Bluetooth周りのPermissionの許可も忘れずに。


<!-- Bluetooth周りのPermission -->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

<application
  android:name=".BeaconApplication"
  android:allowBackup="true"
  android:icon="@drawable/ic_launcher"
  android:label="@string/app_name"
  android:theme="@style/AppTheme" >
  <activity
      android:name=".MyActivity"
      android:label="@string/app_name"
      android:launchMode="singleInstance">
      <intent-filter>
          <action android:name="android.intent.action.MAIN" />
          <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
  </activity>
</application>

修正箇所は2箇所です。

Applicationクラスを指定

android:name=".BeaconApplication"

android:nameに、実装したApplicationクラスを指定します。これでアプリケーション起動時にBeaconApplicationが必ず実行されます。

ActivityのLaunchModeを指定

android:launchMode="singleInstance"

この指定は必ず行う必要があります。必ず単一のActivityインスタンスになるように指定します。

RegionBootstrapクラス

バックグラウンドで領域監視を行うためには、RegionBootstrapクラスを利用します。RegionBootstrapのコンストラクタでは、引数にBootstrapNotifierというインターフェースと、既に何度も出てきている、Regionクラスを要求します。

それぞれを準備していきましょう。

BootstrapNotifierインターフェースを実装

バックグラウンドで領域監視をするためには、BootstrapNotifierインタフェースを実装する必要があります。このインタフェースを実装することで、Activityを終了させても裏側で領域監視を実行することが可能です。今回の例では、BeaconApplicationクラスに実装させます。

public class BeaconApplication extends Application implements BootstrapNotifier {

    public static final String TAG = BeaconApplication.class.getSimpleName();

    private RegionBootstrap regionBootstrap;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void didEnterRegion(Region region) {
        // 領域に入場した
    }

    @Override
    public void didExitRegion(Region region) {
        // 領域から退場した
    }

    @Override
    public void didDetermineStateForRegion(int i, Region region) {
        // 入退場状態が変更された
    }
}

このままだとiBeaconのデータを受信できないのでParserを設定します。このあたりの解説はこちらです。

iBeaconのデータを受信できるようにParserを設定

public class BeaconApplication extends Application implements BootstrapNotifier {

    public static final String TAG = BeaconApplication.class.getSimpleName();

    // iBeaconのデータを認識するためのParserフォーマット
    public static final String IBEACON_FORMAT = "m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24";

    private RegionBootstrap regionBootstrap;

    private BeaconManager beaconManager;

    @Override
    public void onCreate() {
        super.onCreate();
        
        // iBeaconのデータを受信できるようにParserを設定
        beaconManager = BeaconManager.getInstanceForApplication(this);
        beaconManager.getBeaconParsers()
                .add(new BeaconParser().setBeaconLayout(IBEACON_FORMAT));
    }

    @Override
    public void didEnterRegion(Region region) {
        // 領域に入場した
        Log.d(TAG, "Enter Region");
        Intent intent = new Intent(this, MyActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }

    @Override
    public void didExitRegion(Region region) {
        // 領域から退場した
        Log.d(TAG, "Exit Region");
    }

    @Override
    public void didDetermineStateForRegion(int i, Region region) {
        // 入退場状態が変更された
        Log.d(TAG, "Determine State: " + i);
    }
}

動作の結果が分かるように領域に入場した際には、Activityを立ち上げるようにしました。さらに動作が確認できるようLogも追加してあります。これでバックグラウンドでiBeaconの領域監視を行う準備が整いました。

バックグラウンドの領域監視を開始

今までとはちょっと異なり、RegionBootstrapをインスタンス化することで領域の監視が開始されます。BeaconManagerが今まで全て監視の開始や終了を司っていましたが、バックグラウンドでの領域監視だけは少々異なるようです。BeaconManagerは出てきますが、今回はBeaconParserの設定のみしか行っていません。

@Override
public void onCreate() {
    super.onCreate();

    // iBeaconのデータを受信できるようにParserを設定
    beaconManager = BeaconManager.getInstanceForApplication(this);
    beaconManager.getBeaconParsers()
            .add(new BeaconParser().setBeaconLayout(IBEACON_FORMAT));

    // UUID, major, minorの指定はしない
    Region region = new Region("uuid-region-bootstrap-001", null, null, null);
    regionBootstrap = new RegionBootstrap(this, region);
}

今回も、Regionは、UUID, major, minorいずれも指定せずにiBeaconであれば全て取得するように設定しています。

実行結果

起動した後、すぐにバックボタンでActivityを終了させます。さらにそのままiBeaconの領域へ入場してみましょう。しばらく待っていると、終了したActivityが再度起動するかと思います。ログには以下のように出力されています。

03-24 19:56:49.111  30044-30044/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ stopLeScan()
03-24 19:57:31.721  30876-30876/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ stopLeScan()
03-24 19:58:50.011  30876-30876/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ startLeScan(): null
03-24 19:58:50.011  30876-30907/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ onClientRegistered() - status=0 clientIf=5
03-24 19:58:50.121  30876-31707/beacon.android.classmethod.jp.altbeaconlibrarysample D/BeaconApplication﹕ Determine State: 1
03-24 19:58:50.121  30876-31707/beacon.android.classmethod.jp.altbeaconlibrarysample D/BeaconApplication﹕ Enter Region

Enter Regionの出力から、領域に入場したのが確認できます。

再度バックボタンでアプリをActivityを終了させます。そのまま、iBeaconの領域の範囲外へ出てみます。しばらく待っていると、以下の様なログが出力されます。

03-24 19:59:00.041  30876-30876/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ stopLeScan()
03-24 20:00:30.041  30876-30876/beacon.android.classmethod.jp.altbeaconlibrarysample D/dalvikvm﹕ GC_EXPLICIT freed 1058K, 5% free 50787K/53016K, paused 9ms+3ms, total 48ms
03-24 20:03:55.131  30876-30876/beacon.android.classmethod.jp.altbeaconlibrarysample D/dalvikvm﹕ GC_EXPLICIT freed 170K, 5% free 50734K/53016K, paused 2ms+3ms, total 29ms
03-24 20:04:00.001  30876-30876/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ startLeScan(): null
03-24 20:04:00.001  30876-30890/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ onClientRegistered() - status=0 clientIf=5
03-24 20:04:10.041  30876-30876/beacon.android.classmethod.jp.altbeaconlibrarysample D/BluetoothAdapter﹕ stopLeScan()
03-24 20:04:10.051  30876-31973/beacon.android.classmethod.jp.altbeaconlibrarysample D/BeaconApplication﹕ Determine State: 0
03-24 20:04:10.051  30876-31973/beacon.android.classmethod.jp.altbeaconlibrarysample D/BeaconApplication﹕ Exit Region

今度は領域から退場したログが出力されます(Exit Region)。Activity自体は終了させてしまっているので、特に見た目には変化はありませんが、きちんとアプリケーション側は裏側で領域の監視が実行できていることが確認できました。

スキャンのタイミングについて。通常の領域監視に比べて若干反応が遅いなと思われるかもしれません。こちらもBeaconManagerで設定値を変更することが可能です。ただ、バックグラウンド動作であまりにも短いタイミングでスキャンを繰り返しているのも効率がよくないため、ライブラリの方でそれなりに効率よくスキャン間隔の値を設定してくれているようです。なるべく早く反応が欲しい、などの要望がある場合は、setBackgroundBetweenScanPeriod()の設定を実行すると良いでしょう。

今回のソースはこちらです。次回は、BeaconManagerに設定できる細々した値を確認していきます。

参照