2016/11/09

Android で付近の Wi-Fi Access Point を検索して一覧を取得する


> この記事は [Android の Wi-Fi 実装に関する情報のまとめ](http://kokufu.blogspot.jp/2016/10/android-wi-fi_19.html) の一部として書かれました

付近の Wi-Fi Access Point を検索する方法です。


### コード
私の所持している端末では、Wi-Fi が ON になっている時、定期的に周囲の Access Point の検索が走っているようです多分、様々なアプリが `WifiManager.startScan()` を実行しているから?。

最後に検索した結果を取得するには `WifiManager.getScanResults()` を使用します。

```java
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
List scanResults = wm.getScanResults();

for (ScanResult scanResult : scanResults) {
    Log.d(TAG, scanResult.toString());
}
```

ただ、これだと最後に取得した結果がいつのものなのかわかりません`ScanResult` に `timestamp` という情報が乗っているので、実はわかるのですが。
そこで、検索が完了した時に呼ばれる [BroadcastReceiver](https://developer.android.com/reference/android/content/BroadcastReceiver.html) を登録し、
その `onReceive()` の中で先のコードを実行することで、最新のものを取得することにします。

BroadcastReceiver ですが、
全てのアプリが受信可能な通知のため、「止める」という概念がありません。
そのため、以下のような特徴があります。

- `WifiManager.startScan()` は呼ばなくても検索が実行されていることが多い先にも書いたように、様々なアプリが `WifiManager.startScan()` を実行しているからだと思われます

- `stopScan()` という method はない

- `onReceive()` は何度も呼ばれる


止められないので、`onReceive()` が何度も呼ばれるのを防ぐには、`unregisterReceiver()` することになります。


```java
public class MainActivity extends Activity {
    @Override
    protected void onResume() {
        super.onResume();

        // 検索時に呼ばれる BroadcastReceiver を登録
        registerReceiver(mScanResultsReceiver,
                new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));

        // startScan() は呼ばなくても scan が裏で走っていることが多いけど念の為
        WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
        wm.startScan();
    }

    @Override
    protected void onPause() {
        super.onPause();

        try {
            unregisterReceiver(mScanResultsReceiver);
        } catch (IllegalArgumentException e) {
            // 既に登録解除されている場合
            // 事前に知るための API は用意されていない
        }
    }

    private final BroadcastReceiver mScanResultsReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // intent に情報が乗っているわけではないので、
            // WifiManager の getScanResults で結果を取得
            WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
            List scanResults = wm.getScanResults();

            for (ScanResult scanResult : scanResults) {
                Log.d(TAG, scanResult.toString());
            }

            // onReceive() は何度も呼ばれるので、
            // 1度で終了させたい場合はここで unregister する
            try {
                unregisterReceiver(this);
            } catch (IllegalArgumentException e) {
                // 既に登録解除されている場合
                // 事前に知るための API は用意されていない
            }
        }
    };
}
```

### 取得できる結果
`ScanResult` には以下のような情報が入っています。

```
SSID : my-ssid
wifiSsid : my-ssid
BSSID : aa:bb:cc:dd:ee:ff
capabilities : [WPA-PSK-CCMP][WPA2-PSK-CCMP][WPS][ESS]
frequency : 2437
level : -93
timestamp : 713047320153
```

`frequency` は Channel とほぼ同義、 `level` は RSSI とほぼ同義です。

また、API Level 23 (Marshmallow) 以降だと以下のような情報他も追加されています。
こちらは 5GHz 帯の情報になります。

```
centerFreq0 : 5610
centerFreq1 : 0 
channelWidth : 2
```

### パーミッション
`WifiManager.startScan()` を実行するには `android.permission.CHANGE_WIFI_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。

```xml

```

また、 `WifiManager.getScanResults()` を実行するには `android.permission.ACCESS_WIFI_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。

```xml

```

### Android 6.0 以降では追加のパーミッションが必要
さらに、Android 6.0 (API Level 23 Marshmallow) 以降では `android.permission.ACCESS_COARSE_LOCATION` もしくは `android.permission.ACCESS_FINE_LOCATION` が必要で、
これらは動的に許可を得ねばなりません。

> 参考
>
> [穀風: Android 6.0 以降で WifiManager.getScanResults() を普通に実行しても結果が空のリストになってしまう](https://kokufu.blogspot.jp/2016/11/android-60-wifimanagergetscanresults.html)

0 件のコメント: