ネットワーク接続/切断イベントをハンドリング

掲載コードに問題があったので、こっそりエントリを再々修正。
ネットワーク接続状態は必ずConnectivityManagerを使って取得する。なお、ConnectivityManagerを利用するには、AndroidManifest.xmlを追加する必要がある。

サンプルコード

public class NetworkEventActivity extends Activity {
    private boolean connected = false;
    private BroadcastReceiver connectivityActionReceiver;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // 初期状態を取得
        ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo ni = cm.getActiveNetworkInfo();
	connected = (ni != null) && ni.isConnected();

        // BroadcastReceiverを登録
        connectivityActionReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                // ネットワーク接続状態を取得する
                ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
                NetworkInfo ni = cm.getActiveNetworkInfo();
                boolean connected = (ni != null) && ni.isConnected();
                
                // ハンドラをコールする
                onConnectedChanged(connected);
            }
        };
        registerReceiver(connectivityActionReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
    }

    /**
     * ネットワーク接続状態が変わった時にコールされるハンドラ
     */
    private void onConnectedChanged(boolean connected) {
        if (connected == this.connected) return;
        
        this.connected = connected;
        
        if (connected) {
            Log.d("test", "接続済み");
        } else {
            Log.d("test", "切れた!!!1!");
        }
    }

    /**
     * 接続済であればtrueを返す
     */
    private boolean isConnected() {
        return connected;
    }

    @Override
    protected void onDestroy() {
        // 使い終わったBroadcastReceiverを解放
        unregisterReceiver(connectivityActionReceiver);
        super.onDestroy();
    }
}

期待通りに動作しないコード

このエントリは何度か修正しているのだが、動かないコードとその理由を掲載しておく。

ConnectivityManager.EXTRA_NO_CONNECTIVITYを使う
        connectivityActionReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                // ネットワーク接続状態を取得する
                boolean isConnected = !intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
                boolean connected = (ni != null) && ni.isConnected();
                
                // ハンドラをコールする
                onConnectedChanged(connected);
            }
        };

BroadcastReceiver.onReceive(Context, Intent)のIntentからEXTRA_NO_CONNECTIVITYの値を取得すればネットワークが切断されているかわかるよなどと書かれているが、こいつは大嘘。
Issue 13468 -
android -

Problem about ConnectivityManager.EXTRA_NO_CONNECTIVITY -
Android - An Open Handset Alliance Project - Google Project Hosting
に書かれているのだが、機内モードがONの時にネットワークに接続されていると値を返してくるので使ってはいけない。

ConnectivityManager.EXTRA_NETWORK_INFOを使う
        connectivityActionReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                // ネットワーク接続状態を取得する
                NetworkInfo ni = (NetworkInfo)intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
                boolean connected = (ni != null) && ni.isConnected();
                
                // ハンドラをコールする
                onConnectedChanged(connected);
            }
        };

これで行けるだろうと数日間掲載していたのだが、駄目だった。
registerReceiver()すると最初にBroadcastが飛んで来るのだが、このBroadcastはデバイスのネットワークインタフェース毎に飛んでくるようだ(3G回線とWiFiが付いている場合は2回)。そのため、接続中/切断中のインタフェースが混在していると、一瞬切断されたかのようにイベントを拾ってしまう。