您的位置:首页 > 移动开发 > Android开发

Android -- Wifi连接流程分析

2018-03-05 16:32 676 查看
当我们在Android手机上通过Settings连接一个AP时,间接调用WifiManager的connect()方法:[java] view plain copy/** 
     * 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中被处理:[java] view plain copy/* 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处理:[java] view plain copycase 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;  
通过阅读代码,可知主要的处理动作如下:
将connect()传过来的AP信息保存到WifiConfigStore对象中
通过WifiConfigStore::selectNetwork()函数更新WifiConfigStore和config的Priority优先级属性,最后更新到wpa_s配置文件中;enable当前要连接的AP,disable其他的AP
通过WifiNative::reconnect()函数向wpa_s发送连接指令,连接选定的AP

连接选定的AP是通过调用WifiNative方法向wpa_supplicant发送connect指令,wpa_s通知驱动进行连接;当底层无线连接成功后,framework就能通过WifiMonitor接受到wpa_s上报的event消息:[java] view plain copy/** 
     * 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;  
        }  
    }  
[java] view plain copyprivate 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进行处理:[java] view plain copycase 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()函数:[java] view plain copypublic 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地址:[java] view plain copy@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()函数处理:[java] view plain copyvoid startDhcp() {  
    maybeInitDhcpStateMachine(); //确保初始化WifiStateMachine持有的mDhcpStateMachine对象,会传入当前的WifiStateMachine对象,用来回发消息  
    mDhcpStateMachine.registerForPreDhcpNotification();//注册mRegisteredForPreDhcpNotification字段为true,表明我们在发送DHCP包之前需要做一些准备工作  
    mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);//发送开始DHCP的消息  
}  
DhcpStateMachine是一个小状态机,它主要处理DHCP下的IP地址获取过程,并将获取到的DHCP结果告知WifiStateMachine。看DhcpStateMachine处理该消息的过程:[java] view plain copycase 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处理该消息:[java] view plain copycase DhcpStateMachine.CMD_PRE_DHCP_ACTION:  
                  handlePreDhcpSetup();  
                  break;  
[java] view plain copyvoid 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中的消息处理:[java] view plain copycase CMD_PRE_DHCP_ACTION_COMPLETE:  
                    if (runDhcpStart()) {  
                        transitionTo(mRunningState);  
                    } else {  
                        transitionTo(mPollingState);  
                    }  
                    break;  
[java] view plain copyprivate 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等等地址信息。[java] view plain copyprivate 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消息:[java] view plain copycase 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()更新网络的配置信息:[java] view plain copyprivate 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()更新当前的网络配置信息:[java] view plain copyprivate 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消息:[java] view plain copycase CMD_IP_CONFIGURATION_SUCCESSFUL:  
                    handleSuccessfulIpConfiguration();  
                    sendConnectedState();  
                    transitionTo(mConnectedState);  
handleSuccessfulIpConfiguration()函数处理:[java] view plain copyprivate 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中三个常用的字段值:[java] view plain copy/** 
 * The link properties of the wifi interface. 
 * Do not modify this directly; use updateLinkProperties instead. 
 */  
private LinkProperties mLinkProperties; //保存当前网络连接的配置信息,包括IP地址集、DNS地址集、路由地址集等  
[java] view plain copy// 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的人来说,是比较痛苦的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: