1. 程式人生 > >關於WiFi模組(連線流程介紹)

關於WiFi模組(連線流程介紹)

 (一)基本類的呼叫

Android 已經幫助我們做好了一些可以直接呼叫的類
開啟andriod.net.wifi 我們可以看到Android 提供的幾個類。
在看這一部分之前,最好能用一下PC 上的wifi,這樣對我們的理解會很有幫助!
這裡列了很多,但是大致可以分為四個主要的類ScanResult,wifiConfiguration,WifiInfo,WifiManager

(1)ScanResult,主要是通過wifi 硬體的掃描來獲取一些周邊的wifi 熱點的資訊。
在我們進行wifi 搜尋的時候,一般會搜到這些資訊,首先是接入點名字、接入點資訊的強弱、還有接入點使用的安全模式,是WPA、WPE。

BSSID 接入點的地址,這裡主要是指小範圍幾個無線裝置相連線所獲取的地址,比如說兩臺筆記本通過無線網絡卡進行連線,雙方的無線網絡卡分配的地址
SSID 網路的名字,當我們搜尋一個網路時,就是靠這個來區分每個不同的網路接入點
Capabilities 網路接入的效能,這裡主要是來判斷網路的加密方式等
Frequency 頻率,每一個頻道互動的MHz 數
Level 等級,主要來判斷網路連線的優先數。
這裡只提供了一個方法,就是將獲得資訊程式設計字串toString()

(2)wifiConfiguration 在我們連通一個wifi 接入點的時候,需要獲取到的一些資訊。

六個子類
WifiConfiguration.AuthAlgorthm 用來判斷加密方法
WifiConfiguration.GroupCipher 獲取使用GroupCipher 的方法來進行加密
WifiConfiguration.KeyMgmt 獲取使用KeyMgmt 進行
WifiConfiguration.PairwiseCipher 獲取使用WPA 方式的加密
WifiConfiguration.Protocol 獲取使用哪一種協議進行加密
wifiConfiguration.Status 獲取當前網路的狀態

(3)WifiInfo 在我們的wifi 已經連通了以後,可以通過這個類獲得一些已經連通的wifi 連線的資訊getBSSID() 獲取BSSID
getDetailedStateOf() 獲取客戶端的連通性,
getHiddenSSID() 獲得SSID 是否被隱藏
getIpAddress() 獲取IP 地址
getLinkSpeed() 獲得連線的速度
getMacAddress() 獲得Mac 地址
getRssi() 獲得802.11n 網路的訊號
getSSID() 獲得SSID
getSupplicanState() 返回具體客戶端狀態的資訊

(4)wifiManager 這個不用說,就是用來管理我們的wifi 連線。


這裡來說相對複雜,裡面的內容比較多,但是通過字面意思,我們還是可以獲得很多相關的資訊。這
個類裡面預先定義了許多常量,我們可以直接使用,不用再次建立。
addNetwork(WifiConfiguration config) 通過獲取到的網路的連結狀態資訊,來新增網路

calculateSignalLevel(int rssi , int numLevels) 計算訊號的等級
compareSignalLevel(int rssiA, int rssiB) 對比連線A 和連線B
createWifiLock(int lockType, String tag) 建立一個wifi 鎖,鎖定當前的wifi 連線
disableNetwork(int netId) 讓一個網路連線失效
disconnect() 斷開連線
enableNetwork(int netId, Boolean disableOthers) 連線一個連線
getConfiguredNetworks() 獲取網路連線的狀態
getConnectionInfo() 獲取當前連線的資訊
getDhcpInfo() 獲取DHCP 的資訊
getScanResulats() 獲取掃描測試的結果
getWifiState() 獲取一個wifi 接入點是否有效
isWifiEnabled() 判斷一個wifi 連線是否有效
pingSupplicant() ping 一個連線,判斷是否能連通
ressociate() 即便連線沒有準備好,也要連通
reconnect() 如果連線準備好了,連通
removeNetwork() 移除某一個網路
saveConfiguration() 保留一個配置資訊

setWifiEnabled() 讓一個連線有效
startScan() 開始掃描
updateNetwork(WifiConfiguration config) 更新一個網路連線的資訊
此外wifiManaer 還提供了一個內部的子類,也就是wifiManagerLock
WifiManagerLock 的作用是這樣的,在普通的狀態下,如果我們的wifi 的狀態處於閒置,那麼網路的連通,將會暫時中斷。但是如果我們把當前的網路狀態鎖上,那麼wifi 連通將會保持在一定狀態,當然接觸鎖定之後,就會恢復常態。

(二)wifi流程

(1)初始化

在 SystemServer 啟動的時候,會生成一個 ConnectivityService 的例項,

              try {
                    Log.i(TAG, "Starting Connectivity Service.");
                    ServiceManager.addService(Context.CONNECTIVITY_SERVICE, new ConnectivityService(context));
              } catch (Throwable e) {
                    Log.e(TAG, "Failure starting Connectivity Service", e);
              }

ConnectivityService 的建構函式會建立 WifiService,

