5

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

 3 years ago
source link: https://blog.csdn.net/Innost/article/details/20863901
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

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

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

                                                                                      第5章  深入理解WifiService

本章主要内容:

  • 介绍Android Framework中的WifiService及相关知识;
  • 介绍Android Framework中的WifiWatchdogStateMachine;
  • 介绍Android Framework中和Captive Port Check相关知识

5.4  WifiWatchdogStateMachine和Captive Portal Check介绍

1 WifiWatchdogStateMachine介绍

WifiWatchdogStateMachine用于监控无线网络的信号质量,它在WifiService的checkAndStartWifi函数中被创建,其创建函数是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中的各个状态及层级关系:

图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_ACTION和NETWORK_STATE_CHANGED_ACTION广播发送的次数非常频繁。所以,WifiWatchdogStateMachine也不会太清闲。

下面,我们直接从WifiStateMachine在VerifyingLinkState的enter函数中发送的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类中,而且它也是一个HSM。CaptivePortalTracker的创建位于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网络连接成功时,ConnectivityService的handleConnect将被触发,该函数内部将发送一个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));

CaptivePortalTracker的NoActiveNetworkState将处理该消息。相关代码如下所示:

[-->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

            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.1和4.2相关模块的代码后,发现4.2中的处理似乎有一些问题。此处先记下此问题,希望有兴趣的读者参与讨论。该问题如下:

  • 4.2中,WifiStateMachine在ConnectedState前增加了一个CaptivePortalCheckState。很明显,CaptivePortalCheckState的目的是在WifiStateMachine转入ConnectedState之前完成Captive Portal Check。但根据本节对CaptivePortalTracker的介绍,只有WifiStateMachine进入ConnectedState后,ConnectivityService才会发送CONNECTIVITY_ACTION广播。而在4.1中,WifiWatchdogStateMachine也是在WifiStateMachine转入ConnectedState后进入WalledGardenCheckState的。

提示:那么,WifiStateMachine如何从CaptivePortalCheckState转为ConnectedState呢?答案在ConnectivityService的handleCaptivePortalTrackerCheck函数中。在那里,WifiStateMachine的captivePortalCheckComplete函数最终会被调用。在那个函数中,CMD_CAPTIVE_CHECK_COMPLETE将被发送,故CaptivePortalCheckState才会转入ConnectedState状态。但是,在这个流程中,CaptivePortalTracker并未被真正触发以进行Captive Portal Check。

5.5  本章总结和参考资料说明

5.5.1 本章总结

本章对WifiService相关模块、WifiWatchdogStateMachine以及CaptivePortalTracker进行了一番介绍。其中:

  • HSM和AsyncChannel是本章的重要基础知识。希望读者认真学习它们的用法。
  • WifiService中的WifiStateMachine是本章的核心。其定义的状态之多、处理之曲折在Android系统中算是非常突出的。如果条件允许的话,读者不妨通过Eclipse来调试它以加深认识。
  • WifiWatchdogStateMacine和CaptivePortalTracker内容比较简单,但其功能却比较有意思。对于这部分内容,读者做简单了解即可。

笔者一直觉得WifiService的实现过于复杂,而且其运行效率较低。不知道读者看完本章后是否有同感。

5.5.2 参考资料说明

Captive Portal Check

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

维基百科对Captive Portal的介绍。读者仅作简单了解即可。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK