2016/10/27

Android で Wi-Fi の接続状態を確認する


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

Wi-Fi 接続して、通信を行っているか等の状態を確認する方法です。

Android の Wi-Fi 接続状態を保持するクラスは `WifiInfo` と `NetwrokInfo` の2種類があります。
前者は Wi-Fi に特化しているのに対し、後者は接続全般LTE等をカバーします。
状況によって使い分けるのが良いでしょう。


### WifiInfo を使うコード
`wpa_supplicant` が提供する情報を取得する方法です。

Wi-Fi 接続していない場合も `null` が返る**のではなく**、`INACTIVE` な状態が返ります。 

```java
// Activity 等の Context 内で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wm.getConnectionInfo();

WifiInfo.SupplicantState state = wifiInfo.getSupplicantState();
```

取得できる SupplicantState
```java
public enum SupplicantState implements Parcelable {
    DISCONNECTED,
    INTERFACE_DISABLED,
    INACTIVE,
    SCANNING,
    AUTHENTICATING,
    ASSOCIATING,
    ASSOCIATED,
    FOUR_WAY_HANDSHAKE,
    GROUP_HANDSHAKE,
    COMPLETED,
    DORMANT,
    UNINITIALIZED,
    INVALID
}
```

各状態については JavaDoc を参照のこと。

> 参考
>
> [SupplicantState | Android Developers](https://developer.android.com/reference/android/net/wifi/SupplicantState.html) 

この `SupplicantState` ですが、JavaDoc の中に以下のような記述があります。

> This is more fine-grained than most users will be interested in.
> In general, it is better to use `NetworkInfo.State`.

意訳すると以下のような感じでしょうか

> この情報は多くのユーザーにとって詳細すぎる。一般的には `NetworkInfo.State` を使うほうが良い。

特別な理由が無いのであれば `NetworkInfo` を使っておいた方が良いのかもしれません。


### NetworkInfo を使うコード1
`getActiveNetworkInfo()` で現在使用中のネットワーク情報を取得し、それが Wi-Fi かどうか確認するという方式です。

なお、ネットワーク接続がない状態だと `networkInfo` が `null` になります。

```java
// Activity 等の Context 内で
ConnectivityManager connectivityManager =
        (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);

NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
        // シンプルな状態を取得
        NetworkInfo.State networkState = networkInfo.getState();

        // 詳細状態を取得
        // これを使うことはめったに無いと思われる
        NetworkInfo.DetailedState detailedState = networkInfo.getDetailedState();
}
```

取得できる State
```java
public enum {
    CONNECTING,
    CONNECTED,
    SUSPENDED,
    DISCONNECTING,
    DISCONNECTED,
    UNKNOWN
}
```

取得できる DetailedState
```java
public enum {
    /** Ready to start data connection setup. */
    IDLE,
    /** Searching for an available access point. */
    SCANNING,
    /** Currently setting up data connection. */
    CONNECTING,
    /** Network link established, performing authentication. */
    AUTHENTICATING,
    /** Awaiting response from DHCP server in order to assign IP address information. */
    OBTAINING_IPADDR,
    /** IP traffic should be available. */
    CONNECTED,
    /** IP traffic is suspended */
    SUSPENDED,
    /** Currently tearing down data connection. */
    DISCONNECTING,
    /** IP traffic not available. */
    DISCONNECTED,
    /** Attempt to connect failed. */
    FAILED,
    /** Access to this network is blocked. */
    BLOCKED,
    /** Link has poor connectivity. */
    VERIFYING_POOR_LINK,
    /** Checking if network is a captive portal */
    CAPTIVE_PORTAL_CHECK
}
```


### NetworkInfo を使うコード2 (API Level 21 Lollipop 以降) 
`getAllNetworks()` で使用可能な ~~全ての~~ ネットワークを取得して、その中から Wi-Fi のものをチェックする方法です。

この方法の落とし穴は、`getAllNetworks()` が **接続している** ネットワーク一覧しか返さないというところです。
つまり、一般的な端末では `getAllNetworks().length` は大抵 0 か 1 になります名前と挙動が合ってないし、あんまり使い勝手が良くないと思うのは私だけ?。
ただし、モバイル接続から Wi-Fi 接続に切り替わる瞬間、少しの間だけ 2 になることもあるようです少なくとも、私の手元の Nexus 7 (2012) と Nexus 6P はそういう挙動でした。

そのため、この方法は実質「NetworkInfo を使うコード1」と一緒と言って良いでしょう。

```java
// Activity 等の Context 内で
ConnectivityManager connectivityManager =
        (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);

for (Network network : connectivityManager.getAllNetworks()) {
    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
    if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
        // シンプルな状態を取得
        NetworkInfo.State state = networkInfo.getState();

        // 詳細状態を取得
        NetworkInfo.DetailedState detailedState = networkInfo.getDetailedState();
    }
}
```

取得できる状態は「NetworkInfo を使うコード1」と同じです。


### NetworkInfo を使うコード3 (API Level 20 Kitkat 以前) 
この方法は Lollipop 以降 Deprecated になりました。

なお、Wi-Fi 機能がハードウェア的に存在しない場合は、`networkInfo` が `null` になります。

```java
// Activity 等の Context 内で
ConnectivityManager connectivityManager =
        (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);

NetworkInfo networkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);

// シンプルな状態を取得
NetworkInfo.State state = networkInfo.getState();

// 詳細状態を取得
NetworkInfo.DetailedState detailedState = networkInfo.getDetailedState();
```

取得できる状態は「NetworkInfo を使うコード1」と同じです。


### WifiInfo を使うためのパーミッション
WifiInfo を使うコードを実行するには `android.permission.ACCESS_WIFI_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。

```xml

```

### NetworkInfo を使うためのパーミッション
NetworkInfo を使うコードを実行するには `android.permission.ACCESS_NETWORK_STATE` パーミッションを AndroidManifest.xml で設定する必要があります。

```xml

```

0 件のコメント: