Android -- Wifi连接流程分析

Android -- Wifi连接流程分析


当我们在Android手机上通过Settings连接一个AP时,间接调用WifiManager的connect()方法:
/**
     * Connect to a network with the given configuration. The network also
     * gets added to the supplicant configuration.
     *
     * For a new network, this function is used instead of a
     * sequence of addNetwork(), enableNetwork(), saveConfiguration() and
     * reconnect()
     *
     * @param config the set of variables that describe the configuration,
     *            contained in a {@link WifiConfiguration} object.
     * @param listener for callbacks on success or failure. Can be null.
     * @throws IllegalStateException if the WifiManager instance needs to be
     * initialized again
     *
     * @hide
     */
    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);
    }

    /**
     * Connect to a network with the given networkId.
     *
     * This function is used instead of a enableNetwork(), saveConfiguration() and
     * reconnect()
     *
     * @param networkId the network id identifiying the network in the
     *                supplicant configuration list
     * @param listener for callbacks on success or failure. Can be null.
     * @throws IllegalStateException if the WifiManager instance needs to be
     * initialized again
     * @hide
     */
    public void connect(int networkId, ActionListener listener) {
        if (networkId < 0) throw new IllegalArgumentException("Network id cannot be negative");
        validateChannel();
        sAsyncChannel.sendMessage(CONNECT_NETWORK, networkId, putListener(listener));
    }
connect()方法有两种形式,一种接受WifiConfiguration对象,一种接受某个AP的networkID。WifiConfiguration描述了一个Wifi连接的所有配置信息。

WifiManager的ServiceHandler和WifiService的ClientHandler通过异步通道进行通信。所以这里通过AsyncChannel机制,向WifiServiceImpl发送CONNECT_NETWORK消息,可知在WifiServiceImpl::ClientHandler中被处理:
/* Client commands are forwarded to state machine */
                case WifiManager.CONNECT_NETWORK:
                case WifiManager.SAVE_NETWORK: {
                    WifiConfiguration config = (WifiConfiguration) msg.obj;
                    int networkId = msg.arg1;
                    if (msg.what == WifiManager.SAVE_NETWORK) {
                        Slog.e("WiFiServiceImpl ", "SAVE"
                                + " nid=" + Integer.toString(networkId)
                                + " uid=" + msg.sendingUid
                                + " name="
                                + mContext.getPackageManager().getNameForUid(msg.sendingUid));
                    }
                    if (msg.what == WifiManager.CONNECT_NETWORK) {
                        Slog.e("WiFiServiceImpl ", "CONNECT "
                                + " nid=" + Integer.toString(networkId)
                                + " uid=" + msg.sendingUid
                                + " name="
                                + mContext.getPackageManager().getNameForUid(msg.sendingUid));
                    }

                    if (config != null && isValid(config)) {
                        if (DBG) Slog.d(TAG, "Connect with config" + config);
                        mWifiStateMachine.sendMessage(Message.obtain(msg));
                    } else if (config == null
                            && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
                        if (DBG) Slog.d(TAG, "Connect with networkId" + networkId);
                        mWifiStateMachine.sendMessage(Message.obtain(msg));
                    } else {
                        Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
                        if (msg.what == WifiManager.CONNECT_NETWORK) {
                            replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED,
                                    WifiManager.INVALID_ARGS);
                        } else {
                            replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED,
                                    WifiManager.INVALID_ARGS);
                        }
                    }
                    break;
                }
ClientHandler中并不做具体的连接动作,主要将CONNECT_NETWORK消息被转发到WifiStateMachine中,通过Wifi状态机来驱动连接和DHCP过程 。ConnectModeState处理:
                case WifiManager.CONNECT_NETWORK:
                    /**
                     *  The connect message can contain a network id passed as arg1 on message or
                     * or a config passed as obj on message.
                     * For a new network, a config is passed to create and connect.
                     * For an existing network, a network id is passed
                     */
                    netId = message.arg1;
                    config = (WifiConfiguration) message.obj;
                    mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
                    boolean updatedExisting = false;

                    /* Save the network config */
                    if (config != null) {
                        // When connecting to an access point, WifiStateMachine wants to update the
                        // relevant config with administrative data. This update should not be
                        // considered a 'real' update, therefore lockdown by Device Owner must be
                        // disregarded.
                        if (!recordUidIfAuthorized(config, message.sendingUid,
                                /* onlyAnnotate */ true)) {
                            logw("Not authorized to update network "
                                 + " config=" + config.SSID
                                 + " cnid=" + config.networkId
                                 + " uid=" + message.sendingUid);
                            replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                                           WifiManager.NOT_AUTHORIZED);
                            break;
                        }

                        String configKey = config.configKey(true /* allowCached */);
                        WifiConfiguration savedConfig =
                                mWifiConfigStore.getWifiConfiguration(configKey);
                        if (savedConfig != null) {
                            // There is an existing config with this netId, but it wasn't exposed
                            // (either AUTO_JOIN_DELETED or ephemeral; see WifiConfigStore#
                            // getConfiguredNetworks). Remove those bits and update the config.
                            config = savedConfig;
                            logd("CONNECT_NETWORK updating existing config with id=" +
                                    config.networkId + " configKey=" + configKey);
                            config.ephemeral = false;
                            config.autoJoinStatus = WifiConfiguration.AUTO_JOIN_ENABLED;
                            updatedExisting = true;
                        }

                        result = mWifiConfigStore.saveNetwork(config, message.sendingUid);
                        netId = result.getNetworkId();
                    }
                    config = mWifiConfigStore.getWifiConfiguration(netId);

                    if (config == null) {
                        logd("CONNECT_NETWORK no config for id=" + Integer.toString(netId) + " "
                                + mSupplicantStateTracker.getSupplicantStateName() + " my state "
                                + getCurrentState().getName());
                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                                WifiManager.ERROR);
                        break;
                    } else {
                        String wasSkipped = config.autoJoinBailedDueToLowRssi ? " skipped" : "";
                        logd("CONNECT_NETWORK id=" + Integer.toString(netId)
                                + " config=" + config.SSID
                                + " cnid=" + config.networkId
                                + " supstate=" + mSupplicantStateTracker.getSupplicantStateName()
                                + " my state " + getCurrentState().getName()
                                + " uid = " + message.sendingUid
                                + wasSkipped);
                    }

                    autoRoamSetBSSID(netId, "any");

                    if (message.sendingUid == Process.WIFI_UID
                        || message.sendingUid == Process.SYSTEM_UID) {
                        // As a sanity measure, clear the BSSID in the supplicant network block.
                        // If system or Wifi Settings want to connect, they will not
                        // specify the BSSID.
                        // If an app however had added a BSSID to this configuration, and the BSSID
                        // was wrong, Then we would forever fail to connect until that BSSID
                        // is cleaned up.
                        clearConfigBSSID(config, "CONNECT_NETWORK");
                    }

                    if (deferForUserInput(message, netId, true)) {
                        break;
                    } else if (mWifiConfigStore.getWifiConfiguration(netId).userApproved ==
                                                                    WifiConfiguration.USER_BANNED) {
                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                                WifiManager.NOT_AUTHORIZED);
                        break;
                    }

                    mAutoRoaming = WifiAutoJoinController.AUTO_JOIN_IDLE;

                    /* Tell autojoin the user did try to connect to that network if from settings */
                    boolean persist =
                        mWifiConfigStore.checkConfigOverridePermission(message.sendingUid);
                    mWifiAutoJoinController.updateConfigurationHistory(netId, true, persist);

                    mWifiConfigStore.setLastSelectedConfiguration(netId);

                    didDisconnect = false;
                    if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID
                            && mLastNetworkId != netId) {
                        /** Supplicant will ignore the reconnect if we are currently associated,
                         * hence trigger a disconnect
                         */
                        didDisconnect = true;
                        mWifiNative.disconnect();
                    }

                    // Make sure the network is enabled, since supplicant will not reenable it
                    mWifiConfigStore.enableNetworkWithoutBroadcast(netId, false);

                    if (mWifiConfigStore.selectNetwork(config, /* updatePriorities = */ true,
                            message.sendingUid) && mWifiNative.reconnect()) {
                        lastConnectAttemptTimestamp = System.currentTimeMillis();
                        targetWificonfiguration = mWifiConfigStore.getWifiConfiguration(netId);

                        /* The state tracker handles enabling networks upon completion/failure */
                        mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
                        replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
                        if (didDisconnect) {
                            /* Expect a disconnection from the old connection */
                            transitionTo(mDisconnectingState);
                        } else if (updatedExisting && getCurrentState() == mConnectedState &&
                                getCurrentWifiConfiguration().networkId == netId) {
                            // Update the current set of network capabilities, but stay in the
                            // current state.
                            updateCapabilities(config);
                        } else {
                            /**
                             *  Directly go to disconnected state where we
                             * process the connection events from supplicant
                             **/
                            transitionTo(mDisconnectedState);
                        }
                    } else {
                        loge("Failed to connect config: " + config + " netId: " + netId);
                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                                WifiManager.ERROR);
                        break;
                    }
                    break;
通过阅读代码,可知主要的处理动作如下:

  1. 将connect()传过来的AP信息保存到WifiConfigStore对象中
  2. 通过WifiConfigStore::selectNetwork()函数更新WifiConfigStore和config的Priority优先级属性,最后更新到wpa_s配置文件中;enable当前要连接的AP,disable其他的AP
  3. 通过WifiNative::reconnect()函数向wpa_s发送连接指令,连接选定的AP

连接选定的AP是通过调用WifiNative方法向wpa_supplicant发送connect指令,wpa_s通知驱动进行连接;当底层无线连接成功后,framework就能通过WifiMonitor接受到wpa_s上报的event消息:
/**
     * Handle all supplicant events except STATE-CHANGE
     * @param event the event type
     * @param remainder the rest of the string following the
     * event name and " — "
     */
    void handleEvent(int event, String remainder) {
        if (DBG) {
            logDbg("handleEvent " + Integer.toString(event) + "  " + remainder);
        }
        switch (event) {
            case DISCONNECTED:
                handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder);
                break;

            case CONNECTED:
                handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder);
                break;

            case SCAN_RESULTS:
                mStateMachine.sendMessage(SCAN_RESULTS_EVENT);
                break;

            case SCAN_FAILED:
                mStateMachine.sendMessage(SCAN_FAILED_EVENT);
                break;

            case UNKNOWN:
                if (DBG) {
                    logDbg("handleEvent unknown: " + Integer.toString(event) + "  " + remainder);
                }
                break;
            default:
                break;
        }
    }
   private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) {
        String BSSID = null;
        int networkId = -1;
        int reason = 0;
        int ind = -1;
        int local = 0;
        Matcher match;
        if (newState == NetworkInfo.DetailedState.CONNECTED) {
            match = mConnectedEventPattern.matcher(data);
            if (!match.find()) {
               if (DBG) Log.d(TAG, "handleNetworkStateChange: Couldnt find BSSID in event string");
            } else {
                BSSID = match.group(1);
                try {
                    networkId = Integer.parseInt(match.group(2));
                } catch (NumberFormatException e) {
                    networkId = -1;
                }
            }
            notifyNetworkStateChange(newState, BSSID, networkId, reason);
        } else if (newState == NetworkInfo.DetailedState.DISCONNECTED) {
            match = mDisconnectedEventPattern.matcher(data);
            if (!match.find()) {
               if (DBG) Log.d(TAG, "handleNetworkStateChange: Could not parse disconnect string");
            } else {
                BSSID = match.group(1);
                try {
                    reason = Integer.parseInt(match.group(2));
                } catch (NumberFormatException e) {
                    reason = -1;
                }
                try {
                    local = Integer.parseInt(match.group(3));
                } catch (NumberFormatException e) {
                    local = -1;
                }
            }
            notifyNetworkStateChange(newState, BSSID, local, reason);
        }
    }

    /**
     * Send the state machine a notification that the state of Wifi connectivity
     * has changed.
     * @param newState the new network state
     * @param BSSID when the new state is {@link NetworkInfo.DetailedState#CONNECTED},
     * this is the MAC address of the access point. Otherwise, it
     * is {@code null}.
     * @param netId the configured network on which the state change occurred
     */
    void notifyNetworkStateChange(NetworkInfo.DetailedState newState,
                                  String BSSID, int netId, int reason) {
        if (newState == NetworkInfo.DetailedState.CONNECTED) {
            Message m = mStateMachine.obtainMessage(NETWORK_CONNECTION_EVENT,
                    netId, reason, BSSID);
            mStateMachine.sendMessage(m);
        } else {

            Message m = mStateMachine.obtainMessage(NETWORK_DISCONNECTION_EVENT,
                    netId, reason, BSSID);
            if (DBG) logDbg("WifiMonitor notify network disconnect: "
                    + BSSID
                    + " reason=" + Integer.toString(reason));
            mStateMachine.sendMessage(m);
        }
    }
WifiMonitor与wpa_s之间的通信是通过socket建立的,如前几篇博客所介绍的那样:WifiMonitor通过建立socket连接与wpa_s通信;每当wpa_s有事件要上报时,WiFiMonitor会解析该event,并转发到Wifi状态机中。

WifiMonitor接收到wpa_s的连接事件时,向WifiStateMachine发送NETWORK_CONNECTION_EVENT消息,通知状态机底层无线已经连接成功,下一步可以获取IP了。

转到WifiStateMachine中,ConnectModeState进行处理:
case WifiMonitor.NETWORK_CONNECTION_EVENT:
                    if (DBG) log("Network connection established");
                    mLastNetworkId = message.arg1; //成功加入到某无线网络中的AP的NetworkId
                    mLastBssid = (String) message.obj;

                    mWifiInfo.setBSSID(mLastBssid);
                    mWifiInfo.setNetworkId(mLastNetworkId);

                    sendNetworkStateChangeBroadcast(mLastBssid);
                    transitionTo(mObtainingIpState);
                    break;
这里会将这次连接的AP的networkID保存下来,并进入到ObtainingIpState状态去真正触发DHCP动作。L2ConnectedState是ObtainingIpState的父状态,看它的enter()函数:
public void enter() {
            mRssiPollToken++;
            if (mEnableRssiPolling) {
                sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0);
            }
            if (mNetworkAgent != null) {
                loge("Have NetworkAgent when entering L2Connected");
                setNetworkDetailedState(DetailedState.DISCONNECTED);
            }
            setNetworkDetailedState(DetailedState.CONNECTING);//更新当前的网络连接状态

            if (!TextUtils.isEmpty(mTcpBufferSizes)) {
                mLinkProperties.setTcpBufferSizes(mTcpBufferSizes);
            }
            mNetworkAgent = new WifiNetworkAgent(getHandler().getLooper(), mContext,
                    "WifiNetworkAgent", mNetworkInfo, mNetworkCapabilitiesFilter,
                    mLinkProperties, 60);//此处创建一个NetworkAgent对象用于向ConnectivityService通知相应的网络配置更新操作

            // We must clear the config BSSID, as the wifi chipset may decide to roam
            // from this point on and having the BSSID specified in the network block would
            // cause the roam to faile and the device to disconnect
            clearCurrentConfigBSSID("L2ConnectedState");

            try {
                mIpReachabilityMonitor = new IpReachabilityMonitor(
                        mInterfaceName,
                        new IpReachabilityMonitor.Callback() {
                            @Override
                            public void notifyLost(InetAddress ip, String logMsg) {
                                sendMessage(CMD_IP_REACHABILITY_LOST, logMsg);
                            }
                        });
            } catch (IllegalArgumentException e) {
                Log.wtf("Failed to create IpReachabilityMonitor", e);
            }
        }
我们再看ObtainingIpState::enter()方法看如何获取获取IP地址:
        @Override
        public void enter() {
            if (DBG) {
                String key = "";
                if (getCurrentWifiConfiguration() != null) {
                    key = getCurrentWifiConfiguration().configKey();
                }
                log("enter ObtainingIpState netId=" + Integer.toString(mLastNetworkId)
                        + " " + key + " "
                        + " roam=" + mAutoRoaming
                        + " static=" + mWifiConfigStore.isUsingStaticIp(mLastNetworkId)
                        + " watchdog= " + obtainingIpWatchdogCount);
            }

            // Reset link Debouncing, indicating we have successfully re-connected to the AP
            // We might still be roaming
            linkDebouncing = false;

            // Send event to CM & network change broadcast
            setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);

            // We must clear the config BSSID, as the wifi chipset may decide to roam
            // from this point on and having the BSSID specified in the network block would
            // cause the roam to faile and the device to disconnect
            clearCurrentConfigBSSID("ObtainingIpAddress");

            try {
                mNwService.enableIpv6(mInterfaceName);
            } catch (RemoteException re) {
                loge("Failed to enable IPv6: " + re);
            } catch (IllegalStateException e) {
                loge("Failed to enable IPv6: " + e);
            }

            if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
                if (isRoaming()) {
                    renewDhcp();
                } else {
                    // Remove any IP address on the interface in case we're switching from static
                    // IP configuration to DHCP. This is safe because if we get here when not
                    // roaming, we don't have a usable address.
                    clearIPv4Address(mInterfaceName);
                    startDhcp();// DHCP过程启动
                }
                obtainingIpWatchdogCount++;
                logd("Start Dhcp Watchdog " + obtainingIpWatchdogCount);
                // Get Link layer stats so as we get fresh tx packet counters
                getWifiLinkLayerStats(true);
                sendMessageDelayed(obtainMessage(CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER,
                        obtainingIpWatchdogCount, 0), OBTAINING_IP_ADDRESS_GUARD_TIMER_MSEC);
            } else {
                // stop any running dhcp before assigning static IP
                stopDhcp();
                StaticIpConfiguration config = mWifiConfigStore.getStaticIpConfiguration(
                        mLastNetworkId);
                if (config.ipAddress == null) {
                    logd("Static IP lacks address");
                    sendMessage(CMD_STATIC_IP_FAILURE);
                } else {
                    InterfaceConfiguration ifcg = new InterfaceConfiguration();
                    ifcg.setLinkAddress(config.ipAddress);
                    ifcg.setInterfaceUp();
                    try {
                        mNwService.setInterfaceConfig(mInterfaceName, ifcg);
                        if (DBG) log("Static IP configuration succeeded");
                        DhcpResults dhcpResults = new DhcpResults(config);
                        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);
                    }
                }
            }
        }
这里Wifi分了两种连接方式,Static IP和DHCP。这里区分静态和DHCP是通过WifiConfiguration对象来处理的。WifiConfiguration代表一个配置过的AP连接,主要包含IpAssignment(标识上层的连接方式是静态还是DHCP)、AP的networkID、AP的名字等等。

我们主要看动态获取IP的过程。进入DHCP流程之前,会先清除当前的IP地址信息。着重看startDhcp()函数处理:
    void startDhcp() {
        maybeInitDhcpStateMachine(); //确保初始化WifiStateMachine持有的mDhcpStateMachine对象,会传入当前的WifiStateMachine对象,用来回发消息
        mDhcpStateMachine.registerForPreDhcpNotification();//注册mRegisteredForPreDhcpNotification字段为true,表明我们在发送DHCP包之前需要做一些准备工作
        mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);//发送开始DHCP的消息
    }
DhcpStateMachine是一个小状态机,它主要处理DHCP下的IP地址获取过程,并将获取到的DHCP结果告知WifiStateMachine。看DhcpStateMachine处理该消息的过程:
case CMD_START_DHCP:
                    if (mRegisteredForPreDhcpNotification) {
                        /* Notify controller before starting DHCP */
                        mController.sendMessage(CMD_PRE_DHCP_ACTION);
                        transitionTo(mWaitBeforeStartState);
                    } else {
                        if (runDhcpStart()) {
                            transitionTo(mRunningState);
                        }
                    }
                    break;
由于我们之前设置了mRegisteredForPreDhcpNotification为true,这里会向WifiStateMachine发送CMD_PRE_DHCP_ACTION消息,告知Wifi状态机做一些DHCP之前的预处理工作。L2ConnectedState处理该消息:
case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
                  handlePreDhcpSetup();
                  break;
    void handlePreDhcpSetup() {
        mDhcpActive = true;
        if (!mBluetoothConnectionActive) {
            /*
             * There are problems setting the Wi-Fi driver's power
             * mode to active when bluetooth coexistence mode is
             * enabled or sense.
             * <p>
             * We set Wi-Fi to active mode when
             * obtaining an IP address because we've found
             * compatibility issues with some routers with low power
             * mode.
             * <p>
             * In order for this active power mode to properly be set,
             * we disable coexistence mode until we're done with
             * obtaining an IP address.  One exception is if we
             * are currently connected to a headset, since disabling
             * coexistence would interrupt that connection.
             */
            // Disable the coexistence mode
            mWifiNative.setBluetoothCoexistenceMode(
                    mWifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
        }

        // Disable power save and suspend optimizations during DHCP
        // Note: The order here is important for now. Brcm driver changes
        // power settings when we control suspend mode optimizations.
        // TODO: Remove this comment when the driver is fixed.
        setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, false);
        mWifiNative.setPowerSave(false);

        // Update link layer stats
        getWifiLinkLayerStats(false);

        /* P2p discovery breaks dhcp, shut it down in order to get through this */
        Message msg = new Message();
        msg.what = WifiP2pServiceImpl.BLOCK_DISCOVERY;
        msg.arg1 = WifiP2pServiceImpl.ENABLED;
        msg.arg2 = DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE;
        msg.obj = mDhcpStateMachine;
        mWifiP2pChannel.sendMessage(msg);
    }
从注释可知,为了保证Wifi DHCP过程的顺利进行,对Bluetooth、Power和P2p都做了处理工作,其中:蓝牙会Disable the coexistence mode;停止p2p的discovery过程,防止影响DHCP流程。MD_PRE_DHCP_ACTION_COMPLETE消息会在WifiP2pServiceImpl设置完p2p部分后,被转发到DhcpStateMachine,告知预处理工作已经结束,可以进行DHCP了。看DhcpStateMachine中的消息处理:
case CMD_PRE_DHCP_ACTION_COMPLETE:
                    if (runDhcpStart()) {
                        transitionTo(mRunningState);
                    } else {
                        transitionTo(mPollingState);
                    }
                    break;
    private boolean runDhcpStart() {
        /* Stop any existing DHCP daemon before starting new */
        NetworkUtils.stopDhcp(mInterfaceName);//在启动新的DHCP流程之前,停止当前正在进行的DHCP过程
        mDhcpResults = null;

        if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName);
        if (!NetworkUtils.startDhcp(mInterfaceName) || !dhcpSucceeded()) {
            Log.e(TAG, "DHCP request failed on " + mInterfaceName + ": " +
                    NetworkUtils.getDhcpError());
            mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)
                    .sendToTarget();
            return false;
        }
        return true;
    }
调用NetworkUtils.startDhcp()方法启动DHCP流程去获取IP地址,调用dhcpSucceeded()方法获取该次DHCP的DhcpResults对象,它包含了IP地址、网关、DNS等等地址信息。
private boolean dhcpSucceeded() {
        DhcpResults dhcpResults = new DhcpResults();
        if (!NetworkUtils.getDhcpResults(mInterfaceName, dhcpResults)) {
            return false;
        }

        if (DBG) Log.d(TAG, "DHCP results found for " + mInterfaceName);
        long leaseDuration = dhcpResults.leaseDuration; //int to long conversion

        //Sanity check for renewal
        if (leaseDuration >= 0) {
            //TODO: would be good to notify the user that his network configuration is
            //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS
            if (leaseDuration < MIN_RENEWAL_TIME_SECS) {
                leaseDuration = MIN_RENEWAL_TIME_SECS;
            }
            //Do it a bit earlier than half the lease duration time
            //to beat the native DHCP client and avoid extra packets
            //48% for one hour lease time = 29 minutes
            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    SystemClock.elapsedRealtime() +
                    leaseDuration * 480, //in milliseconds
                    mDhcpRenewalIntent);
        } else {
            //infinite lease time, no renewal needed
        }

        // Fill in any missing fields in dhcpResults from the previous results.
        // If mDhcpResults is null (i.e. this is the first server response),
        // this is a noop.
        dhcpResults.updateFromDhcpRequest(mDhcpResults);
        mDhcpResults = dhcpResults;
        mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpResults)
            .sendToTarget();
        return true;
    }
如果getDhcpResults()函数执行成功,DhcpStateMachine就会发送CMD_POST_DHCP_ACTION消息给WifiStateMachine,状态位是DHCP_SUCCESS,并附加runDhcp()获取到的DhcpResult对象;失败时则附加DHCP_FAILURE。L2ConnectedState处理CMD_POST_DHCP_ACTION消息:
case DhcpStateMachine.CMD_POST_DHCP_ACTION:
                  handlePostDhcpSetup();
                  if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
                      if (DBG) log("DHCP successful");
                      handleIPv4Success((DhcpResults) message.obj, DhcpStateMachine.DHCP_SUCCESS);
                      // We advance to mConnectedState because handleIPv4Success will call
                      // updateLinkProperties, which then sends CMD_IP_CONFIGURATION_SUCCESSFUL.
                  } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {
                      mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_DHCP_FAILURE);
                      if (DBG) {
                          int count = -1;
                          WifiConfiguration config = getCurrentWifiConfiguration();
                          if (config != null) {
                              count = config.numConnectionFailures;
                          }
                          log("DHCP failure count=" + count);
                      }
                      handleIPv4Failure(DhcpStateMachine.DHCP_FAILURE);
                      // As above, we transition to mDisconnectingState via updateLinkProperties.
                  }
                  break;
首先调用handlePostDhcpSetup()重置之前所做的预处理动作;再处理附加的状态位,成功则调用handleIPv4Success()更新网络的配置信息:
private void handleIPv4Success(DhcpResults dhcpResults, int reason) {

        if (PDBG) {
            logd("handleIPv4Success <" + dhcpResults.toString() + ">");
            logd("link address " + dhcpResults.ipAddress);
        }

        Inet4Address addr;
        synchronized (mDhcpResultsLock) {
            mDhcpResults = dhcpResults;//保存当前的DHCP结果
            addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
        }

        if (isRoaming()) {
            int previousAddress = mWifiInfo.getIpAddress();
            int newAddress = NetworkUtils.inetAddressToInt(addr);
            if (previousAddress != newAddress) {
                logd("handleIPv4Success, roaming and address changed" +
                        mWifiInfo + " got: " + addr);
            }
        }
        mWifiInfo.setInetAddress(addr);
        mWifiInfo.setMeteredHint(dhcpResults.hasMeteredHint());
        updateLinkProperties(reason);//更新网络配置信息
    }
updateLinkProperties()更新当前的网络配置信息:
    private void updateLinkProperties(int reason) {
        LinkProperties newLp = makeLinkProperties();//根据DHCP的结果创建新的LinkProperties对象,用于对比DHCP前后的网络配置是否已经改变

        final boolean linkChanged = !newLp.equals(mLinkProperties);
        final boolean wasProvisioned = isProvisioned(mLinkProperties);
        final boolean isProvisioned = isProvisioned(newLp);
        // TODO: Teach LinkProperties how to understand static assignment
        // and simplify all this provisioning change detection logic by
        // unifying it under LinkProperties.compareProvisioning().
        final boolean lostProvisioning =
                (wasProvisioned && !isProvisioned) ||
                (mLinkProperties.hasIPv4Address() && !newLp.hasIPv4Address()) ||
                (mLinkProperties.isIPv6Provisioned() && !newLp.isIPv6Provisioned());
        final DetailedState detailedState = getNetworkDetailedState();

        if (linkChanged) { //网络配置信息改变
            if (DBG) {
                log("Link configuration changed for netId: " + mLastNetworkId
                        + " old: " + mLinkProperties + " new: " + newLp);
            }
            mLinkProperties = newLp;//将新的配置信息保存到该字段中
            if (mIpReachabilityMonitor != null) {
                mIpReachabilityMonitor.updateLinkProperties(mLinkProperties);
            }
            if (mNetworkAgent != null) mNetworkAgent.sendLinkProperties(mLinkProperties);//通过NetworkAgent对象通知ConnectifyService更新网络配置信息
        }

        if (lostProvisioning) {
            log("Lost IP layer provisioning!" +
                    " was: " + mLinkProperties +
                    " now: " + newLp);
        }

        // If we just configured or lost IP configuration, do the needful.
        // We don't just call handleSuccessfulIpConfiguration() or handleIpConfigurationLost()
        // here because those should only be called if we're attempting to connect or already
        // connected, whereas updateLinkProperties can be called at any time.
        switch (reason) {
            case DhcpStateMachine.DHCP_SUCCESS:
            case CMD_STATIC_IP_SUCCESS:
                // IPv4 provisioning succeded. Advance to connected state.
                sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);
                if (!isProvisioned) {
                    // Can never happen unless DHCP reports success but isProvisioned thinks the
                    // resulting configuration is invalid (e.g., no IPv4 address, or the state in
                    // mLinkProperties is out of sync with reality, or there's a bug in this code).
                    // TODO: disconnect here instead. If our configuration is not usable, there's no
                    // point in staying connected, and if mLinkProperties is out of sync with
                    // reality, that will cause problems in the future.
                    logd("IPv4 config succeeded, but not provisioned");
                }
                break;

            case DhcpStateMachine.DHCP_FAILURE:
                // DHCP failed. If we're not already provisioned, or we had IPv4 and now lost it,
                // give up and disconnect.
                // If we're already provisioned (e.g., IPv6-only network), stay connected.
                if (!isProvisioned || lostProvisioning) {
                    sendMessage(CMD_IP_CONFIGURATION_LOST);
                } else {
                    // DHCP failed, but we're provisioned (e.g., if we're on an IPv6-only network).
                    sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);

                    // To be sure we don't get stuck with a non-working network if all we had is
                    // IPv4, remove the IPv4 address from the interface (since we're using DHCP,
                    // and DHCP failed). If we had an IPv4 address before, the deletion of the
                    // address  will cause a CMD_UPDATE_LINKPROPERTIES. If the IPv4 address was
                    // necessary for provisioning, its deletion will cause us to disconnect.
                    //
                    // This shouldn't be needed, because on an IPv4-only network a DHCP failure will
                    // have empty DhcpResults and thus empty LinkProperties, and isProvisioned will
                    // not return true if we're using DHCP and don't have an IPv4 default route. So
                    // for now it's only here for extra redundancy. However, it will increase
                    // robustness if we move to getting IPv4 routes from netlink as well.
                    loge("DHCP failure: provisioned, clearing IPv4 address.");
                    if (!clearIPv4Address(mInterfaceName)) {
                        sendMessage(CMD_IP_CONFIGURATION_LOST);
                    }
                }
                break;

            case CMD_STATIC_IP_FAILURE:
                // Static configuration was invalid, or an error occurred in applying it. Give up.
                sendMessage(CMD_IP_CONFIGURATION_LOST);
                break;

            case CMD_UPDATE_LINKPROPERTIES:
                // IP addresses, DNS servers, etc. changed. Act accordingly.
                if (lostProvisioning) {
                    // We no longer have a usable network configuration. Disconnect.
                    sendMessage(CMD_IP_CONFIGURATION_LOST);
                } else if (!wasProvisioned && isProvisioned) {
                    // We have a usable IPv6-only config. Advance to connected state.
                    sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);
                }
                if (linkChanged && getNetworkDetailedState() == DetailedState.CONNECTED) {
                    // If anything has changed and we're already connected, send out a notification.
                    sendLinkConfigurationChangedBroadcast();
                }
                break;
        }
    }
最后会根据DHCP是否成功发送CMD_IP_CONFIGURATION_SUCCESSFUL消息表明IP的配置已经完成(因为我们已经告知了ConnectifyService去更新网络配置,并附加了新的mLinkProperties对象),L2ConnectedState处理CMD_IP_CONFIGURATION_SUCCESSFUL消息:
case CMD_IP_CONFIGURATION_SUCCESSFUL:
                    handleSuccessfulIpConfiguration();
                    sendConnectedState();
                    transitionTo(mConnectedState);
