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.             * 

* 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. *

* 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的人来说,是比较痛苦的。







更多相关文章

  1. Android Handler机制 - MessageQueue如何处理消息
  2. Android 网络编程基础之简单聊天程序
  3. Android 9.0网络权限适配
  4. 深入理解Android消息处理系统 Handler

随机推荐

  1. Android(安卓)USB转 串口
  2. rk3288 7.1去掉桌面搜索框
  3. Android(安卓)通过 IMSI 判断手机运营商
  4. android ANR 案例分析
  5. android 4.0 屏蔽home键实现
  6. android jbox2d学习笔记一 滑轮关节
  7. android 获取系统一些信息
  8. github Android优秀项目源码
  9. Android(安卓)Studio: Android(安卓)Mani
  10. Android架构优秀文章收集