《深入理解Android:Wi-Fi,NFC和GPS》章节连载[节选]--第五章 深入理解WifiService

时间:2021-10-03 15:13:37

首先感谢各位兄弟姐妹们的耐心等待。本书预计在3月中旬上市发售。从今天开始,我将在博客中连载此书的一些内容。注意,此处连载的是未经出版社编辑的原始稿件,所以样子会有些非专业。

注意,如下是本章目录,本文节选5.4~5.5节

 

为了方便读者深入学习,本系列连载都会将作者研究过

  

程中所学习的参考文献列出来

 

                                                                                      第5章  深入理解WifiService

《深入理解Android:Wi-Fi,NFC和GPS》章节连载[节选]--第五章 深入理解WifiService

《深入理解Android:Wi-Fi,NFC和GPS》章节连载[节选]--第五章 深入理解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(

                                   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中的各个状态及层级关系:

《深入理解Android:Wi-Fi,NFC和GPS》章节连载[节选]--第五章 深入理解WifiService

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所示:

《深入理解Android:Wi-Fi,NFC和GPS》章节连载[节选]--第五章 深入理解WifiService

5-8  Captive Portal Check示意图

测试时,笔者禁用了无线网络安全,所以AirPcap可以解析这些没有加密的数据包。由图5-8可知,当笔者的Note 2发起HTTP GET请求后,小区宽带服务器回复了HTTP 302,所以笔者手机状态栏才会显然如图5-9所示的提示项以提醒笔者该无线网络需要登录。

《深入理解Android:Wi-Fi,NFC和GPS》章节连载[节选]--第五章 深入理解WifiService

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的介绍。读者仅作简单了解即可。