handleSuccessfulIpConfiguration()函数处理:
private void handleSuccessfulIpConfiguration() {
        mLastSignalLevel = -1; // Force update of signal strength
        WifiConfiguration c = getCurrentWifiConfiguration();//获取代表当前连接的网络的WifiConfiguration对象
        if (c != null) {
            // Reset IP failure tracking
            c.numConnectionFailures = 0;
//重置numConnectionFailures字段,因为在WifiConfigStore中,会根据numConnectionFailures字段判断当前网络的连接失败次数,默认失败10次时,会终止重连操作
            // Tell the framework whether the newly connected network is trusted or untrusted.
            updateCapabilities(c);
        }
        if (c != null) {
            ScanResult result = getCurrentScanResult();
            if (result == null) {
                logd("WifiStateMachine: handleSuccessfulIpConfiguration and no scan results" +
                        c.configKey());
            } else {
                // Clear the per BSSID failure count
                result.numIpConfigFailures = 0;
                // Clear the WHOLE BSSID blacklist, which means supplicant is free to retry
                // any BSSID, even though it may already have a non zero ip failure count,
                // this will typically happen if the user walks away and come back to his arrea
                // TODO: implement blacklisting based on a timer, i.e. keep BSSID blacklisted
                // in supplicant for a couple of hours or a day
                mWifiConfigStore.clearBssidBlacklist();
            }
        }
    }
sendConnectedState()主要做一些网络状态的更新操作,发送NETWORK_STATE_CHANGED_ACTION通知当前的网络状态已经变化。此广播会附加WifiStateMachine中三个常用的字段值:
    /**
     * The link properties of the wifi interface.
     * Do not modify this directly; use updateLinkProperties instead.
     */
    private LinkProperties mLinkProperties; //保存当前网络连接的配置信息,包括IP地址集、DNS地址集、路由地址集等
    // NOTE: Do not return to clients - use #getWiFiInfoForUid(int)
    private WifiInfo mWifiInfo;//描述了一个Wifi连接的状态,通过该对象我们可以得知当前Wifi连接的SSID、netId、IP地址、Mac地址等信息
    private NetworkInfo mNetworkInfo;//表示一个网络接口(wlan0/eth0)的连接状态,通过该对象可以得知当前Wifi连接处于哪一步、是否连接等状态
最后切换到ConnectedState,至此AP已经连接、IP也已获取到,并且网络状态也已更新。后续的过程貌似没什么大用了。一个Wifi的连接流程到这里就分析完了。

PS:

4.4之后Android加入了AP评分机制。这个机制根据某个AP的很多配置信息一通处理,最后得出一个score,来表明某个AP的可连接性。它评判的属性有点多,后面在研究,现在本渣还是没有搞明白,囧....

在framework里面,其实屏蔽了很多复杂的操作,比如WifiNative里面的大多数方法都是要通过JNI调用wifi.c,再调到wpa_s。这些中间过程其实比framework的处理要复杂的多,代码也更晦涩。例如runDhcp()动作,有个专门的dhcpcd守护进程进行处理;它的实现跟wpa_s一样,都是很复杂的。即使了解DHCP协议的工作流程,要看懂它的代码实现也很痛苦。Android里还有的PPPoE拨号方式,它的守护进程pppd的实现也很复杂......;要做这方面的定制,对于偏向framework的人来说,是比较痛苦的。








  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
AndroidWiFi连接流程图主要包括以下步骤: 1. 打开WiFi:用户进入设置界面,打开设备的WiFi开关,使设备开始搜索可用的WiFi网络。 2. 搜索和扫描:Android设备开始搜索周围的WiFi网络,扫描到的网络信息将会显示在WiFi设置列表中,包括网络名称(SSID)、信号强度等。 3. 用户选择网络:用户根据列表中显示的网络信息,选择要连接WiFi网络。 4. 请求连接Android设备通过向选择的WiFi网络发送请求连接的请求,包括认证信息和其他必要的参数。 5. 连接认证:WiFi网络收到连接请求后,会进行身份验证,包括密码验证、MAC地址过滤等。如果认证通过,网络将发送认可的连接响应。 6. IP地址获取:一旦连接成功,Android设备将向WiFi网络请求分配一个IP地址,以便在网络上进行通信。 7. 地址分配:WiFi网络会为设备分配一个合法的IP地址,并将该信息通过DHCP(动态主机配置协议)返回给设备。 8. 连接成功:设备接收到IP地址和其他网络配置信息后,与WiFi网络建立连接成功。 9. 数据传输:现在,设备可以通过WiFi网络进行数据传输,包括浏览互联网、收发电子邮件等。 10. 断开连接:当用户关闭设备的WiFi开关或者设备离开WiFi网络的范围时,设备会与WiFi网络断开连接。 总的来说,AndroidWiFi连接流程图涵盖了搜索、选择、连接认证、IP地址获取、数据传输等多个环节,确保设备可以顺利地连接和使用WiFi网络。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值