《深刻理解Android:Wi-Fi,NFC和GPS》章節連載[節選]--第五章 深刻理解WifiService

首先感謝各位兄弟姐妹們的耐心等待。本書預計在3月中旬上市發售。從今天開始,我將在博客中連載此書的一些內容。注意,此處連載的是未經出版社編輯的原始稿件,因此樣子會有些非專業。java

注意,以下是本章目錄,本文節選5.4~5.5節算法

 

爲了方便讀者深刻學習,本系列連載都會將做者研究過api

  

程中所學習的參考文獻列出來安全

 

                                                                                      第5章  深刻理解WifiService服務器

 

本章主要內容:

  • 介紹Android Framework中的WifiService及相關知識;
  • 介紹Android Framework中的WifiWatchdogStateMachine
  • 介紹Android Framework中和Captive Port Check相關知識

5.4  WifiWatchdogStateMachine和Captive Portal Check介紹

1 WifiWatchdogStateMachine介紹

WifiWatchdogStateMachine用於監控無線網絡的信號質量,它在WifiServicecheckAndStartWifi函數中被建立,其建立函數是makeWifiWatchdogStateMachine,代碼以下所示:網絡

[-->WifiWatchdogStateMachine.java::makeWifiWatchdogStateMachine]函數

public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) {學習

    ContentResolver contentResolver = context.getContentResolver();測試

 

    ConnectivityManager cm = (ConnectivityManager) context.getSystemService(google

                                   Context.CONNECTIVITY_SERVICE);

   //判斷手機是否只支持Wifi。很顯然,對於大部分手機來講,sWifiOnly爲false

    sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);

   //WIFI_WATCHDOG_ON功能默認是打開的

    putSettingsGlobalBoolean(contentResolver, Settings.Global.WIFI_WATCHDOG_ON, true);

 

    //建立一個WifiWatchdogStateMachine對象,它也是一個HSM

    WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);

    wwsm.start();//啓動HSM

     return wwsm;

 }

先來看WifiWatchdogStateMachine的初始化流程。

1  WifiWatchdogStateMachine構造函數分析

[-->WifiWatchdogStateMachine.java::WifiWatchdogStateMachine]

private WifiWatchdogStateMachine(Context context) {

   super(TAG);

   mContext = context;  mContentResolver = context.getContentResolver();

   mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

   //mWsmChannel用於和WifiStateMachine交互

   mWsmChannel.connectSync(mContext, getHandler(),

                           mWifiManager.getWifiStateMachineMessenger());

   //關鍵函數:setupNetworkReceiver將建立一個廣播接收對象,用於接收NETWORK_STATE_CHANGED_ACTION、

  // WIFI_STATE_CHANGED_ACTION、RSSI_CHANGED_ACTION、SUPPLICANT_STATE_CHANGED_ACTION等廣播

   setupNetworkReceiver();

 

   //監控Wifi Watchdog設置的變化狀況

   registerForSettingsChanges();

   registerForWatchdogToggle();

 

   addState(mDefaultState);

   ......//添加狀態,一共有9個狀態。如圖5-7所示

   //Wifi Watchdog默認是開啓的,故狀態機轉入NotConnectedState狀態

  if (isWatchdogEnabled())   setInitialState(mNotConnectedState);

  else   setInitialState(mWatchdogDisabledState);

 

  updateSettings();

 }

5-7所示爲WifiWatchdogStateMachine中的各個狀態及層級關係:

5-7  WifiWatchdogStateMachine狀態及層級關係

上面代碼中,WifiWatchdogStateMachine的初始狀態是NotConnectedState。不過這個狀態僅實現了enter函數,並且該函數中僅實現了一句打印輸出的代碼。因此NotConnectedState是一個象徵意義遠大於實際做用的類。

WifiWatchdogStateMachine徹底靠廣播事件來驅動。相關代碼在setupNetworkReceiver函數中。

[-->WifiWatchdogStateMachine.java::setupNetworkReceiver]

private void setupNetworkReceiver() {

   mBroadcastReceiver = new BroadcastReceiver() {

        public void onReceive(Context context, Intent intent) {

           String action = intent.getAction();

           if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {

              obtainMessage(EVENT_RSSI_CHANGE,

                 intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget();

           } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {

                  sendMessage(EVENT_SUPPLICANT_STATE_CHANGE, intent);

           } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {

                 sendMessage(EVENT_NETWORK_STATE_CHANGE, intent);

           } ......

           else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION))

                sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,intent.getIntExtra(

                            WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN));

      }

    };

 

    mIntentFilter = new IntentFilter();

    mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);

    ......//添加感興趣的廣播事件類型

    mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);

}

在前面介紹的WifiService工做流程中,SUPPLICANT_STATE_CHANGED_ACTIONNETWORK_STATE_CHANGED_ACTION廣播發送的次數很是頻繁。因此,WifiWatchdogStateMachine也不會太悠閒。

下面,咱們直接從WifiStateMachineVerifyingLinkStateenter函數中發送的NETWORK_STATE_CHANGED_ACTION廣播開始分析WifiWatchdogStateMachine的處理流程。

2  EVENT_NETWORK_STATE_CHANGE處理流程分析

該消息被NotConnectedState的父狀態WatchdogEnabledState處理,相關代碼以下所示:

[-->WifiWatchdogStateMachine.java::WatchdogEnabledState:processMessage]

  public boolean processMessage(Message msg) {

     Intent intent;

     switch (msg.what) {

       ......

       case EVENT_NETWORK_STATE_CHANGE:

             intent = (Intent) msg.obj;

             NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(

                                  WifiManager.EXTRA_NETWORK_INFO);

             mWifiInfo = (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);

             //更新bssid信息

             updateCurrentBssid(mWifiInfo != null ? mWifiInfo.getBSSID() : null);

 

             switch (networkInfo.getDetailedState()) {

                case VERIFYING_POOR_LINK://WifiStateMachine在VerifyingLinkState中設置的狀態

                      mLinkProperties = (LinkProperties) intent.getParcelableExtra(

                                    WifiManager.EXTRA_LINK_PROPERTIES);

                       //mPoorNetworkDetectionEnabled用於判斷是否須要監控AP的信號質量

                      if (mPoorNetworkDetectionEnabled) {

                            if (mWifiInfo == null || mCurrentBssid == null) {

                                  //下面這個函數將經過mWsmChannel向WifiStateMachine發送

                                  //GOOD_LINK_DETECTED消息

                                 sendLinkStatusNotification(true);

                             }

                             else  transitionTo(mVerifyingLinkState);//進入VerifyingLinkState

                      } else  sendLinkStatusNotification(true);

                  break;

                case CONNECTED://WifiStateMachine在ConnectedState中設置的狀態,請讀者自行分析

               //OnlineWatchState的處理流程

                       transitionTo(mOnlineWatchState);

              }

           .......

       }

     return HANDLED;

  }

若是WifiWatchdogStateMachine開啓了無線網絡信號質量監控的話,它將轉入VerifyingLinkState,其enter函數以下所示:

[-->WifiWatchdogStateMachine.java::VerifyingLinkState:enter]

public void enter() {

     mSampleCount = 0;

     mCurrentBssid.newLinkDetected();

     //向本身發送CMD_RSSI_FETCH消息

     sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0));

  }

來看CMD_RSSI_FETCH消息的處理。

3  CMD_RSSI_FETCH處理流程分析

[-->WifiWatchdogStateMachine.java::VerifyingLinkState:processMessage]

public boolean processMessage(Message msg) {

    switch (msg.what) {

       ......

       case CMD_RSSI_FETCH:

            if (msg.arg1 == mRssiFetchToken) {

                  /*

                   向WifiStateMachine發送RSSI_PKTCNT_FETCH消息,WifiStateMachine的處理過程

                   就是調用WifiNative的signalPoll和pktcntPoll以獲取RSSI、LinkSpeed、發送

                   Packet的總個數、發送失敗的Packet總個數。注意,4.2中的WPAS才支持pktcntPoll。

                   WifiStateMachine處理完RSSI_PKTCNT_FETCH後將回復RSSI_PKTCNT_FETCH_SUCCEEDED

                   消息給WifiWatchdogStateMachine

                   */

                  mWsmChannel.sendMessage(WifiManager.RSSI_PKTCNT_FETCH);

                  //LINK_SAMPLING_INTERVAL_MS值爲1000ms

                  sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0),

                                LINK_SAMPLING_INTERVAL_MS);

             }

           break;

       case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:

           //WifiStateMachine回覆的消息中攜帶一個RssiPacketCountInfo對象

           RssiPacketCountInfo info = (RssiPacketCountInfo) msg.obj;

           int rssi = info.rssi;

           /*

             WifiWatchdog用了一個名爲指數加權移動平均算法(Volume-weighted Exponential

             Moving Average)的方法來辨別網絡信號質量的好壞。本書不擬對它進行討論,感興趣的讀者不妨

             自行研究

           */

           long time = mCurrentBssid.mBssidAvoidTimeMax - SystemClock.elapsedRealtime();

             //假設網絡質量很好,則調用sendLinkStateNotification以發送GOOD_LINK_DETECT消息給

            //WifiStateMachine

            if (time <= 0) sendLinkStatusNotification(true);

            else {

                 //此時的rssi好於某個閾值。mGoodLinkTargetRssi由算法計算得來

                 if (rssi >= mCurrentBssid.mGoodLinkTargetRssi) {

                      //當採樣次數大於必定值時,才認爲網絡狀態變好。mGoodLinkTargetCount也是經過

                     //相關方法計算得來

                      if (++mSampleCount >= mCurrentBssid.mGoodLinkTargetCount) {

                            mCurrentBssid.mBssidAvoidTimeMax = 0;

                            sendLinkStatusNotification(true);

                     }

                 } else  mSampleCount = 0;

             }

             break;

          ......

      }

    return HANDLED;

}

WifiWatchdogStateMachine檢測到Pool link時,它將發送POOL_LINK_DETECT消息給WifiStateMachine去處理。相關流程請感興趣的讀者自行研究。

WifiWatchdogStateMachine是一個比較有趣的模塊。其目的很簡單,就是監測無線網絡的信號質量,而後作相應動做。另外,WifiWatchdogStateMachine還使用了一些比較高級的算法來判斷網絡信號質量的好壞,感興趣的讀者不妨進行一番深刻研究。

2.  Captive Portal Check介紹

Android 4.2中,Captive Portal Check功能集中在CaptivePortalTracker類中,並且它也是一個HSMCaptivePortalTracker的建立位於ConnectivityService構造函數中。在那裏,它的makeCaptivePortalTracker函數被調用。相關代碼以下所示:

[-->CaptivePortalTracker.java::makeCaptivePortalTracker]

public static CaptivePortalTracker makeCaptivePortalTracker(Context context,

            IConnectivityManager cs) {

  CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, cs);

  captivePortal.start();//啓動HSM

  return captivePortal;

 }

立刻來看CaptivePortalTracker的構造函數

1  CaptivePortalTracker構造函數分析

[-->CaptivePortalTracker.java::CaptivePortalTracker]

private CaptivePortalTracker(Context context, IConnectivityManager cs) {

   super(TAG);

 

  mContext = context;mConnService = cs;

  mTelephonyManager = (TelephonyManager) context.getSystemService(

                                      Context.TELEPHONY_SERVICE);

   //註冊一個廣播接收對象,用於處理CONNECTIVITY_ACTION消息

   IntentFilter filter = new IntentFilter();

   filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);

   mContext.registerReceiver(mReceiver, filter);

   //CAPTIVE_PORTAL_SERVER用於設置進行Captive Portal Check測試的服務器地址

   mServer = Settings.Global.getString(mContext.getContentResolver(),

                               Settings.Global.CAPTIVE_PORTAL_SERVER);

   //若是沒有指明服務器地址的,則採用DEFAULT_SERVER,其地址是「clients3.google.com」

   if (mServer == null) mServer = DEFAULT_SERVER;

    //是否開啓Captive Portal Check功能,默認是開始

    mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),

                Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;

 

    addState(mDefaultState);//CaptivePortalTracker只有4個狀態

            addState(mNoActiveNetworkState, mDefaultState);

            addState(mActiveNetworkState, mDefaultState);

                addState(mDelayedCaptiveCheckState, mActiveNetworkState);

   setInitialState(mNoActiveNetworkState);

}

CaptivePortalTracker只監聽CONNECTIVITY_ACTION廣播,而WifiService相關模塊並不會發送這個廣播。那麼,在前面介紹的流程中,哪一步會觸發CaptivePortalTracker進行工做呢?來看下節。

2  CMD_CONNECTIVITY_CHANGE處理流程分析

Wifi網絡鏈接成功時,ConnectivityServicehandleConnect將被觸發,該函數內部將發送一個CONNECTIVITY_ACTION消息。這個消息將被CaptivePortalTracker註冊的廣播接收對象處理。相關代碼以下所示:

[-->CaptivePortalTracker.java::onReceive]

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {

        public void onReceive(Context context, Intent intent) {

            String action = intent.getAction();

            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {

                NetworkInfo info = intent.getParcelableExtra(

                        ConnectivityManager.EXTRA_NETWORK_INFO);

                //向狀態機發送CMD_CONNECTIVITY_CHANGE消息

                sendMessage(obtainMessage(CMD_CONNECTIVITY_CHANGE, info));

            }

        }

    };

CaptivePortalTrackerNoActiveNetworkState將處理該消息。相關代碼以下所示:

[-->CaptivePortalTracker.java::NoActiveNetworkState:processMessage]

public boolean processMessage(Message message) {

      InetAddress server;     NetworkInfo info;

      switch (message.what) {

         case CMD_CONNECTIVITY_CHANGE:

               info = (NetworkInfo) message.obj;

               //無線網絡已經鏈接成功,而且手機當前使用的就是Wifi。isActiveNetwork將查詢

               //ConnectivityService以獲取當前活躍的數據連接類型

               if (info.isConnected() && isActiveNetwork(info)) {

                     mNetworkInfo = info;

                     transitionTo(mDelayedCaptiveCheckState);//轉移到DelayedCaptiveCheckState

               }

         ......

        }

    return HANDLED;

}

來看DelayedCaptiveCheckState

3  CMD_DELAYED_CAPTIVE_CHECK處理流程分析

代碼以下所示:

[-->CaptivePortalTracker.java::DelayedCaptiveCheckState]

private class DelayedCaptiveCheckState extends State {

       public void enter() {

            //發送一個延遲消息,延遲時間爲10秒

            sendMessageDelayed(obtainMessage(CMD_DELAYED_CAPTIVE_CHECK,

                        ++mDelayedCheckToken, 0), DELAYED_CHECK_INTERVAL_MS);

        }

        public boolean processMessage(Message message) {

           switch (message.what) {

                case CMD_DELAYED_CAPTIVE_CHECK:

                    if (message.arg1 == mDelayedCheckToken) {

                        InetAddress server = lookupHost(mServer);//獲取Server的ip地址

                        if (server != null) {

                            //AP是否須要Captive Portal Check。若是是的話,setNotificiationVisible

                           //將在狀態欄中添加一個提醒信息

                            if (isCaptivePortal(server))  setNotificationVisible(true);

                        }

                         transitionTo(mActiveNetworkState);//轉到ActiveNetworkState

                    }

                    break;

                default:

                    return NOT_HANDLED;

            }

            return HANDLED;

        }

    }

上述代碼中,isCaptivePortal用於判斷server是否須要Captive Portal Check。其代碼以下所示:

[-->CaptivePortalTracker.java::isCaptivePortal]

