JUnitについての書籍が遂に発売します!
Java開発時のユニットテストを加速する「JUnit速効レシピ」
Java開発時のユニットテストを加速する「JUnit速効レシピ」
Androidでは、アプリによってWifiの接続先を切り替える事が出来ます。
今回は、そんな接続先の切り替えを、各無線LANスポットの電波強度によって
自動的に判別し、一番電波状態の良い無線LANスポットへ自動的に接続を切り替える仕組みの
実装方法について解説します。
接続情報を取得する
Androidアプリ開発では、現在接続中のWifi情報を取得する事が出来ます。
具体的には、WifiManagerを使用します。
早速、WifiManagerのインスタンスを取得しましょう。
WifiManagerのインスタンスの取得は『getSystemService()』メソッドを介して行います。
WifiManager wifiManager = (WifiManager)getSystemService(WIFI_SERVICE);
インスタンスを取得しましたら、『getConnectionInfo()』メソッドを起動する事により、
現在接続中のWifi接続情報を保持する『WifiInfo』のオブジェクトを取得する事が出来ます。
WifiInfo info = wifiManager.getConnectionInfo();
接続中のWifi接続情報は全て、この『WifiInfo』から取得する事が出来ます。
『WifiInfo』から取得出来る情報と、取得する為の専用メソッドは下記の通りです。
getBSSID() | BSSIDを取得します。 |
---|---|
getDetailedStateOf(SupplicantState suppState) | アプリカントの状態をマッピングします。 |
getHiddenSSID() | ステルスモードの状態を取得します。 |
getIpAddress() | IPアドレスを取得します。 |
getLinkSpeed() | リンクスピードを取得します。 |
getMacAddress() | MACアドレスを取得します。 |
getNetworkId() | ネットワークIDを取得します。 |
getRssi() | 電波強度を取得します。 |
getSSID() | SSIDを取得します。 |
getSupplicantState() | サプリカントの状態を取得します。 |
toString() | これらの情報を文字列へシリアライズします。 |
周辺の無線LANスポットを検索する
現在検知しているWifiの一覧を取得するには、
『WifiManager』の『startScan()』メソッドを使用します。
// wifiマネージャーを取得する WifiManager manager = (WifiManager)getSystemService(WIFI_SERVICE); // 現在検知しているwifiスポットの検索を開始する manager.startScan();
『startScan()』メソッドによる検索結果は、『getScanResults()』メソッドにて取得する事が出来、
個々の情報は『ScanResult』オブジェクトとしてリストに纏められています。
それぞれに参照する場合は、『for()』等で回して扱いましょう。
『ScanResult』オブジェクトには、プロパティとして下記情報が格納されています。
BSSID | MACアドレス |
---|---|
SSID | ネットワーク識別名 |
capabilities | 暗号化情報 |
frequency | 周波数 |
level | 信号レベル |
timestamp | 情報取得日時タイムスタンプ |
for(ScanResult result : manager.getScanResults()) { Log.v("ScanResult.BSSID",result.BSSID); Log.v("ScanResult.SSID",result.SSID); Log.v("ScanResult.capabilities",result.capabilities); Log.v("ScanResult.frequency",result.frequency); Log.v("ScanResult.level",result.level); Log.v("ScanResult.timestamp",result.timestamp); }
接続履歴を取得する
無線LANスポットへ接続した経歴を取得する事が出来ます。
経歴といっても、「いつ・どこで」の様なヒストリーではなく、
接続実績のある無線LANスポットの一覧を取得出来ます。
その履歴には、過去に入力したパスワード等も含まれるため、再接続する際に必要となる情報です。
それでは早速履歴を取得して見ましょう。
履歴の取得は『WifiManager』のインスタンスから『getConfiguredNetworks()』メソッドにて
一覧を取得します。
具体的なコードは下記のとおりです。
// wifiマネージャーを取得する WifiManager manager = (WifiManager)getSystemService(WIFI_SERVICE); // 接続実績のあるwifi一覧を取得 Listconfig_list = manager.getConfiguredNetworks();
取得出来るのは、無線LAN個々の設定情報を保持した『WifiConfiguration』のリストです。
『WifiConfiguration』では、下記の情報をプロパティから取得する事が出来ます。
BSSID | MACアドレス |
---|---|
SSID | ネットワーク識別名 |
allowedAuthAlgorithms | 認証プロトコルセット |
allowedGroupCiphers | グループの暗号セット |
allowedKeyManagement | キー管理プロトコルのセット |
allowedPairwiseCiphers | WPAのための暗号とのペア情報 |
allowedProtocols | セキュリティプロトコルのセット |
enterpriseConfig | EAP関連の詳細情報 |
hiddenSSID | ステルスモードの状態 |
networkId | ネットワークID |
preSharedKey | WPA-PSKのキー |
prioritye | 優先度 |
status | ネットワークの状態 |
wepKeys | WEPキー |
wepTxKeyIndex | WEPキーのインデックス |
// wifiマネージャーを取得する WifiManager manager = (WifiManager)getSystemService(WIFI_SERVICE); // 接続実績のあるwifi一覧を取得 Listconfig_list = manager.getConfiguredNetworks(); // 接続経験のあるスポットを順に確認する for(int i=0; i < config_list.size(); i++) { Log.v("WifiConfiguration.BSSID",config_list.get(i).BSSID); Log.v("WifiConfiguration.SSID",config_list.get(i).SSID); Log.v("WifiConfiguration.allowedAuthAlgorithms",config_list.get(i).allowedAuthAlgorithms); Log.v("WifiConfiguration.allowedGroupCiphers",config_list.get(i).allowedGroupCiphers); Log.v("WifiConfiguration.allowedKeyManagement",config_list.get(i).allowedKeyManagement); Log.v("WifiConfiguration.allowedPairwiseCiphers",config_list.get(i).allowedPairwiseCiphers); Log.v("WifiConfiguration.allowedProtocols",config_list.get(i).allowedProtocols); Log.v("WifiConfiguration.enterpriseConfig",config_list.get(i).enterpriseConfig); Log.v("WifiConfiguration.hiddenSSID",config_list.get(i).hiddenSSID); Log.v("WifiConfiguration.networkId",config_list.get(i).networkId); Log.v("WifiConfiguration.preSharedKey",config_list.get(i).preSharedKey); Log.v("WifiConfiguration.prioritye",config_list.get(i).prioritye); Log.v("WifiConfiguration.status",config_list.get(i).status); Log.v("WifiConfiguration.wepKeys",config_list.get(i).wepKeys); Log.v("WifiConfiguration.wepTxKeyIndex",config_list.get(i).wepTxKeyIndex); }
電波強度の高い無線LANスポットへ自動的に接続を切り替える
それでは、本記事の目的でもある、電波強度の高い無線LANスポットへ、Wifi接続を自動的に切り替える
実装を行いたいと思います。
これまで解説して来た内容を組み合わせて、現在検知しているWifiと、過去に接続した事のある履歴と比較し、
合致するものの中から一番電波強度の高い無線LANスポットへ接続を切り替えます。
早速見て行きましょう。
まずはWifiを扱う時に必要となるマネージャーを取得し、接続履歴の取得、周囲のwifiのスキャンを行いましょう。
// wifiマネージャーを取得する WifiManager manager = (WifiManager)getSystemService(WIFI_SERVICE); // 接続履歴のあるwifi一覧を取得 Listconfig_list = manager.getConfiguredNetworks(); // 現在検知しているwifiスポットの検索を開始する manager.startScan();
続いて、取得した周囲のWifiと、接続履歴を比較します。
そして、合致するものに関しては『WifiManager』のスタティックメソッド『compareSignalLevel()』にて
電波の強度を比較し、より強いWifiの情報を退避させて起きます。
最終的に残ったWifi情報が、電波が一番強い接続実績のある無線LANスポットとなります。
また、注意する点としては、SSIDの情報にダブルクォーテーションが付いている場合があります。
もし付いていると文字列比較がし難くなってしまいますので、
今回のケースではダブルクォーテーションを除去しています。
// 一番強いWifi情報の受け皿を用意する ScanResult best_signal = null; // 検知しているwifiスポットを順に確認する for(ScanResult result : manager.getScanResults()) { // 接続経験のあるスポットを順に確認する for(int i=0; i < config_list.size(); i++) { // Android4.2以降よりダブルクォーテーションが付いてくるので除去 String ssid = config_list.get(i).SSID.replace("\"", ""); // 接続経験のあるスポットを検知していれば、情報を退避する if(result.SSID.equals( ssid )){ // 初めと、後は比較して大きければ更新する if (best_signal == null || WifiManager.compareSignalLevel(best_signal.level, result.level) < 0 ){ best_signal = result; } } } } // 見つからなかったら終わり if( best_signal == null ){ return false; }
続いて、現在の接続情報を確認して、新しく接続しようとしているWifiスポットと同じであれば
何もせず、異なる場合は現在の接続を切断する処理を行いましょう。
// 現在の接続情報を取得 WifiInfo info = manager.getConnectionInfo(); // 現在と同じか確認する if( info.getSSID().equals( best_signal.SSID.replace("\"", "") ) ){ // 既に接続済みなので、成功を返却する return true; } else{ // 現在の接続を無効にする manager.disableNetwork(info.getNetworkId()); // 接続の関連付けを行う manager.enableNetwork(info.getNetworkId(), false); }
現在の接続と同じか否かはSSIDの比較にて行います。
この際にも、ダブルクォーテーションを意識して除去を行っています。
重複する場合は即座にreturnし、異なる場合は『disableNetwork()』にて接続を強制的に無効にします。
無効にする事で接続を切断する事が出来ますが、このままでは無効のままとなり、再度接続しようとしても
認識する事が出来ません。
ですので、接続情報を無効から有効へ復帰させる必要があります。そこで、他との接続に影響を及ばさない様に、
『enableNetwork()』メソッドによる接続時に、第二引数へfalseを指定しています。
こうする事で、Wifiへの接続が向こうの状態から有効へ復帰します。
最後に、新しく取得した電波強度の一番高いWifiスポットへ接続します。
// 接続設定情報を構築する WifiConfiguration config = new WifiConfiguration(); // Android4.2以降よりダブルクォーテーションが付いてくるので除去 config.SSID = best_signal.SSID.replace("\"", ""); // 接続に関する各設定を行う config.hiddenSSID = true; config.status = WifiConfiguration.Status.ENABLED; config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); config.allowedProtocols.set(WifiConfiguration.Protocol.RSN); // 接続する if( manager.addNetwork(config) == -1 ){ // 接続に失敗 return false; }; // 最新のコネクションへ強制的に接続する manager.saveConfiguration(); manager.updateNetwork(config); return manager.enableNetwork(config.networkId, true);
今度は、特定のWifiスポットへ接続しにいきますので、『enableNetwork()』メソッドの第二引数には『true』を指定し、
他の接続を破棄して強制的に目的のWifiスポットへ接続します。
これまでの処理を纏めると、下記の様になるかと思います。
public boolean bestWifiConnect() { // wifiマネージャーを取得する WifiManager manager = (WifiManager)getSystemService(WIFI_SERVICE); // 接続履歴のあるwifi一覧を取得 Listconfig_list = manager.getConfiguredNetworks(); // 現在検知しているwifiスポットの検索を開始する manager.startScan(); // 一番強いWifi情報の受け皿を用意する ScanResult best_signal = null; // 検知しているwifiスポットを順に確認する for(ScanResult result : manager.getScanResults()) { // 接続経験のあるスポットを順に確認する for(int i=0; i < config_list.size(); i++) { // Android4.2以降よりダブルクォーテーションが付いてくるので除去 String ssid = config_list.get(i).SSID.replace("\"", ""); // 接続経験のあるスポットを検知していれば、情報を退避する if(result.SSID.equals( ssid )){ // 初めと、後は比較して大きければ更新する if (best_signal == null || WifiManager.compareSignalLevel(best_signal.level, result.level) < 0 ){ best_signal = result; } } } } // 見つからなかったら終わり if( best_signal == null ){ return false; } // 現在の接続情報を取得 WifiInfo info = manager.getConnectionInfo(); // 現在と同じか確認する if( info.getSSID().equals( best_signal.SSID.replace("\"", "") ) ){ // 既に接続済みなので、成功を返却する return true; } else{ // 現在の接続を無効にする manager.disableNetwork(info.getNetworkId()); // 接続の関連付けを行う manager.enableNetwork(info.getNetworkId(), false); } // 接続設定情報を構築する WifiConfiguration config = new WifiConfiguration(); // Android4.2以降よりダブルクォーテーションが付いてくるので除去 config.SSID = best_signal.SSID.replace("\"", ""); // 接続に関する各設定を行う config.hiddenSSID = true; config.status = WifiConfiguration.Status.ENABLED; config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); config.allowedProtocols.set(WifiConfiguration.Protocol.RSN); // 接続する if( manager.addNetwork(config) == -1 ){ // 接続に失敗 return false; }; // 最新のコネクションへ強制的に接続する manager.saveConfiguration(); manager.updateNetwork(config); return manager.enableNetwork(config.networkId, true); }
後は、作成した上記メソッドを定期的に実行し、電波状況を監視して常に最適なWifi環境を提供しましょう。
メソッドの定期実行には『Timer』を使用しましょう。そして、間に『Handler』を挟む事によって、
電波状況を切り替えた際のViewへのアクセスも可能としています。
下記サンプルでは、1秒毎の定期実行を行っています。
タイマーに関しては、後の停止処理の事を考えメンバ変数として扱っています。
Timer timer = null; // 電波強度の高いwifiに接続する public void bestWifiConnectStart() { final Handler mHandler = new Handler(); this.timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { mHandler.post( new Runnable() { public void run() { // 接続を試みる if( MainActivity.this.bestWifiConnect() ){ Log.v("connect","OK"); } else{ Log.v("connect","NG"); } } }); } },0,1000); } // タイマーを停止する public void bestWifiConnectEnd() { this.timer.cancel(); }
これで自動的に電波強度の高いWifiへの接続切り替えが可能となります。
こういった機能はiPhone/iPad/iPodアプリでは出来ないAndroid独自の特権の様な実装ですので、
使い所がありましたら、是非ともユーザエクスペリエンスの一環として、導入を検討して下さい。
JUnitについての書籍が遂に発売します!
Java開発時のユニットテストを加速する「JUnit速効レシピ」
Java開発時のユニットテストを加速する「JUnit速効レシピ」