Android WiFi 扫描和连接热点

本章主要介绍用户手动的在Settings中点击Scan和Connect按钮,输入密码后的连接过程,先看整体流程图:

Android WiFi 扫描和连接热点_第1张图片

WiFi Scan过程分析

当用户进入Settings点击Scan后,就会调用到WifiManager的startScan()方法,当然在Settings里面有设置Scan的定时器,每隔一段时间就会去scan,在Wifi Framework中也有scan的定时器。对照上面的流程图,来看一下WifiManager的startScan()方法:

    public boolean startScan(WorkSource workSource) {
        try {
            mService.startScan(workSource);
            return true;
        } catch (RemoteException e) {
            return false;
        }
    }

WifiService.java
    public void startScan(WorkSource workSource) {
        enforceChangePermission();
        if (workSource != null) {
            enforceWorkSourcePermission();
            // WifiManager currently doesn't use names, so need to clear names out of the
            // supplied WorkSource to allow future WorkSource combining.
            workSource.clearNames();
        }
        mWifiStateMachine.startScan(Binder.getCallingUid(), workSource);
    }

WiFiStateMachine的startScan方法会给自己发送一个CMD_START_SCAN的message,由前面toggle on wifi的知识,这个消息将由DisconnectedState及其父State来处理,从代码中可以很容易的分析到,CMD_START_SCAN将会被DriverStartedState 来处理,进入到处理的代码中:

        public boolean processMessage(Message message) {
            switch(message.what) {
                case CMD_START_SCAN:
                    noteScanStart(message.arg1, (WorkSource) message.obj);
                    startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP);
                    break;

noteScanStart是用于通知电量统计用;startScanNative会向wpa_supplicant发送SCAN的命令,当wpa_suppliant执行完SCAN并成功找到一些AP后,就会给WifiMonitor发送CTRL-EVENT-SCAN-RESULTS的event,WifiMonitor会parse出这个event,并向WifiStateMachine发送SCAN_RESULTS_EVENT消息,WifiStateMachine的SupplicantStartedState会处理这个消息,如下:

                case WifiMonitor.SCAN_RESULTS_EVENT:
                    setScanResults();
                    sendScanResultsAvailableBroadcast();
                    mScanResultIsPending = false;
                    break;

这里主要做了两件事,一是去获取scanResults,另外会发送一个广播信息出去,如果有检测这个广播的receive收到这个广播后,就可以调用函数去获取到scanResults并显示到listview上面,例如WifiSettings。进入到setScanResults里面来分析:

    private void setScanResults() {

        while (true) {
            tmpResults = mWifiNative.scanResults(sid);
            if (TextUtils.isEmpty(tmpResults)) break;
            scanResultsBuf.append(tmpResults);
            scanResultsBuf.append("\n");
            String[] lines = tmpResults.split("\n");
            sid = -1;
            for (int i=lines.length - 1; i >= 0; i--) {
                if (lines[i].startsWith(END_STR)) {
                    break;
                } else if (lines[i].startsWith(ID_STR)) {
                    try {
                        sid = Integer.parseInt(lines[i].substring(ID_STR.length())) + 1;
                    } catch (NumberFormatException e) {
                        // Nothing to do
                    }
                    break;
                }
            }
            if (sid == -1) break;
        }

        scanResults = scanResultsBuf.toString();
        if (TextUtils.isEmpty(scanResults)) {
           return;
        }

        synchronized(mScanResultCache) {

            for (String line : lines) {
                if (line.startsWith(BSSID_STR)) {
                    bssid = new String(line.getBytes(), bssidStrLen, line.length() - bssidStrLen);
                } else if (line.startsWith(FREQ_STR)) {
                    try {
                        freq = Integer.parseInt(line.substring(FREQ_STR.length()));
                    } catch (NumberFormatException e) {
                        freq = 0;
                    }
                }
                else if (line.startsWith(SSID_STR)) {
                    wifiSsid = WifiSsid.createFromAsciiEncoded(
                            line.substring(SSID_STR.length()));
                } else if (line.startsWith(DELIMITER_STR) || line.startsWith(END_STR)) {
                    if (bssid != null) {
                        String ssid = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
                        String key = bssid + ssid;
                        ScanResult scanResult = mScanResultCache.get(key);
                        if (scanResult != null) {
                            scanResult.level = level;
                            scanResult.wifiSsid = wifiSsid;
                            // Keep existing API
                            scanResult.SSID = (wifiSsid != null) ? wifiSsid.toString() :
                                    WifiSsid.NONE;
                            scanResult.capabilities = flags;
                            scanResult.frequency = freq;
                            scanResult.timestamp = tsf;
                        } else {
                            scanResult =
                                new ScanResult(
                                        wifiSsid, bssid, flags, level, freq, tsf);
                            mScanResultCache.put(key, scanResult);
                        }
                        mScanResults.add(scanResult);
                    }


这个函数看起来比较复杂,其实仔细分析,它只是循环的parse从WifiNative获取到AP列表信息,WifiNative.scanResut的返回结果如下,每个AP之间用"===="分割,末尾以“####”来表示结束。

     id=1
     bssid=68:7f:76:d7:1a:6e
     freq=2412
     level=-44
     tsf=1344626243700342
     flags=[WPA2-PSK-CCMP][WPS][ESS]
     ssid=zfdy
     ====
    id=2
    bssid=68:5f:74:d7:1a:6f
    req=5180
    level=-73
    tsf=1344626243700373
    flags=[WPA2-PSK-CCMP][WPS][ESS]
    ssid=zuby
    ####
当所有的结果都被parse出来后,会被存到mScanResults这个ArrayList当中,另外会用bssid+ssid做key值,将这个scanResult存到mScanResultCache这个LRU(最近最少使用) cache当中。当然随着wifi driver不断的scan,发现新的AP,mScanResults和mScanResultCache中的数据也在不断的变化。

当应用程序收到sendScanResultsAvailableBroadcast发送的WifiManager.SCAN_RESULTS_AVAILABLE_ACTION这个broadcast后,就可以去获取上面提供的mScanResults信息了,获取过程很简单,直接复制mScanResults这个ArrayList里面的成员,然后返回。,值得注意的是,sendScanResultsAvailableBroadcast设置了Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT这个属性,所以只有动态注册的broadcastReceive才会收到这个broadcast。

连接AP的流程

当用户点击AP列表中一项并输入正确的密码后,就可以开始AP的连接过程了,主要调用的是WifiManager的connect函数,如下:

    public void connect(WifiConfiguration config, ActionListener listener) {
        if (config == null) throw new IllegalArgumentException("config cannot be null");
        validateChannel();
        // Use INVALID_NETWORK_ID for arg1 when passing a config object
        // arg1 is used to pass network id when the network already exists
        sAsyncChannel.sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
                putListener(listener), config);
    }

其中第一个参数WifiConfiguration是当前需要连接的AP的配置信息,包括SSID、BSSID、密码以及加密方式等信息;ActionListern作为callback来通知客户程序connect方法是否调用成功,这里的调用成功只是指参数是否正确,并不表示AP是否连接成功。由前面介绍的AsyncChannel的知识,到WifiService中去看看如果处理CONNECT_NETWORK这个消息:

                case WifiManager.CONNECT_NETWORK:
                case WifiManager.SAVE_NETWORK: {
                    WifiConfiguration config = (WifiConfiguration) msg.obj;
                    int networkId = msg.arg1;
                    if (config != null && config.isValid()) {
                        // This is restricted because there is no UI for the user to
                        // monitor/control PAC.
                        if (config.proxySettings != ProxySettings.PAC) {
                            if (DBG) Slog.d(TAG, "Connect with config" + config);
                            mWifiStateMachine.sendMessage(Message.obtain(msg));
                        } reak;
                }

WifiService将这个消息传递给WifiStateMachine处理,由前面介绍的Scan的知识,这时候WifiStateMachine的ConnectModeState将处理CONNECT_NETWORK这个消息,代码如下:
                case WifiManager.CONNECT_NETWORK:
                   
                    int netId = message.arg1;
                    config = (WifiConfiguration) message.obj;

                    /* Save the network config */
                    if (config != null) {
                        NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
                        netId = result.getNetworkId();
                    }

                    if (mWifiConfigStore.selectNetwork(netId) &&
                            mWifiNative.reconnect()) {
                        /* The state tracker handles enabling networks upon completion/failure */
                        mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
                        replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
                        /* Expect a disconnection from the old connection */
                        transitionTo(mDisconnectingState);
                    }
                    break;

这里主要调用了下面几个函数来进行AP的连接:WifiConfigStore.saveNetwok(config)将AP的配置信息写入到wpa_supplicant.conf中;WifiConfigStore.selectNetwork(netId)用于enable即将要连接的AP,而disable掉其它的AP;WifiNative.reconnect()发起重新连接的请求给wpa_supplicant。接着transition到DisconnectingState中,来看看DisconnectingState,这个状态code比较少:

    class DisconnectingState extends State {
        @Override
        public boolean processMessage(Message message) {
            switch (message.what) {
                case CMD_SET_OPERATIONAL_MODE:
                    if (message.arg1 != CONNECT_MODE) {
                        deferMessage(message);
                    }
                    break;
                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                    /* If we get a SUPPLICANT_STATE_CHANGE_EVENT before NETWORK_DISCONNECTION_EVENT
                     * we have missed the network disconnection, transition to mDisconnectedState
                     * and handle the rest of the events there
                     */
                    deferMessage(message);
                    handleNetworkDisconnect();
                    transitionTo(mDisconnectedState);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

当执行完WifiNative.reconnect(),wpa_supplicant会不断的往WifiMonitor发送包括CTRL-EVENT-STATE-CHANGE、ASSOCIATING、ASSOCIATED、FOUR_WAY_HANDSHARK、GROUP_HANDSHARK等event,WifiMonitor会不断的去parse这些event并向WifiStatemachine发送消息,其中一个比较重要的消息就是当wpa_supplicant的状态改变是会发送WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT,上面的DisconnectiongState 收到这个消息后,会transition到DisconnectedState。

当Wifi和AP之间已经连接成功后,就会收到wpa_supplicant发送上来的CTRL-EVENT-CONNECTED这个event,WifiMonitor收到这个消息后,会向WifiStateMachine发送NETWORK_CONNECTION_EVENT表示已经和AP之间成功的连线,WifiStateMachine的ConnectModeState会来处理这个消息,代码如下:

                case WifiMonitor.NETWORK_CONNECTION_EVENT:
                    if (DBG) log("Network connection established");
                    mLastNetworkId = message.arg1;
                    mLastBssid = (String) message.obj;

                    mWifiInfo.setBSSID(mLastBssid);
                    mWifiInfo.setNetworkId(mLastNetworkId);
                    /* send event to CM & network change broadcast */
                    setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);
                    sendNetworkStateChangeBroadcast(mLastBssid);
                    transitionTo(mObtainingIpState);
                    break;

WifiStateMachine处理完这个消息后,会跳转到ObtainingIpState,进到到ObtainingIpState的enter函数看看:

    class ObtainingIpState extends State {
        @Override
        public void enter() {
            if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
                // TODO: If we're switching between static IP configuration and DHCP, remove the
                // static configuration first.
                startDhcp();
            } else {
                // stop any running dhcp before assigning static IP
                stopDhcp();
                DhcpResults dhcpResults = new DhcpResults(
                        mWifiConfigStore.getLinkProperties(mLastNetworkId));
                InterfaceConfiguration ifcg = new InterfaceConfiguration();
                Iterator addrs =
                        dhcpResults.linkProperties.getLinkAddresses().iterator();
                if (!addrs.hasNext()) {
                    loge("Static IP lacks address");
                    sendMessage(CMD_STATIC_IP_FAILURE);
                } else {
                    ifcg.setLinkAddress(addrs.next());
                    ifcg.setInterfaceUp();
                    try {
                        mNwService.setInterfaceConfig(mInterfaceName, ifcg);
                        if (DBG) log("Static IP configuration succeeded");
                        sendMessage(CMD_STATIC_IP_SUCCESS, dhcpResults);
                    } catch (RemoteException re) {
                        loge("Static IP configuration failed: " + re);
                        sendMessage(CMD_STATIC_IP_FAILURE);
                    } catch (IllegalStateException e) {
                        loge("Static IP configuration failed: " + e);
                        sendMessage(CMD_STATIC_IP_FAILURE);
                    }
                }
            }
        }

ObtainingIpState就是获取IP的状态,这里分为两种获取IP的方式,一种是用户静态配置的,另一种是通过DHCP动态分配。这里只看动态分配的,进到到startDhcp去分析:

    void startDhcp() {
        if (mDhcpStateMachine == null) {
            mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
                    mContext, WifiStateMachine.this, mInterfaceName);

        }
        mDhcpStateMachine.registerForPreDhcpNotification();
        mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
    }

首先去创建DhcpStateMachine的实例,然后向它发送一个CMD_START_DHCP的命令,DhcpStateMachine初始化完毕后,收到这个消息就会马上向WifiStateMachine发送CMD_PRE_DHCP_ACTION表示DhcpStateMachine马上就要开始发送discovery或者renew的封包了,来看WifiStateMachine收到这个消息的处理:

        public boolean processMessage(Message message) {
            switch (message.what) {
              case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
                  handlePreDhcpSetup();
                  break;
              case DhcpStateMachine.CMD_POST_DHCP_ACTION:
                  handlePostDhcpSetup();
                  if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
                      if (DBG) log("DHCP successful");
                      handleSuccessfulIpConfiguration((DhcpResults) message.obj);
                      transitionTo(mVerifyingLinkState);
                  } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {
                      if (DBG) log("DHCP failed");
                      handleFailedIpConfiguration();
                      transitionTo(mDisconnectingState);
                  }
                  break;

在处理完CMD_PRE_DHCP_ACTION后,WifiStateMachine会向DhcpStateMachine发送CMD_PRE_DHCP_ACTION_COMPLETE,用于指示前期准备工作已经做好了,这是就可以开始dhcp的discovery/reponse了,用于两端来获取IP,当IP成功获取后,DhcpStateMachine会给WifiStateMachine发送CMD_POST_DHCP_ACTION消息,其中arg1表示是成功还是失败,如果成功,就会调用handleSuccessfulIpConfiguration来处理,并transition 到VerifyingLinkState中;如果失败则会transition到DisconectingState中。这里只看成功获取IP的情况,进入到VerifyingLinkState中:

    class VerifyingLinkState extends State {
        @Override
        public void enter() {
            log(getName() + " enter");
            setNetworkDetailedState(DetailedState.VERIFYING_POOR_LINK);
            mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.VERIFYING_POOR_LINK);
            sendNetworkStateChangeBroadcast(mLastBssid);
        }
        @Override
        public boolean processMessage(Message message) {
            switch (message.what) {
                case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
                    //stay here
                    log(getName() + " POOR_LINK_DETECTED: no transition");
                    break;
                case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
                    log(getName() + " GOOD_LINK_DETECTED: transition to captive portal check");
                    transitionTo(mCaptivePortalCheckState);
                    break;
                default:
                    if (DBG) log(getName() + " what=" + message.what + " NOT_HANDLED");
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

在VerifyingLinkState主要是来验证当前连接状况的,主要方式是通过统计信号强度以及丢包率,这些工作是交给WifiWatchdogStateMachine来做的,当WifiAP的信号强度增强或者变弱,会发送两种消息给WifiStateMachine,一种是WifiWatchdogStateMachine.GOOD_LINK_DETECTED,另一种是WifiWatchdogStateMachine.POOR_LINK_DETECTED。当收到GOOD_LINK_DETECTED消息后,就会跳转到CaptivePortalCheckState中;当收到的是POOR_LINK_DETECTED,则维持原来的状态不变。我们跳转到CaptivePortalCheckState去分析:
    class CaptivePortalCheckState extends State {
        @Override
        public void enter() {
            log(getName() + " enter");
            setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);
            mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CAPTIVE_PORTAL_CHECK);
            sendNetworkStateChangeBroadcast(mLastBssid);
        }
        @Override
        public boolean processMessage(Message message) {
            switch (message.what) {
                case CMD_CAPTIVE_CHECK_COMPLETE:
                    log(getName() + " CMD_CAPTIVE_CHECK_COMPLETE");
                    try {
                        mNwService.enableIpv6(mInterfaceName);
                    } catch (RemoteException re) {
                        loge("Failed to enable IPv6: " + re);
                    } catch (IllegalStateException e) {
                        loge("Failed to enable IPv6: " + e);
                    }
                    setNetworkDetailedState(DetailedState.CONNECTED);
                    mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED);
                    sendNetworkStateChangeBroadcast(mLastBssid);
                    transitionTo(mConnectedState);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

首先会发送CAPTIVE_PORTAL_CHECK的broadcast,这个会被WifiStateTracker接收并处理,然后调用ConnectivityService的接口去处理captive portal相关的内容,与captive portal相关的只是可以参考:http://en.wikipedia.org/wiki/Captive_portal
当ConnectivityService完成captive portal check后,就会给WifiStateMachine发送CMD_CAPTIVE_CHECK_COMPLETE消息,就会跳转到ConnectedState表示连接过程的结束了。

你可能感兴趣的:(Android)