Wi-Fi スキャンの概要

WifiManager API が提供する Wi-Fi スキャン機能を使用すると、デバイスから認識できる Wi-Fi アクセス ポイントのリストを取得できます。

Wi-Fi スキャン プロセス

スキャン プロセスには次の 3 つのステップがあります。

  1. SCAN_RESULTS_AVAILABLE_ACTION のブロードキャスト リスナーを登録します。これは、スキャン リクエストが完了したときに送信され、成功または失敗のステータスを提供します。Android 10(API レベル 29)以降を搭載するデバイスの場合は、プラットフォームまたは他のアプリによってデバイスで実行されたフル Wi-Fi スキャンに対してこのブロードキャストが送信されます。アプリは独自にスキャンを実行せずにブロードキャストを使用して、デバイス上のすべてのスキャンの完了を傍受できます。

  2. WifiManager.startScan() を使用してスキャンをリクエストします。次のいずれかの理由で呼び出しが失敗する可能性があるため、メソッドの戻りステータスを確認してください。

    • 短時間にスキャンが多すぎると、スキャン リクエストがスロットリングされることがあります。
    • デバイスはアイドル状態で、スキャンが無効になっている。
    • Wi-Fi ハードウェアがスキャンエラーを報告した。
  3. WifiManager.getScanResults() を使用してスキャン結果を取得します。返されるスキャン結果は、最新の更新結果です。現在のスキャンが完了していないか、成功していない場合は、以前のスキャンの結果が返されることがあります。つまり、SCAN_RESULTS_AVAILABLE_ACTION ブロードキャストが成功する前にこのメソッドを呼び出すと、古いスキャン結果が返される可能性があります。

次のコードは、上記のステップを実装する方法の例を示しています。

Kotlin

val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager

val wifiScanReceiver = object : BroadcastReceiver() {

  override fun onReceive(context: Context, intent: Intent) {
    val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
    if (success) {
      scanSuccess()
    } else {
      scanFailure()
    }
  }
}

val intentFilter = IntentFilter()
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
context.registerReceiver(wifiScanReceiver, intentFilter)

val success = wifiManager.startScan()
if (!success) {
  // scan failure handling
  scanFailure()
}

....

private fun scanSuccess() {
  val results = wifiManager.scanResults
  ... use new scan results ...
}

private fun scanFailure() {
  // handle failure: new scan did NOT succeed
  // consider using old scan results: these are the OLD results!
  val results = wifiManager.scanResults
  ... potentially use older scan results ...
}

Java

WifiManager wifiManager = (WifiManager)
                   context.getSystemService(Context.WIFI_SERVICE);

BroadcastReceiver wifiScanReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context c, Intent intent) {
    boolean success = intent.getBooleanExtra(
                       WifiManager.EXTRA_RESULTS_UPDATED, false);
    if (success) {
      scanSuccess();
    } else {
      // scan failure handling
      scanFailure();
    }
  }
};

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
context.registerReceiver(wifiScanReceiver, intentFilter);

boolean success = wifiManager.startScan();
if (!success) {
  // scan failure handling
  scanFailure();
}

....

private void scanSuccess() {
  List<ScanResult> results = wifiManager.getScanResults();
  ... use new scan results ...
}

private void scanFailure() {
  // handle failure: new scan did NOT succeed
  // consider using old scan results: these are the OLD results!
  List<ScanResult> results = wifiManager.getScanResults();
  ... potentially use older scan results ...
}

制限

Android 8.0(API レベル 26)では、権限と許可される Wi-Fi スキャンの頻度に関する制限が導入されました。

ネットワーク パフォーマンス、セキュリティ、電池寿命を改善するため、Android 9(API レベル 28)では権限に関する要件を厳格にし、Wi-Fi スキャンの頻度をさらに制限しました。

権限

Android 8.0(API レベル 26)および Android 8.1(API レベル 27):

WifiManager.getScanResults() を正常に呼び出すには、次のいずれかの権限が必要です。

呼び出し元アプリにこれらの権限のいずれも付与されていない場合、呼び出しは SecurityException で失敗します。

また、Android 8.0(API レベル 26)以降を搭載したデバイスでは、CompanionDeviceManager を使用して、位置情報の利用許可を必要とせずに、アプリに代わって近くのコンパニオン デバイスをスキャンできます。このオプションの詳細については、コンパニオン デバイスのペア設定をご覧ください。

Android 9(API レベル 28):

WifiManager.startScan() の呼び出しを成功させるには、次の条件をすべて満たす必要があります。

  • アプリに ACCESS_FINE_LOCATION または ACCESS_COARSE_LOCATION 権限が付与されている。
  • アプリに CHANGE_WIFI_STATE 権限が付与されている。
  • デバイスで([設定] > [位置情報] に移動して)位置情報サービスを有効にしている。

Android 10(API レベル 29)以降:

WifiManager.startScan() の呼び出しを成功させるには、次の条件をすべて満たす必要があります。

  • アプリが Android 10(API レベル 29)以降の SDK を対象としている場合は、アプリに ACCESS_FINE_LOCATION 権限が付与されている。
  • アプリが Android 10(API レベル 29)より前の SDK を対象としている場合は、アプリに ACCESS_COARSE_LOCATION または ACCESS_FINE_LOCATION の権限が付与されている。
  • アプリに CHANGE_WIFI_STATE 権限が付与されている。
  • デバイスで([設定] > [位置情報] に移動して)位置情報サービスを有効にしている。

WifiManager.getScanResults() を正常に呼び出すには、次の条件をすべて満たす必要があります。

  • アプリが Android 10(API レベル 29)以降の SDK を対象としている場合は、アプリに ACCESS_FINE_LOCATION 権限が付与されている。
  • アプリが Android 10(API レベル 29)より前の SDK を対象としている場合は、アプリに ACCESS_COARSE_LOCATION または ACCESS_FINE_LOCATION の権限が付与されている。
  • アプリに ACCESS_WIFI_STATE 権限が付与されている。
  • デバイスで([設定] > [位置情報] に移動して)位置情報サービスを有効にしている。

呼び出し元のアプリがこれらの要件をすべて満たしていない場合、呼び出しは SecurityException で失敗します。

スロットリング

WifiManager.startScan() を使用するスキャン頻度には次の制限が適用されます。

Android 8.0、Android 8.1:

各バックグラウンド アプリは、30 分に 1 回スキャンを実行できます。

Android 9:

各フォアグラウンド アプリは、2 分間の間に 4 回スキャンを実行できます。これにより、短時間で大量のスキャンを行うことができます。

すべてのバックグラウンド アプリをまとめて、30 分に 1 回スキャンを実行できます。

Android 10 以降:

Android 9 と同じスロットリング制限が適用されます。ローカルテスト用にスロットリングをオフに切り替える新しい開発者向けオプションがあります([開発者向けオプション] > [ネットワーク] > [Wi-Fi スキャンのスロットリング])。