Wifiの電波強度によって、接続先の無線LANスポットを変える方法!

JUnitについての書籍が遂に発売します!
Java開発時のユニットテストを加速する「JUnit速効レシピ」



Androidでは、アプリによってWifiの接続先を切り替える事が出来ます。
今回は、そんな接続先の切り替えを、各無線LANスポットの電波強度によって
自動的に判別し、一番電波状態の良い無線LANスポットへ自動的に接続を切り替える仕組みの
実装方法について解説します。



  1. 接続情報を取得する
  2. 周辺の無線LANスポットを検索する
  3. 接続履歴を取得する
  4. 電波強度の高い無線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』オブジェクトには、プロパティとして下記情報が格納されています。




BSSIDMACアドレス
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一覧を取得
List config_list = manager.getConfiguredNetworks();


取得出来るのは、無線LAN個々の設定情報を保持した『WifiConfiguration』のリストです。
『WifiConfiguration』では、下記の情報をプロパティから取得する事が出来ます。


BSSIDMACアドレス
SSIDネットワーク識別名
allowedAuthAlgorithms認証プロトコルセット
allowedGroupCiphersグループの暗号セット
allowedKeyManagementキー管理プロトコルのセット
allowedPairwiseCiphersWPAのための暗号とのペア情報
allowedProtocolsセキュリティプロトコルのセット
enterpriseConfigEAP関連の詳細情報
hiddenSSIDステルスモードの状態
networkIdネットワークID
preSharedKeyWPA-PSKのキー
prioritye優先度
statusネットワークの状態
wepKeysWEPキー
wepTxKeyIndexWEPキーのインデックス


// wifiマネージャーを取得する
WifiManager manager = (WifiManager)getSystemService(WIFI_SERVICE);

// 接続実績のあるwifi一覧を取得
List config_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一覧を取得
List config_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一覧を取得
 List config_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速効レシピ」

コメントをお待ちしています

人気の投稿

Category

Algorithm (2) Android (8) ASP/aspx (1) Blogger (2) C/C++ (1) Chrome (5) CSS (9) Firefox (4) Fortran (1) Google (9) GoogleMap (2) HTML (12) IE (3) Information (4) iOS (2) iPhone/iPad/iPod (2) Java (6) JavaScript (16) jQuery (9) JSP (1) LifeRecipe (5) Linux (2) Macintosh (2) MapKit (4) Marketing (7) MySQL (3) NAMAZU (2) Objective-C (7) Other (7) Perl (1) PHP (9) Python (1) RSS/Atom (2) Ruby (1) Safari (2) SEO (11) Smarty (2) SQL (2) Tex (1) Three.js (1) Twitter (1) TwitterLog (313) UIKit (5) Unix (1) VBA/VBS (1) Windows (5) WordPress (3) Writing (5) XAMPP (1) XML (1) Yahoo (2) ZendFramework2 (14)