if (DBG) {

 Log.v(TAG, "Starting Wifi Service.");

}

          mWifiStateTracker = new WifiStateTracker(context, handler);
          WifiService wifiService = new WifiService(context, mWifiStateTracker);
          ServiceManager.addService(Context.WIFI_SERVICE, wifi
Service);

WifiStateTracker 會建立 WifiMonitor 接收來自底層的事件,WifiService 和 WifiMonitor 是整個模組的核心。WifiService 負責啟動關閉 wpa_supplicant、啟動關閉 WifiMonitor 監視執行緒和把命令下發給 wpa_supplicant,而 WifiMonitor 則負責從 wpa_supplicant 接收事件通知。

(2)連線AP

1. 使能 WIFI

WirelessSettings 在初始化的時候配置了由 WifiEnabler 來處理 Wifi 按鈕,

     private void initToggles() {

          mWifiEnabler = new WifiEnabler(this,  (WifiManager) getSystemService(WIFI_SERVICE),(CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI));

當用戶按下 Wifi 按鈕後,  Android 會呼叫 WifiEnabler 的 onPreferenceChange,    再由 WifiEnabler呼叫 WifiManager 的 setWifiEnabled 介面函式,通過 AIDL,實際呼叫的是 WifiService 的setWifiEnabled 函式,WifiService 接著向自身傳送一條 MESSAGE_ENABLE_WIFI 訊息,在處理該訊息的程式碼中做真正的使能工作:首先裝載 WIFI 核心模組(該模組的位置硬編碼為"/system/lib/modules/wlan.ko" ), 然 後 啟 動 wpa_supplicant ( 配 置 文 件 硬 編 碼 為"/data/misc/wifi/wpa_supplicant.conf") 再通過 WifiStateTracker 來啟動 WifiMonitor 中的監視執行緒。

     private boolean setWifiEnabledBlocking(boolean enable) {

          final  int eventualWifiState =  enable ? WIFI_STATE_ENABLED :WIFI_STATE_DISABLED;

updateWifiState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING);

          if (enable) {

              if (!WifiNative.loadDriver()) {

                       Log.e(TAG, "Failed to load Wi-Fi driver.");

             updateWifiState(WIFI_STATE_UNKNOWN);

                       return false;
                }

           if (!WifiNative.startSupplicant()) {

                       WifiNative.unloadDriver();

             Log.e(TAG, "Failed to start supplicant daemon.");

                       updateWifiState(WIFI_STATE_UNKNOWN);

                       return false; 

                }
                mWifiStateTracker.startEventLoop();
          }
             // Success!
             persistWifiEnabled(enable);
             updateWifiState(eventualWifiState);
             return true;
      }

當使能成功後,會廣播發送 WIFI_STATE_CHANGED_ACTION 這個 Intent 通知外界 WIFI已 經 成 功 使 能 了 。 WifiEnabler 創 建 的 時 候 就 會 向 Android 注 冊 接 收WIFI_STATE_CHANGED_ACTION,因此它會收到該 Intent,從而開始掃描。

          private void handleWifiStateChanged(int wifiState) {
             if (wifiState == WIFI_STATE_ENABLED) {

                  loadConfiguredAccessPoints();

                  attemptScan();

             }

2. 查詢 AP

掃描的入口函式是 WifiService 的 startScan,它其實也就是往 wpa_supplicant 傳送 SCAN 命令。

static jboolean android_net_wifi_scanCommand(JNIEnv* env, jobject clazz)

{

      jboolean result;

      // Ignore any error from setting the scan mode.

      // The scan will still work.

      (void)doBooleanCommand("DRIVER SCAN-ACTIVE", "OK");

      result = doBooleanCommand("SCAN", "OK");

      (void)doBooleanCommand("DRIVER SCAN-PASSIVE", "OK");

      return result;

}

當 wpa_supplicant 處理完 SCAN 命令後,它會向控制通道傳送事件通知掃描完成,從而wifi_wait_for_event 函式會接收到該事件,由此 WifiMonitor 中的 MonitorThread 會被執行來出來這個事件,

             void handleEvent(int event, String remainder) {
                        case SCAN_RESULTS:
                             mWifiStateTracker.notifyScanResultsAvailable();
                             break;

WifiStateTracker 則接著廣播發送 SCAN_RESULTS_AVAILABLE_ACTION 這個 Intent

                  case EVENT_SCAN_RESULTS_AVAILABLE:
                        mContext.sendBroadcast(new 
Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));

WifiLayer 註冊了接收 SCAN_RESULTS_AVAILABLE_ACTION 這個 Intent,所以它的相關處理函式 handleScanResultsAvailable 會被呼叫,在該函式中,先會去拿到 SCAN 的結果(最終是往 wpa_supplicant 傳送 SCAN_RESULT 命令並讀取返回值來實現的)                             ,

                List<ScanResult> list = mWifiManager.getScanResults();

對每一個掃描返回的 AP,WifiLayer 會呼叫 WifiSettings 的 onAccessPointSetChanged 函式,從而最終把該 AP 加到 GUI 顯示列表中。

     public void onAccessPointSetChanged(AccessPointState ap, boolean added) {

          AccessPointPreference pref = mAps.get(ap);

          if (added) {

                if (pref == null) {

                      pref = new AccessPointPreference(this, ap);

                      mAps.put(ap, pref);

                } else {

                      pref.setEnabled(true);

                }

                mApCategory.addPreference(pref);

          }

     }

3. 配置 AP 引數

當用戶在 WifiSettings 介面上選擇了一個 AP 後,會顯示配置 AP 引數的一個對話方塊,

     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {

          if (preference instanceof AccessPointPreference) {

                AccessPointState  state = ((AccessPointPreference) preference).getAccessPointState();

                showAccessPointDialog(state, AccessPointDialog.MODE_INFO);

          }

     }

4. 連線

當用戶在 AcessPointDialog 中選擇好加密方式和輸入金鑰之後,再點選連線按鈕,Android就會去連線這個 AP。

     private void handleConnect() {

          String password = getEnteredPassword();

          if (!TextUtils.isEmpty(password)) {

                mState.setPassword(password);

          }

          mWifiLayer.connectToNetwork(mState);

     }

WifiLayer 會先檢測這個 AP 是不是之前被配置過,這個是通過向 wpa_supplicant 傳送LIST_NETWORK 命令並且比較返回值來實現的,

          // Need WifiConfiguration for the AP

          WifiConfiguration config = findConfiguredNetwork(state);

如果 wpa_supplicant 沒有這個 AP 的配置資訊, 則會向 wpa_supplicant 傳送 ADD_NETWORK命令來新增該 AP,

          if (config == null) {

                // Connecting for the first time, need to create it

                config = addConfiguration(state, ADD_CONFIGURATION_ENABLE|ADD_CONFIGURATION_SAVE);

          }

ADD_NETWORK 命 令 會 返 回 一 個 ID , WifiLayer 再 用 這 個 返 回 的 ID 作 為 參 數 向wpa_supplicant 傳送 ENABLE_NETWORK 命令,從而讓 wpa_supplicant 去連線該 AP。

          // Make sure that network is enabled, and disable others

          mReenableApsOnNetworkStateChange = true;

          if (!mWifiManager.enableNetwork(state.networkId, true)) {

                Log.e(TAG, "Could not enable network ID " + state.networkId);

                error(R.string.error_connecting);

                return false;

          }

5. 配置 IP 地址

當 wpa_supplicant 成功連線上 AP 之後,它會向控制通道傳送事件通知連線上 AP 了,從而wifi_wait_for_event 函式會接收到該事件,由此 WifiMonitor 中的 MonitorThread 會被執行來出來這個事件,

          void handleEvent(int event, String remainder) {

                     case CONNECTED:

                           handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED,remainder);

                           break;

WifiMonitor 再呼叫 WifiStateTracker 的 notifyStateChange,WifiStateTracker 則接著會往自身傳送 EVENT_DHCP_START 訊息來啟動 DHCP 去獲取 IP 地址,

     private void handleConnectedState() {

          setPollTimer();

          mLastSignalLevel = -1;

          if (!mHaveIPAddress && !mObtainingIPAddress) {

                mObtainingIPAddress = true;

                mDhcpTarget.obtainMessage(EVENT_DHCP_START).sendToTarget();

          }

     }

然後再廣播發送 NETWORK_STATE_CHANGED_ACTION 這個 Intent

                case EVENT_NETWORK_STATE_CHANGED:

                     if (result.state != DetailedState.DISCONNECTED || !mDisconnectPending) {

                           intent   = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);

                           intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo);

                          if (result.BSSID != null)

                                intent.putExtra(WifiManager.EXTRA_BSSID, result.BSSID);

                                mContext.sendStickyBroadcast(intent);

                     }

                     break;

WifiLayer 註冊了接收 NETWORK_STATE_CHANGED_ACTION 這個 Intent,所以它的相關處理函式 handleNetworkStateChanged 會被呼叫,當 DHCP 拿到 IP 地址之後,會再發送 EVENT_DHCP_SUCCEEDED 訊息,

     private class DhcpHandler extends Handler {

          public void handleMessage(Message msg) {

                switch (msg.what) {

                     case EVENT_DHCP_START:

                          if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {

                                event = EVENT_DHCP_SUCCEEDED;

                                                      }

WifiLayer 處 理 EVENT_DHCP_SUCCEEDED 消 息 , 會 再 次 廣 播 發 送NETWORK_STATE_CHANGED_ACTION 這個 Intent,這次帶上完整的 IP 地址資訊。

                case EVENT_DHCP_SUCCEEDED:

                     mWifiInfo.setIpAddress(mDhcpInfo.ipAddress);

                     setDetailedState(DetailedState.CONNECTED);

                     intent  =  new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);

                     intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo);

                     mContext.sendStickyBroadcast(intent);

                     break;

至此為止,整個連線過程完成