2016/11/09

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

2018/1/19 追記
ScanResult の level を RSSI に変換する方法を追記しました。

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

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

コード

私の所持している端末では、Wi-Fi が ON になっている時、定期的に周囲の Access Point の検索が走っているようです1

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

1
2
3
4
5
6
7
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
List<ScanResult> scanResults = wm.getScanResults();
 
for (ScanResult scanResult : scanResults) {
    Log.d(TAG, scanResult.toString());
}

ただ、これだと最後に取得した結果がいつのものなのかわかりません2。 そこで、検索が完了した時に呼ばれる BroadcastReceiver を登録し、 その onReceive() の中で先のコードを実行することで、最新のものを取得することにします。

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

  • WifiManager.startScan() は呼ばなくても検索が実行されていることが多い3

  • stopScan() という method はない

  • onReceive() は何度も呼ばれる

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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<ScanResult> 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 は 電波強度で、単位は デシベルミリワット[dBm] です。 これを、表示形式(いわゆる RSSI)に変換するには WifiManager.calculateSignalLevel(int, int) を使用します。

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

centerFreq0 : 5610
centerFreq1 : 0 
channelWidth : 2

パーミッション

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

1
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

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

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">

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() を普通に実行しても結果が空のリストになってしまう

  1. 多分、様々なアプリが WifiManager.startScan() を実行しているから? 
  2. ScanResulttimestamp という情報が乗っているので、実はわかるのですが 
  3. 先にも書いたように、様々なアプリが WifiManager.startScan() を実行しているからだと思われます 
?

11 件のコメント:

さやか さんのコメント...

投稿読まさせていただきました。初めてコメントさせていただきます。
この記事について質問させていただいてもよろしいでしょうか。
お返事お待ちしております。
よろしくお願いいたします。

Yusuke さんのコメント...

さやかさん、コメントありがとうございます。
質問はいつでもどうぞ。

さやか さんのコメント...

お返事ありがとうございます。
現在、周囲のWi-Fiのアクセスポイント を検索し、接続されていないWi-Fiも含めてRSSIを取得するプログラムを作成しています。接続しているWi-FiのRSSIは取得できましたが、接続されていないWi-FiのRSSIの取得の方法がわかりません。どうかご教授いただけませんでしょうか。お返事お待ちしております。

Yusuke さんのコメント...

ScanResult の level に関して記述が曖昧でしたね。失礼しました。

本文にも追記しておきましたが、level の値は生の電波強度で単位は dBm です。これをいわゆる RSSI に変換するには WifiManager.calculateSignalLevel(int,int) を使用します。

さやか さんのコメント...

お返事ありがとうございます。このカードを使えば接続していないwifiの電波強度も表示できるということぇしょうか。

また、List…を使用するとlistviewを使って表示させられないのですが、何が原因なのかわかりません。こちらも教えていただけると助かります。
お返事お待ちしております。よろしくお願いいたします。

さやか さんのコメント...

誤字がひどいので、訂正させていただきます。
カード→コード、できるということぇしょうか→できるということでしょうか、List…→List…

Yusuke さんのコメント...

はい。
WifiManager.getScanResults() で付近のアクセスポイントの電波強度が取得できます。

表示に関してなのですが、このコメント欄で ListView の使い方を説明するのは厳しいので、まずは本文中のコードにあるように LogCat で ScanResults の内容を表示して、値が取れているか確認してみることをお勧めします。

さやか さんのコメント...


お返事ありがとうございます。
何度も質問をして申し訳ございません。
WifiManager.getScanResults()で、付近のwifi の情報をスキャンし、List< Scan Result>で、ScanResultsに一つづつ入れ、そこから、SSIDやRSSIなどが参照できるという認識で正しいでしょうか。

ScanResultsを使ってないような表示はできているのですが、List< Scan Result>を使用すると表示できなくなってしまいます…
どうしたら良いのかわからなくて困っています。
よろしくお願いいたします。

Yusuke さんのコメント...

ちょっと質問が漠然としすぎですね。
「List< Scan Result>を使用すると表示できなく...」 だけだと、大きくわけて以下の2つが考えられるかと思います。

- List がそもそも取れていない
- ListView の使い方が間違っている

さらに、それぞれに様々な状況が考えられます。
前者であれば、

- ScanResult が一つも取れない
- ScanResult は取れるけど、level がとれない
- 例外が発生してしまう

などなど。数え切れません。
こういった原因を一つづつ見つけて直していくのをデバッグといいますが、このコメント欄で一つ一つそのサポートをするのはかなり難しいです。
そういった質問を受け付けるサービスがあるので、そちらを利用されてはどうでしょうか?
コードを貼り付けることも出来るので、回答が得られやすいかもしれません。

teratailスタック・オーバーフロー 等が有名です。

ただ、こういったサイトで質問する場合も、ただ「動きません」「出来ません」だと回答する方も困ってしまいます。
なるべく具体的に、そして自力で調べられることは調べてから質問するようにすると良いでしょう。

さやか さんのコメント...

パーミッションを修正することで解決いたしました。
このサイトを参考にさせていただいて、作成することができました。ご丁寧に回答いただき感謝いたします。
ありがとうございました。

Yusuke さんのコメント...

あまりお役にたてませんでしたが、解決したようで良かったです。
Android のパーミッション周りはどんどん複雑になっているので厄介ですよね。