[Android][Lollipop]プロセスのNetworkInterfaceを設定する

**この記事はLollipop(API 21)限定です。Marshmallow(API 23)でDeprecatedになる内容を含んでいます**

# Lollipopではアプリが使うNetworkを選択できる

Lollipopでは新機能としてアプリ内での通信に使うNetworkのInterfaceを切り替えることができます。

[Android 5.0（Lollipop）の機能紹介 : Wi-Fiでインターネットにアクセスできない場合はモバイルデータ通信に自動的に切り替える機能が追加 ｜ ガジェット通信](http://getnews.jp/archives/685034)

通信単位で切り替える場合は[Network](http://developer.android.com/reference/android/net/Network.html)(平凡な名前なのにAPI21！)を取得してSocket等を生成して通信することが可能です。
以下の記事を参考にしてください。

[Android 5.0/Lollipopでインターネット接続していないWi-Fiポインタに接続する](http://qiita.com/hishida/items/e43d5ada3d83c71ce953)

この記事ではアプリプロセスが使うNetworkInterfaceごと切り替えてしまう方法をメモします。
と言っても[ConnectivityManager.setProcessDefaultNetwork](http://developer.android.com/reference/android/net/ConnectivityManager.html#setProcessDefaultNetwork(android.net.Network))を呼ぶだけです。

# プロセスの全通信のNetworkInterfaceを変更する

## ネットワークの取得

まず、プロセスに設定したいネットワークをリクエストします。

```java:
    ConnectivityManager mConnectivityManager;
    mConnectivityManager = (ConnectivityManager) mContext
                .getSystemService(Context.CONNECTIVITY_SERVICE);

    // 設定したいNetworkが使えるかは確認しておいていいと思います。
    NetworkInfo info = mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
    if (info == null || !info.isAvailable())
        return false;
    
    // 必要に応じて、機内モードの設定やデータ通信の設定を確認しました。

    // リクエストしたいNetworkを取得するためにNetworkRequestを作成します。
    NetworkRequest.Builder builder = new NetworkRequest.Builder();
    // Networkの種類？を指定します。ここではインターネットに使えるネットワークを指定しています。
    builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
    // Transportタイプを指定します。ここではCellularを指定しているのでモバイル通信になります。
    builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
    NetworkRequest networkRequest = builder.build();

    // Callbackを実装します。
    // 解除する時のためにinstanceは参照しておいたほうがいいです。
    ConnectivityManager.NetworkCallback mCallback = new MyCallback();

    // NetworkRequestとNetworkCallbackを使ってNetworkをリクエストします。
    mConnectivityManager.requestNetwork(networkRequest, mCallback);
```

また、リクエストの成否を確認するCallbackを実装しています。

```java:MyCallback.java
class MyCallback extends ConnectivityManager.NetworkCallback {

    @Override
    public void onAvailable(Network network) {
        // 接続されたときに呼ばれる
        super.onAvailable(network);
        // ConnectivityManagerのstaticメソッドでアプリ(プロセス)全体のネットワークを設定します。
        // networkの中身を確認してから設定してもいいでしょう。
        boolean a = ConnectivityManager.setProcessDefaultNetwork(network);

        // UI更新など、必要な処理をします。
        // Callbackが生きている限りはなんども呼ばれるのでその前提で処理してください。
    }

    @Override
    public void onLost(Network network) {
        // Networkを失った時、そもそも失敗した時に呼ばれる
        super.onLost(network);
        // 必要な処理をします。実装によっては特に継承する必要はないかもしれません。
    }
};
```

## ネットワークの解除

setProcessDefaultNetworkはドキュメントによるとnullをセットすることでresetができます。
ただ、requestNetworkでリクエストしたNetworkInterfaceはNetworkCallbackが生きている間はActiveのままになるようです。

しかし、API 21では[requestNetwork](http://developer.android.com/reference/android/net/ConnectivityManager.html#requestNetwork(android.net.NetworkRequest,%20android.net.ConnectivityManager.NetworkCallback))の反対のメソッドはありません。
(API 22ではPendingIntentを引数にする[requestNetwork](http://developer.android.com/reference/android/net/ConnectivityManager.html#requestNetwork(android.net.NetworkRequest,%20android.app.PendingIntent))が追加され、対応する[releaseNetworkRequest](http://developer.android.com/reference/android/net/ConnectivityManager.html#releaseNetworkRequest(android.app.PendingIntent))も追加されています。)

registerNetworkCallbackにはunregisterNetworkCallbackがあり、NetworkCallbackを空にできるのですが、これを空にするとリクエストしたNetworkInterfaceもDeactiveになるようです。

```java:
public void resetDefaultNetwork() {
    // すでにプロセスのネットワークが設定されている時はnullを設定する
    if (ConnectivityManager.getProcessDefaultNetwork() != null) {
        ConnectivityManager.setProcessDefaultNetwork(null);
    }
    // NetworkCallbackを設定している時はunregisterする。
    if (mCallback != null) {
        try {
            mConnectivityManager.unregisterNetworkCallback(mCallback);
        } catch (IllegalArgumentException e) {
            // unregisterしたCallbackが設定されていないとExceptionがthrowされるのできちんと管理するか、catchする必要があります。
            e.printStackTrace();
        }
    }
}
```


## 注意点

### パーミッションがいろいろ必要

冒頭に紹介した記事内でも書かれていますが、ConnectivityManager.requestNetworkを使うのに[CHANGE_NETWORK_STATE](http://developer.android.com/reference/android/Manifest.permission.html#CHANGE_NETWORK_STATE)のパーミッションが必要になります。

それ以前にgetActiveNetworkInfo等にはACCESS_NETWORK_STATEが必要になります。

### API 22,23でもメソッドが追加されている

Android6.0でモバイル通信が制限された時の挙動等はおそらく機内モードと同じですが、異なる場合もあると思うので要確認かと思います。

また、ConnectivityManagerにはすでにAPI 22,23で追加されるメソッドが幾つかあります。
挙動がまた変更される可能性が高いので注意が必要でしょう。

API 22

* [releaseNetworkRequest (PendingIntent operation)](http://developer.android.com/reference/android/net/ConnectivityManager.html#registerNetworkCallback(android.net.NetworkRequest,%20android.app.PendingIntent))
* [requestNetwork (NetworkRequest request, PendingIntent operation)](http://developer.android.com/reference/android/net/ConnectivityManager.html#releaseNetworkRequest(android.app.PendingIntent))

API 23

* [bindProcessToNetwork](http://developer.android.com/reference/android/net/ConnectivityManager.html#getAllNetworkInfo())
* [getBoundNetworkForProcess]()
* [getActiveNetwork]()
* [getDefaultProxy]()
* [reportNetworkConnectivity]()
* [requestBandwidthUpdate]()
* [registerNetworkCallback (NetworkRequest request, PendingIntent operation)]()
* [unregisterNetworkCallback (PendingIntent operation)]()


### API 23でsetProcessDefaultNetwork自体がDeprecated

Android6.0では上記の通り、いろいろなメソッドが追加されていますが、そのためか、setProcessDefaultNetwork自体がDeprecatedになりますので、注意が必要です。
代わりにbindProcessToNetworkを使うようです。

API 23でDeprecatedなメソッド

* [getAllNetworkInfo](http://developer.android.com/reference/android/net/ConnectivityManager.html#getAllNetworkInfo())
* [getNetworkInfo](http://developer.android.com/reference/android/net/ConnectivityManager.html#getNetworkInfo(int))
 代替：[getAllNetworks](http://developer.android.com/reference/android/net/ConnectivityManager.html#getAllNetworks())や[getNetworkInfo](http://developer.android.com/reference/android/net/ConnectivityManager.html#getNetworkInfo(android.net.Network))

* [isNetworkTypeValid](http://developer.android.com/reference/android/net/ConnectivityManager.html#isNetworkTypeValid(int))
 代替：以後networkTypeは使わない

* [reportBadNetwork](http://developer.android.com/reference/android/net/ConnectivityManager.html#reportBadNetwork(android.net.Network))(API 21で追加)
 代替：[reportNetworkConnectivity](http://developer.android.com/reference/android/net/ConnectivityManager.html#reportNetworkConnectivity(android.net.Network,%20boolean))

* [getProcessDefaultNetwork](http://developer.android.com/reference/android/net/ConnectivityManager.html#getProcessDefaultNetwork())(staticメソッド)(API 21で追加)
 代替：[getBoundNetworkForProcess](http://developer.android.com/reference/android/net/ConnectivityManager.html#getBoundNetworkForProcess())

* [setProcessDefaultNetwork](http://developer.android.com/reference/android/net/ConnectivityManager.html#setProcessDefaultNetwork(android.net.Network))(staticメソッド)(API 21で追加)
 代替：[bindProcessToNetwork](http://developer.android.com/reference/android/net/ConnectivityManager.html#bindProcessToNetwork(android.net.Network))

API 21で追加された関数も多くはKitkat(API19,20)で追加されたものを整理しなおしたものだったりするので、まだ設計の上で整理している最中だと思われます。
setProcessDefaultNetworkなどはstaticメソッドだったりして、あまりいい設計ではなかったのかもしれません。

Marshmallowでの実装方法も時間があれば調査してメモしておきたいと思います。