private boolean isCaptivePortal(InetAddress server) {

    HttpURLConnection urlConnection = null;

    if (!mIsCaptivePortalCheckEnabled) return false;

    //mUrl實際訪問的地址是:http://clients3.google.com/generate_204

    mUrl = "http://" + server.getHostAddress() + "/generate_204";

    /*

    Captive Portal的測試很是簡單,就是向mUrl發送一個HTTP GET請求。若是無線網絡提供商沒有設置Portal

    Check,則HTTP GET請求將返回204。204表示請求處理成功,但沒有數據返回。若是無線網絡提供商設置了

    Portal Check,則它必定會重定向到某個特定網頁。這樣,HTTP GET的返回值就不是204

   */

    try {

            URL url = new URL(mUrl);

            urlConnection = (HttpURLConnection) url.openConnection();

            urlConnection.setInstanceFollowRedirects(false);

            urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);

            urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);

            urlConnection.setUseCaches(false);

            urlConnection.getInputStream();

            return urlConnection.getResponseCode() != 204;

     }......

}

處理完畢後,CaptivePortalTracker將轉入ActiveNetworkState狀態。該狀態的內容很是簡單,讀者可自行閱讀它。因爲筆者家中所在小區寬帶提供商使用了Capive Portal Check,因此筆者利用AirPcap截獲了相關網絡交換數據,如圖5-8所示:

5-8  Captive Portal Check示意圖

測試時,筆者禁用了無線網絡安全,因此AirPcap能夠解析這些沒有加密的數據包。由圖5-8可知,當筆者的Note 2發起HTTP GET請求後,小區寬帶服務器回覆了HTTP 302,因此筆者手機狀態欄纔會顯然如圖5-9所示的提示項以提醒筆者該無線網絡須要登陸。

5-9  Captive Portal Check提示示意圖

4  CaptivePortalTracker總結和討論

WifiWatchdogStateMachine同樣,CaptivePortalTracker也比較有意思。CaptivePortalTracker類出現於4.2中。而4.1中的Captive Portacl Check功能則是由WifiWatchdogStateMachin來完成的。在4.1中,WifiWatchdogStateMachine定義了一個名爲WalledGardenCheckState的類用於處理Captive Portal Check

不過,筆者在研究4.14.2相關模塊的代碼後,發現4.2中的處理彷佛有一些問題。此處先記下此問題,但願有興趣的讀者參與討論。該問題以下:

  • 4.2中,WifiStateMachineConnectedState前增長了一個CaptivePortalCheckState。很明顯,CaptivePortalCheckState的目的是在WifiStateMachine轉入ConnectedState以前完成Captive Portal Check。但根據本節對CaptivePortalTracker的介紹,只有WifiStateMachine進入ConnectedState後,ConnectivityService纔會發送CONNECTIVITY_ACTION廣播。而在4.1中,WifiWatchdogStateMachine也是在WifiStateMachine轉入ConnectedState後進入WalledGardenCheckState的。

提示:那麼,WifiStateMachine如何從CaptivePortalCheckState轉爲ConnectedState呢?答案在ConnectivityServicehandleCaptivePortalTrackerCheck函數中。在那裏,WifiStateMachinecaptivePortalCheckComplete函數最終會被調用。在那個函數中,CMD_CAPTIVE_CHECK_COMPLETE將被髮送,故CaptivePortalCheckState纔會轉入ConnectedState狀態。可是,在這個流程中,CaptivePortalTracker並未被真正觸發以進行Captive Portal Check

 

5.5  本章總結和參考資料說明

5.5.1 本章總結

本章對WifiService相關模塊、WifiWatchdogStateMachine以及CaptivePortalTracker進行了一番介紹。其中:

  • HSMAsyncChannel是本章的重要基礎知識。但願讀者認真學習它們的用法。
  • WifiService中的WifiStateMachine是本章的核心。其定義的狀態之多、處理之曲折在Android系統中算是很是突出的。若是條件容許的話,讀者不妨經過Eclipse來調試它以加深認識。
  • WifiWatchdogStateMacineCaptivePortalTracker內容比較簡單,但其功能卻比較有意思。對於這部份內容,讀者作簡單瞭解便可。

筆者一直以爲WifiService的實現過於複雜,並且其運行效率較低。不知道讀者看完本章後是否有同感。

5.5.2 參考資料說明

Captive Portal Check

[1]  https://en.wikipedia.org/wiki/Captive_portal

維基百科對Captive Portal的介紹。讀者僅做簡單瞭解便可。

相關文章
相關標籤/搜索