Android -- Wifi连接流程分析

Android -- Wifi连接流程分析


当我们在Android手机上通过Settings连接一个AP时,间接调用WifiManager的connect()方法:
[java] view plain copy
  1. /** 
  2.      * Connect to a network with the given configuration. The network also 
  3.      * gets added to the supplicant configuration. 
  4.      * 
  5.      * For a new network, this function is used instead of a 
  6.      * sequence of addNetwork(), enableNetwork(), saveConfiguration() and 
  7.      * reconnect() 
  8.      * 
  9.      * @param config the set of variables that describe the configuration, 
  10.      *            contained in a {@link WifiConfiguration} object. 
  11.      * @param listener for callbacks on success or failure. Can be null. 
  12.      * @throws IllegalStateException if the WifiManager instance needs to be 
  13.      * initialized again 
  14.      * 
  15.      * @hide 
  16.      */  
  17.     public void connect(WifiConfiguration config, ActionListener listener) {  
  18.         if (config == nullthrow new IllegalArgumentException("config cannot be null");  
  19.         validateChannel();  
  20.         // Use INVALID_NETWORK_ID for arg1 when passing a config object  
  21.         // arg1 is used to pass network id when the network already exists  
  22.         sAsyncChannel.sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,  
  23.                 putListener(listener), config);  
  24.     }  
  25.   
  26.     /** 
  27.      * Connect to a network with the given networkId. 
  28.      * 
  29.      * This function is used instead of a enableNetwork(), saveConfiguration() and 
  30.      * reconnect() 
  31.      * 
  32.      * @param networkId the network id identifiying the network in the 
  33.      *                supplicant configuration list 
  34.      * @param listener for callbacks on success or failure. Can be null. 
  35.      * @throws IllegalStateException if the WifiManager instance needs to be 
  36.      * initialized again 
  37.      * @hide 
  38.      */  
  39.     public void connect(int networkId, ActionListener listener) {  
  40.         if (networkId < 0throw new IllegalArgumentException("Network id cannot be negative");  
  41.         validateChannel();  
  42.         sAsyncChannel.sendMessage(CONNECT_NETWORK, networkId, putListener(listener));  
  43.     }  
connect()方法有两种形式,一种接受WifiConfiguration对象,一种接受某个AP的networkID。WifiConfiguration描述了一个Wifi连接的所有配置信息。
WifiManager的ServiceHandler和WifiService的ClientHandler通过异步通道进行通信。所以这里通过AsyncChannel机制,向WifiServiceImpl发送CONNECT_NETWORK消息,可知在WifiServiceImpl::ClientHandler中被处理:
[java] view plain copy
  1. /* Client commands are forwarded to state machine */  
  2.                 case WifiManager.CONNECT_NETWORK:  
  3.                 case WifiManager.SAVE_NETWORK: {  
  4.                     WifiConfiguration config = (WifiConfiguration) msg.obj;  
  5.                     int networkId = msg.arg1;  
  6.                     if (msg.what == WifiManager.SAVE_NETWORK) {  
  7.                         Slog.e("WiFiServiceImpl ""SAVE"  
  8.                                 + " nid=" + Integer.toString(networkId)  
  9.                                 + " uid=" + msg.sendingUid  
  10.                                 + " name="  
  11.                                 + mContext.getPackageManager().getNameForUid(msg.sendingUid));  
  12.                     }  
  13.                     if (msg.what == WifiManager.CONNECT_NETWORK) {  
  14.                         Slog.e("WiFiServiceImpl ""CONNECT "  
  15.                                 + " nid=" + Integer.toString(networkId)  
  16.                                 + " uid=" + msg.sendingUid  
  17.                                 + " name="  
  18.                                 + mContext.getPackageManager().getNameForUid(msg.sendingUid));  
  19.                     }  
  20.   
  21.                     if (config != null && isValid(config)) {  
  22.                         if (DBG) Slog.d(TAG, "Connect with config" + config);  
  23.                         mWifiStateMachine.sendMessage(Message.obtain(msg));  
  24.                     } else if (config == null  
  25.                             && networkId != WifiConfiguration.INVALID_NETWORK_ID) {  
  26.                         if (DBG) Slog.d(TAG, "Connect with networkId" + networkId);  
  27.                         mWifiStateMachine.sendMessage(Message.obtain(msg));  
  28.                     } else {  
  29.                         Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);  
  30.                         if (msg.what == WifiManager.CONNECT_NETWORK) {  
  31.                             replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED,  
  32.                                     WifiManager.INVALID_ARGS);  
  33.                         } else {  
  34.                             replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED,  
  35.                                     WifiManager.INVALID_ARGS);  
  36.                         }  
  37.                     }  
  38.                     break;  
  39.                 }  
ClientHandler中并不做具体的连接动作,主要将CONNECT_NETWORK消息被转发到WifiStateMachine中,通过Wifi状态机来驱动连接和DHCP过程 。ConnectModeState处理:
[java] view plain copy
  1. case WifiManager.CONNECT_NETWORK:  
  2.     /** 
  3.      *  The connect message can contain a network id passed as arg1 on message or 
  4.      * or a config passed as obj on message. 
  5.      * For a new network, a config is passed to create and connect. 
  6.      * For an existing network, a network id is passed 
  7.      */  
  8.     netId = message.arg1;  
  9.     config = (WifiConfiguration) message.obj;  
  10.     mWifiConnectionStatistics.numWifiManagerJoinAttempt++;  
  11.     boolean updatedExisting = false;  
  12.   
  13.     /* Save the network config */  
  14.     if (config != null) {  
  15.         // When connecting to an access point, WifiStateMachine wants to update the  
  16.         // relevant config with administrative data. This update should not be  
  17.         // considered a 'real' update, therefore lockdown by Device Owner must be  
  18.         // disregarded.  
  19.         if (!recordUidIfAuthorized(config, message.sendingUid,  
  20.                 /* onlyAnnotate */ true)) {  
  21.             logw("Not authorized to update network "  
  22.                  + " config=" + config.SSID  
  23.                  + " cnid=" + config.networkId  
  24.                  + " uid=" + message.sendingUid);  
  25.             replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,  
  26.                            WifiManager.NOT_AUTHORIZED);  
  27.             break;  
  28.         }  
  29.   
  30.         String configKey = config.configKey(true /* allowCached */);  
  31.         WifiConfiguration savedConfig =  
  32.                 mWifiConfigStore.getWifiConfiguration(configKey);  
  33.         if (savedConfig != null) {  
  34.             // There is an existing config with this netId, but it wasn't exposed  
  35.             // (either AUTO_JOIN_DELETED or ephemeral; see WifiConfigStore#  
  36.             // getConfiguredNetworks). Remove those bits and update the config.  
  37.             config = savedConfig;  
  38.             logd("CONNECT_NETWORK updating existing config with id=" +  
  39.                     config.networkId + " configKey=" + configKey);  
  40.             config.ephemeral = false;  
  41.             config.autoJoinStatus = WifiConfiguration.AUTO_JOIN_ENABLED;  
  42.             updatedExisting = true;  
  43.         }  
  44.   
  45.         result = mWifiConfigStore.saveNetwork(config, message.sendingUid);  
  46.         netId = result.getNetworkId();  
  47.     }  
  48.     config = mWifiConfigStore.getWifiConfiguration(netId);  
  49.   
  50.     if (config == null) {  
  51.         logd("CONNECT_NETWORK no config for id=" + Integer.toString(netId) + " "  
  52.                 + mSupplicantStateTracker.getSupplicantStateName() + " my state "  
  53.                 + getCurrentState().getName());  
  54.         replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,  
  55.                 WifiManager.ERROR);  
  56.         break;  
  57.     } else {  
  58.         String wasSkipped = config.autoJoinBailedDueToLowRssi ? " skipped" : "";  
  59.         logd("CONNECT_NETWORK id=" + Integer.toString(netId)  
  60.                 + " config=" + config.SSID  
  61.                 + " cnid=" + config.networkId  
  62.                 + " supstate=" + mSupplicantStateTracker.getSupplicantStateName()  
  63.                 + " my state " + getCurrentState().getName()  
  64.                 + " uid = " + message.sendingUid  
  65.                 + wasSkipped);  
  66.     }  
  67.   
  68.     autoRoamSetBSSID(netId, "any");  
  69.   
  70.     if (message.sendingUid == Process.WIFI_UID  
  71.         || message.sendingUid == Process.SYSTEM_UID) {  
  72.         // As a sanity measure, clear the BSSID in the supplicant network block.  
  73.         // If system or Wifi Settings want to connect, they will not  
  74.         // specify the BSSID.  
  75.         // If an app however had added a BSSID to this configuration, and the BSSID  
  76.         // was wrong, Then we would forever fail to connect until that BSSID  
  77.         // is cleaned up.  
  78.         clearConfigBSSID(config, "CONNECT_NETWORK");  
  79.     }  
  80.   
  81.     if (deferForUserInput(message, netId, true)) {  
  82.         break;  
  83.     } else if (mWifiConfigStore.getWifiConfiguration(netId).userApproved ==  
  84.                                                     WifiConfiguration.USER_BANNED) {  
  85.         replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,  
  86.                 WifiManager.NOT_AUTHORIZED);  
  87.         break;  
  88.     }  
  89.   
  90.     mAutoRoaming = WifiAutoJoinController.AUTO_JOIN_IDLE;  
  91.   
  92.     /* Tell autojoin the user did try to connect to that network if from settings */  
  93.     boolean persist =  
  94.         mWifiConfigStore.checkConfigOverridePermission(message.sendingUid);  
  95.     mWifiAutoJoinController.updateConfigurationHistory(netId, true, persist);  
  96.   
  97.     mWifiConfigStore.setLastSelectedConfiguration(netId);  
  98.   
  99.     didDisconnect = false;  
  100.     if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID  
  101.             && mLastNetworkId != netId) {  
  102.         /** Supplicant will ignore the reconnect if we are currently associated, 
  103.          * hence trigger a disconnect 
  104.          */  
  105.         didDisconnect = true;  
  106.         mWifiNative.disconnect();  
  107.     }  
  108.   
  109.     // Make sure the network is enabled, since supplicant will not reenable it  
  110.     mWifiConfigStore.enableNetworkWithoutBroadcast(netId, false);  
  111.   
  112.     if (mWifiConfigStore.selectNetwork(config, /* updatePriorities = */ true,  
  113.             message.sendingUid) && mWifiNative.reconnect()) {  
  114.         lastConnectAttemptTimestamp = System.currentTimeMillis();  
  115.         targetWificonfiguration = mWifiConfigStore.getWifiConfiguration(netId);  
  116.   
  117.         /* The state tracker handles enabling networks upon completion/failure */  
  118.         mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);  
  119.         replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);  
  120.         if (didDisconnect) {  
  121.             /* Expect a disconnection from the old connection */  
  122.             transitionTo(mDisconnectingState);  
  123.         } else if (updatedExisting && getCurrentState() == mConnectedState &&  
  124.                 getCurrentWifiConfiguration().networkId == netId) {  
  125.             // Update the current set of network capabilities, but stay in the  
  126.             // current state.  
  127.             updateCapabilities(config);  
  128.         } else {  
  129.             /** 
  130.              *  Directly go to disconnected state where we 
  131.              * process the connection events from supplicant 
  132.              **/  
  133.             transitionTo(mDisconnectedState);  
  134.         }  
  135.     } else {  
  136.         loge("Failed to connect config: " + config + " netId: " + netId);  
  137.         replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,  
  138.                 WifiManager.ERROR);  
  139.         break;  
  140.     }  
  141.     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消息:
[java] view plain copy
  1. /** 
  2.      * Handle all supplicant events except STATE-CHANGE 
  3.      * @param event the event type 
  4.      * @param remainder the rest of the string following the 
  5.      * event name and " — " 
  6.      */  
  7.     void handleEvent(int event, String remainder) {  
  8.         if (DBG) {  
  9.             logDbg("handleEvent " + Integer.toString(event) + "  " + remainder);  
  10.         }  
  11.         switch (event) {  
  12.             case DISCONNECTED:  
  13.                 handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder);  
  14.                 break;  
  15.   
  16.             case CONNECTED:  
  17.                 handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder);  
  18.                 break;  
  19.   
  20.             case SCAN_RESULTS:  
  21.                 mStateMachine.sendMessage(SCAN_RESULTS_EVENT);  
  22.                 break;  
  23.   
  24.             case SCAN_FAILED:  
  25.                 mStateMachine.sendMessage(SCAN_FAILED_EVENT);  
  26.                 break;  
  27.   
  28.             case UNKNOWN:  
  29.                 if (DBG) {  
  30.                     logDbg("handleEvent unknown: " + Integer.toString(event) + "  " + remainder);  
  31.                 }  
  32.                 break;  
  33.             default:  
  34.                 break;  
  35.         }  
  36.     }  
[java] view plain copy
  1. private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) {  
  2.      String BSSID = null;  
  3.      int networkId = -1;  
  4.      int reason = 0;  
  5.      int ind = -1;  
  6.      int local = 0;  
  7.      Matcher match;  
  8.      if (newState == NetworkInfo.DetailedState.CONNECTED) {  
  9.          match = mConnectedEventPattern.matcher(data);  
  10.          if (!match.find()) {  
  11.             if (DBG) Log.d(TAG, "handleNetworkStateChange: Couldnt find BSSID in event string");  
  12.          } else {  
  13.              BSSID = match.group(1);  
  14.              try {  
  15.                  networkId = Integer.parseInt(match.group(2));  
  16.              } catch (NumberFormatException e) {  
  17.                  networkId = -1;  
  18.              }  
  19.          }  
  20.          notifyNetworkStateChange(newState, BSSID, networkId, reason);  
  21.      } else if (newState == NetworkInfo.DetailedState.DISCONNECTED) {  
  22.          match = mDisconnectedEventPattern.matcher(data);  
  23.          if (!match.find()) {  
  24.             if (DBG) Log.d(TAG, "handleNetworkStateChange: Could not parse disconnect string");  
  25.          } else {  
  26.              BSSID = match.group(1);  
  27.              try {  
  28.                  reason = Integer.parseInt(match.group(2));  
  29.              } catch (NumberFormatException e) {  
  30.                  reason = -1;  
  31.              }  
  32.              try {  
  33.                  local = Integer.parseInt(match.group(3));  
  34.              } catch (NumberFormatException e) {  
  35.                  local = -1;  
  36.              }  
  37.          }  
  38.          notifyNetworkStateChange(newState, BSSID, local, reason);  
  39.      }  
  40.  }  
  41.   
  42.  /** 
  43.   * Send the state machine a notification that the state of Wifi connectivity 
  44.   * has changed. 
  45.   * @param newState the new network state 
  46.   * @param BSSID when the new state is {@link NetworkInfo.DetailedState#CONNECTED}, 
  47.   * this is the MAC address of the access point. Otherwise, it 
  48.   * is {@code null}. 
  49.   * @param netId the configured network on which the state change occurred 
  50.   */  
  51.  void notifyNetworkStateChange(NetworkInfo.DetailedState newState,  
  52.                                String BSSID, int netId, int reason) {  
  53.      if (newState == NetworkInfo.DetailedState.CONNECTED) {  
  54.          Message m = mStateMachine.obtainMessage(NETWORK_CONNECTION_EVENT,  
  55.                  netId, reason, BSSID);  
  56.          mStateMachine.sendMessage(m);  
  57.      } else {  
  58.   
  59.          Message m = mStateMachine.obtainMessage(NETWORK_DISCONNECTION_EVENT,  
  60.                  netId, reason, BSSID);  
  61.          if (DBG) logDbg("WifiMonitor notify network disconnect: "  
  62.                  + BSSID  
  63.                  + " reason=" + Integer.toString(reason));  
  64.          mStateMachine.sendMessage(m);  
  65.      }  
  66.  }  
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 copy
  1. case WifiMonitor.NETWORK_CONNECTION_EVENT:  
  2.                     if (DBG) log("Network connection established");  
  3.                     mLastNetworkId = message.arg1; //成功加入到某无线网络中的AP的NetworkId  
  4.                     mLastBssid = (String) message.obj;  
  5.   
  6.                     mWifiInfo.setBSSID(mLastBssid);  
  7.                     mWifiInfo.setNetworkId(mLastNetworkId);  
  8.   
  9.                     sendNetworkStateChangeBroadcast(mLastBssid);  
  10.                     transitionTo(mObtainingIpState);  
  11.                     break;  
这里会将这次连接的AP的networkID保存下来,并进入到ObtainingIpState状态去真正触发DHCP动作。L2ConnectedState是ObtainingIpState的父状态,看它的enter()函数:
[java] view plain copy
  1. public void enter() {  
  2.             mRssiPollToken++;  
  3.             if (mEnableRssiPolling) {  
  4.                 sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0);  
  5.             }  
  6.             if (mNetworkAgent != null) {  
  7.                 loge("Have NetworkAgent when entering L2Connected");  
  8.                 setNetworkDetailedState(DetailedState.DISCONNECTED);  
  9.             }  
  10.             setNetworkDetailedState(DetailedState.CONNECTING);//更新当前的网络连接状态  
  11.   
  12.             if (!TextUtils.isEmpty(mTcpBufferSizes)) {  
  13.                 mLinkProperties.setTcpBufferSizes(mTcpBufferSizes);  
  14.             }  
  15.             mNetworkAgent = new WifiNetworkAgent(getHandler().getLooper(), mContext,  
  16.                     "WifiNetworkAgent", mNetworkInfo, mNetworkCapabilitiesFilter,  
  17.                     mLinkProperties, 60);//此处创建一个NetworkAgent对象用于向ConnectivityService通知相应的网络配置更新操作  
  18.   
  19.             // We must clear the config BSSID, as the wifi chipset may decide to roam  
  20.             // from this point on and having the BSSID specified in the network block would  
  21.             // cause the roam to faile and the device to disconnect  
  22.             clearCurrentConfigBSSID("L2ConnectedState");  
  23.   
  24.             try {  
  25.                 mIpReachabilityMonitor = new IpReachabilityMonitor(  
  26.                         mInterfaceName,  
  27.                         new IpReachabilityMonitor.Callback() {  
  28.                             @Override  
  29.                             public void notifyLost(InetAddress ip, String logMsg) {  
  30.                                 sendMessage(CMD_IP_REACHABILITY_LOST, logMsg);  
  31.                             }  
  32.                         });  
  33.             } catch (IllegalArgumentException e) {  
  34.                 Log.wtf("Failed to create IpReachabilityMonitor", e);  
  35.             }  
  36.         }  
我们再看ObtainingIpState::enter()方法看如何获取获取IP地址:
[java] view plain copy
  1. @Override  
  2. public void enter() {  
  3.     if (DBG) {  
  4.         String key = "";  
  5.         if (getCurrentWifiConfiguration() != null) {  
  6.             key = getCurrentWifiConfiguration().configKey();  
  7.         }  
  8.         log("enter ObtainingIpState netId=" + Integer.toString(mLastNetworkId)  
  9.                 + " " + key + " "  
  10.                 + " roam=" + mAutoRoaming  
  11.                 + " static=" + mWifiConfigStore.isUsingStaticIp(mLastNetworkId)  
  12.                 + " watchdog= " + obtainingIpWatchdogCount);  
  13.     }  
  14.   
  15.     // Reset link Debouncing, indicating we have successfully re-connected to the AP  
  16.     // We might still be roaming  
  17.     linkDebouncing = false;  
  18.   
  19.     // Send event to CM & network change broadcast  
  20.     setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);  
  21.   
  22.     // We must clear the config BSSID, as the wifi chipset may decide to roam  
  23.     // from this point on and having the BSSID specified in the network block would  
  24.     // cause the roam to faile and the device to disconnect  
  25.     clearCurrentConfigBSSID("ObtainingIpAddress");  
  26.   
  27.     try {  
  28.         mNwService.enableIpv6(mInterfaceName);  
  29.     } catch (RemoteException re) {  
  30.         loge("Failed to enable IPv6: " + re);  
  31.     } catch (IllegalStateException e) {  
  32.         loge("Failed to enable IPv6: " + e);  
  33.     }  
  34.   
  35.     if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {  
  36.         if (isRoaming()) {  
  37.             renewDhcp();  
  38.         } else {  
  39.             // Remove any IP address on the interface in case we're switching from static  
  40.             // IP configuration to DHCP. This is safe because if we get here when not  
  41.             // roaming, we don't have a usable address.  
  42.             clearIPv4Address(mInterfaceName);  
  43.             startDhcp();// DHCP过程启动  
  44.         }  
  45.         obtainingIpWatchdogCount++;  
  46.         logd("Start Dhcp Watchdog " + obtainingIpWatchdogCount);  
  47.         // Get Link layer stats so as we get fresh tx packet counters  
  48.         getWifiLinkLayerStats(true);  
  49.         sendMessageDelayed(obtainMessage(CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER,  
  50.                 obtainingIpWatchdogCount, 0), OBTAINING_IP_ADDRESS_GUARD_TIMER_MSEC);  
  51.     } else {  
  52.         // stop any running dhcp before assigning static IP  
  53.         stopDhcp();  
  54.         StaticIpConfiguration config = mWifiConfigStore.getStaticIpConfiguration(  
  55.                 mLastNetworkId);  
  56.         if (config.ipAddress == null) {  
  57.             logd("Static IP lacks address");  
  58.             sendMessage(CMD_STATIC_IP_FAILURE);  
  59.         } else {  
  60.             InterfaceConfiguration ifcg = new InterfaceConfiguration();  
  61.             ifcg.setLinkAddress(config.ipAddress);  
  62.             ifcg.setInterfaceUp();  
  63.             try {  
  64.                 mNwService.setInterfaceConfig(mInterfaceName, ifcg);  
  65.                 if (DBG) log("Static IP configuration succeeded");  
  66.                 DhcpResults dhcpResults = new DhcpResults(config);  
  67.                 sendMessage(CMD_STATIC_IP_SUCCESS, dhcpResults);  
  68.             } catch (RemoteException re) {  
  69.                 loge("Static IP configuration failed: " + re);  
  70.                 sendMessage(CMD_STATIC_IP_FAILURE);  
  71.             } catch (IllegalStateException e) {  
  72.                 loge("Static IP configuration failed: " + e);  
  73.                 sendMessage(CMD_STATIC_IP_FAILURE);  
  74.             }  
  75.         }  
  76.     }  
  77. }  
这里Wifi分了两种连接方式,Static IP和DHCP。这里区分静态和DHCP是通过WifiConfiguration对象来处理的。WifiConfiguration代表一个配置过的AP连接,主要包含IpAssignment(标识上层的连接方式是静态还是DHCP)、AP的networkID、AP的名字等等。我们主要看动态获取IP的过程。进入DHCP流程之前,会先清除当前的IP地址信息。着重看startDhcp()函数处理:
[java] view plain copy
  1. void startDhcp() {  
  2.     maybeInitDhcpStateMachine(); //确保初始化WifiStateMachine持有的mDhcpStateMachine对象,会传入当前的WifiStateMachine对象,用来回发消息  
  3.     mDhcpStateMachine.registerForPreDhcpNotification();//注册mRegisteredForPreDhcpNotification字段为true,表明我们在发送DHCP包之前需要做一些准备工作  
  4.     mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);//发送开始DHCP的消息  
  5. }  
DhcpStateMachine是一个小状态机,它主要处理DHCP下的IP地址获取过程,并将获取到的DHCP结果告知WifiStateMachine。看DhcpStateMachine处理该消息的过程:
[java] view plain copy
  1. case CMD_START_DHCP:  
  2.                     if (mRegisteredForPreDhcpNotification) {  
  3.                         /* Notify controller before starting DHCP */  
  4.                         mController.sendMessage(CMD_PRE_DHCP_ACTION);  
  5.                         transitionTo(mWaitBeforeStartState);  
  6.                     } else {  
  7.                         if (runDhcpStart()) {  
  8.                             transitionTo(mRunningState);  
  9.                         }  
  10.                     }  
  11.                     break;  
由于我们之前设置了mRegisteredForPreDhcpNotification为true,这里会向WifiStateMachine发送CMD_PRE_DHCP_ACTION消息,告知Wifi状态机做一些DHCP之前的预处理工作。L2ConnectedState处理该消息:
[java] view plain copy
  1. case DhcpStateMachine.CMD_PRE_DHCP_ACTION:  
  2.                   handlePreDhcpSetup();  
  3.                   break;  
[java] view plain copy
  1. void handlePreDhcpSetup() {  
  2.     mDhcpActive = true;  
  3.     if (!mBluetoothConnectionActive) {  
  4.         /* 
  5.          * There are problems setting the Wi-Fi driver's power 
  6.          * mode to active when bluetooth coexistence mode is 
  7.          * enabled or sense. 
  8.          * 

     

  9.          * We set Wi-Fi to active mode when 
  10.          * obtaining an IP address because we've found 
  11.          * compatibility issues with some routers with low power 
  12.          * mode. 
  13.          * 

     

  14.          * In order for this active power mode to properly be set, 
  15.          * we disable coexistence mode until we're done with 
  16.          * obtaining an IP address.  One exception is if we 
  17.          * are currently connected to a headset, since disabling 
  18.          * coexistence would interrupt that connection. 
  19.          */  
  20.         // Disable the coexistence mode  
  21.         mWifiNative.setBluetoothCoexistenceMode(  
  22.                 mWifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);  
  23.     }  
  24.   
  25.     // Disable power save and suspend optimizations during DHCP  
  26.     // Note: The order here is important for now. Brcm driver changes  
  27.     // power settings when we control suspend mode optimizations.  
  28.     // TODO: Remove this comment when the driver is fixed.  
  29.     setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, false);  
  30.     mWifiNative.setPowerSave(false);  
  31.   
  32.     // Update link layer stats  
  33.     getWifiLinkLayerStats(false);  
  34.   
  35.     /* P2p discovery breaks dhcp, shut it down in order to get through this */  
  36.     Message msg = new Message();  
  37.     msg.what = WifiP2pServiceImpl.BLOCK_DISCOVERY;  
  38.     msg.arg1 = WifiP2pServiceImpl.ENABLED;  
  39.     msg.arg2 = DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE;  
  40.     msg.obj = mDhcpStateMachine;  
  41.     mWifiP2pChannel.sendMessage(msg);  
  42. }  
从注释可知,为了保证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 copy
  1. case CMD_PRE_DHCP_ACTION_COMPLETE:  
  2.                     if (runDhcpStart()) {  
  3.                         transitionTo(mRunningState);  
  4.                     } else {  
  5.                         transitionTo(mPollingState);  
  6.                     }  
  7.                     break;  
[java] view plain copy
  1. private boolean runDhcpStart() {  
  2.     /* Stop any existing DHCP daemon before starting new */  
  3.     NetworkUtils.stopDhcp(mInterfaceName);//在启动新的DHCP流程之前,停止当前正在进行的DHCP过程  
  4.     mDhcpResults = null;  
  5.   
  6.     if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName);  
  7.     if (!NetworkUtils.startDhcp(mInterfaceName) || !dhcpSucceeded()) {  
  8.         Log.e(TAG, "DHCP request failed on " + mInterfaceName + ": " +  
  9.                 NetworkUtils.getDhcpError());  
  10.         mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)  
  11.                 .sendToTarget();  
  12.         return false;  
  13.     }  
  14.     return true;  
  15. }  
调用NetworkUtils.startDhcp()方法启动DHCP流程去获取IP地址,调用dhcpSucceeded()方法获取该次DHCP的DhcpResults对象,它包含了IP地址、网关、DNS等等地址信息。
[java] view plain copy
  1. private boolean dhcpSucceeded() {  
  2.         DhcpResults dhcpResults = new DhcpResults();  
  3.         if (!NetworkUtils.getDhcpResults(mInterfaceName, dhcpResults)) {  
  4.             return false;  
  5.         }  
  6.   
  7.         if (DBG) Log.d(TAG, "DHCP results found for " + mInterfaceName);  
  8.         long leaseDuration = dhcpResults.leaseDuration; //int to long conversion  
  9.   
  10.         //Sanity check for renewal  
  11.         if (leaseDuration >= 0) {  
  12.             //TODO: would be good to notify the user that his network configuration is  
  13.             //bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS  
  14.             if (leaseDuration < MIN_RENEWAL_TIME_SECS) {  
  15.                 leaseDuration = MIN_RENEWAL_TIME_SECS;  
  16.             }  
  17.             //Do it a bit earlier than half the lease duration time  
  18.             //to beat the native DHCP client and avoid extra packets  
  19.             //48% for one hour lease time = 29 minutes  
  20.             mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,  
  21.                     SystemClock.elapsedRealtime() +  
  22.                     leaseDuration * 480//in milliseconds  
  23.                     mDhcpRenewalIntent);  
  24.         } else {  
  25.             //infinite lease time, no renewal needed  
  26.         }  
  27.   
  28.         // Fill in any missing fields in dhcpResults from the previous results.  
  29.         // If mDhcpResults is null (i.e. this is the first server response),  
  30.         // this is a noop.  
  31.         dhcpResults.updateFromDhcpRequest(mDhcpResults);  
  32.         mDhcpResults = dhcpResults;  
  33.         mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpResults)  
  34.             .sendToTarget();  
  35.         return true;  
  36.     }  
如果getDhcpResults()函数执行成功,DhcpStateMachine就会发送CMD_POST_DHCP_ACTION消息给WifiStateMachine,状态位是DHCP_SUCCESS,并附加runDhcp()获取到的DhcpResult对象;失败时则附加DHCP_FAILURE。L2ConnectedState处理CMD_POST_DHCP_ACTION消息:
[java] view plain copy
  1. case DhcpStateMachine.CMD_POST_DHCP_ACTION:  
  2.                   handlePostDhcpSetup();  
  3.                   if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {  
  4.                       if (DBG) log("DHCP successful");  
  5.                       handleIPv4Success((DhcpResults) message.obj, DhcpStateMachine.DHCP_SUCCESS);  
  6.                       // We advance to mConnectedState because handleIPv4Success will call  
  7.                       // updateLinkProperties, which then sends CMD_IP_CONFIGURATION_SUCCESSFUL.  
  8.                   } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {  
  9.                       mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_DHCP_FAILURE);  
  10.                       if (DBG) {  
  11.                           int count = -1;  
  12.                           WifiConfiguration config = getCurrentWifiConfiguration();  
  13.                           if (config != null) {  
  14.                               count = config.numConnectionFailures;  
  15.                           }  
  16.                           log("DHCP failure count=" + count);  
  17.                       }  
  18.                       handleIPv4Failure(DhcpStateMachine.DHCP_FAILURE);  
  19.                       // As above, we transition to mDisconnectingState via updateLinkProperties.  
  20.                   }  
  21.                   break;  
首先调用handlePostDhcpSetup()重置之前所做的预处理动作;再处理附加的状态位,成功则调用handleIPv4Success()更新网络的配置信息:
[java] view plain copy
  1. private void handleIPv4Success(DhcpResults dhcpResults, int reason) {  
  2.   
  3.         if (PDBG) {  
  4.             logd("handleIPv4Success <" + dhcpResults.toString() + ">");  
  5.             logd("link address " + dhcpResults.ipAddress);  
  6.         }  
  7.   
  8.         Inet4Address addr;  
  9.         synchronized (mDhcpResultsLock) {  
  10.             mDhcpResults = dhcpResults;//保存当前的DHCP结果  
  11.             addr = (Inet4Address) dhcpResults.ipAddress.getAddress();  
  12.         }  
  13.   
  14.         if (isRoaming()) {  
  15.             int previousAddress = mWifiInfo.getIpAddress();  
  16.             int newAddress = NetworkUtils.inetAddressToInt(addr);  
  17.             if (previousAddress != newAddress) {  
  18.                 logd("handleIPv4Success, roaming and address changed" +  
  19.                         mWifiInfo + " got: " + addr);  
  20.             }  
  21.         }  
  22.         mWifiInfo.setInetAddress(addr);  
  23.         mWifiInfo.setMeteredHint(dhcpResults.hasMeteredHint());  
  24.         updateLinkProperties(reason);//更新网络配置信息  
  25.     }  
updateLinkProperties()更新当前的网络配置信息:
[java] view plain copy
  1. private void updateLinkProperties(int reason) {  
  2.     LinkProperties newLp = makeLinkProperties();//根据DHCP的结果创建新的LinkProperties对象,用于对比DHCP前后的网络配置是否已经改变  
  3.   
  4.     final boolean linkChanged = !newLp.equals(mLinkProperties);  
  5.     final boolean wasProvisioned = isProvisioned(mLinkProperties);  
  6.     final boolean isProvisioned = isProvisioned(newLp);  
  7.     // TODO: Teach LinkProperties how to understand static assignment  
  8.     // and simplify all this provisioning change detection logic by  
  9.     // unifying it under LinkProperties.compareProvisioning().  
  10.     final boolean lostProvisioning =  
  11.             (wasProvisioned && !isProvisioned) ||  
  12.             (mLinkProperties.hasIPv4Address() && !newLp.hasIPv4Address()) ||  
  13.             (mLinkProperties.isIPv6Provisioned() && !newLp.isIPv6Provisioned());  
  14.     final DetailedState detailedState = getNetworkDetailedState();  
  15.   
  16.     if (linkChanged) { //网络配置信息改变  
  17.         if (DBG) {  
  18.             log("Link configuration changed for netId: " + mLastNetworkId  
  19.                     + " old: " + mLinkProperties + " new: " + newLp);  
  20.         }  
  21.         mLinkProperties = newLp;//将新的配置信息保存到该字段中  
  22.         if (mIpReachabilityMonitor != null) {  
  23.             mIpReachabilityMonitor.updateLinkProperties(mLinkProperties);  
  24.         }  
  25.         if (mNetworkAgent != null) mNetworkAgent.sendLinkProperties(mLinkProperties);//通过NetworkAgent对象通知ConnectifyService更新网络配置信息  
  26.     }  
  27.   
  28.     if (lostProvisioning) {  
  29.         log("Lost IP layer provisioning!" +  
  30.                 " was: " + mLinkProperties +  
  31.                 " now: " + newLp);  
  32.     }  
  33.   
  34.     // If we just configured or lost IP configuration, do the needful.  
  35.     // We don't just call handleSuccessfulIpConfiguration() or handleIpConfigurationLost()  
  36.     // here because those should only be called if we're attempting to connect or already  
  37.     // connected, whereas updateLinkProperties can be called at any time.  
  38.     switch (reason) {  
  39.         case DhcpStateMachine.DHCP_SUCCESS:  
  40.         case CMD_STATIC_IP_SUCCESS:  
  41.             // IPv4 provisioning succeded. Advance to connected state.  
  42.             sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);  
  43.             if (!isProvisioned) {  
  44.                 // Can never happen unless DHCP reports success but isProvisioned thinks the  
  45.                 // resulting configuration is invalid (e.g., no IPv4 address, or the state in  
  46.                 // mLinkProperties is out of sync with reality, or there's a bug in this code).  
  47.                 // TODO: disconnect here instead. If our configuration is not usable, there's no  
  48.                 // point in staying connected, and if mLinkProperties is out of sync with  
  49.                 // reality, that will cause problems in the future.  
  50.                 logd("IPv4 config succeeded, but not provisioned");  
  51.             }  
  52.             break;  
  53.   
  54.         case DhcpStateMachine.DHCP_FAILURE:  
  55.             // DHCP failed. If we're not already provisioned, or we had IPv4 and now lost it,  
  56.             // give up and disconnect.  
  57.             // If we're already provisioned (e.g., IPv6-only network), stay connected.  
  58.             if (!isProvisioned || lostProvisioning) {  
  59.                 sendMessage(CMD_IP_CONFIGURATION_LOST);  
  60.             } else {  
  61.                 // DHCP failed, but we're provisioned (e.g., if we're on an IPv6-only network).  
  62.                 sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);  
  63.   
  64.                 // To be sure we don't get stuck with a non-working network if all we had is  
  65.                 // IPv4, remove the IPv4 address from the interface (since we're using DHCP,  
  66.                 // and DHCP failed). If we had an IPv4 address before, the deletion of the  
  67.                 // address  will cause a CMD_UPDATE_LINKPROPERTIES. If the IPv4 address was  
  68.                 // necessary for provisioning, its deletion will cause us to disconnect.  
  69.                 //  
  70.                 // This shouldn't be needed, because on an IPv4-only network a DHCP failure will  
  71.                 // have empty DhcpResults and thus empty LinkProperties, and isProvisioned will  
  72.                 // not return true if we're using DHCP and don't have an IPv4 default route. So  
  73.                 // for now it's only here for extra redundancy. However, it will increase  
  74.                 // robustness if we move to getting IPv4 routes from netlink as well.  
  75.                 loge("DHCP failure: provisioned, clearing IPv4 address.");  
  76.                 if (!clearIPv4Address(mInterfaceName)) {  
  77.                     sendMessage(CMD_IP_CONFIGURATION_LOST);  
  78.                 }  
  79.             }  
  80.             break;  
  81.   
  82.         case CMD_STATIC_IP_FAILURE:  
  83.             // Static configuration was invalid, or an error occurred in applying it. Give up.  
  84.             sendMessage(CMD_IP_CONFIGURATION_LOST);  
  85.             break;  
  86.   
  87.         case CMD_UPDATE_LINKPROPERTIES:  
  88.             // IP addresses, DNS servers, etc. changed. Act accordingly.  
  89.             if (lostProvisioning) {  
  90.                 // We no longer have a usable network configuration. Disconnect.  
  91.                 sendMessage(CMD_IP_CONFIGURATION_LOST);  
  92.             } else if (!wasProvisioned && isProvisioned) {  
  93.                 // We have a usable IPv6-only config. Advance to connected state.  
  94.                 sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);  
  95.             }  
  96.             if (linkChanged && getNetworkDetailedState() == DetailedState.CONNECTED) {  
  97.                 // If anything has changed and we're already connected, send out a notification.  
  98.                 sendLinkConfigurationChangedBroadcast();  
  99.             }  
  100.             break;  
  101.     }  
  102. }  
最后会根据DHCP是否成功发送CMD_IP_CONFIGURATION_SUCCESSFUL消息表明IP的配置已经完成(因为我们已经告知了ConnectifyService去更新网络配置,并附加了新的mLinkProperties对象),L2ConnectedState处理CMD_IP_CONFIGURATION_SUCCESSFUL消息:
[java] view plain copy
  1. case CMD_IP_CONFIGURATION_SUCCESSFUL:  
  2.                     handleSuccessfulIpConfiguration();  
  3.                     sendConnectedState();  
  4.                     transitionTo(mConnectedState);  
handleSuccessfulIpConfiguration()函数处理:
[java] view plain copy
  1. private void handleSuccessfulIpConfiguration() {  
  2.         mLastSignalLevel = -1// Force update of signal strength  
  3.         WifiConfiguration c = getCurrentWifiConfiguration();//获取代表当前连接的网络的WifiConfiguration对象  
  4.         if (c != null) {  
  5.             // Reset IP failure tracking  
  6.             c.numConnectionFailures = 0;  
  7. //重置numConnectionFailures字段,因为在WifiConfigStore中,会根据numConnectionFailures字段判断当前网络的连接失败次数,默认失败10次时,会终止重连操作  
  8.             // Tell the framework whether the newly connected network is trusted or untrusted.  
  9.             updateCapabilities(c);  
  10.         }  
  11.         if (c != null) {  
  12.             ScanResult result = getCurrentScanResult();  
  13.             if (result == null) {  
  14.                 logd("WifiStateMachine: handleSuccessfulIpConfiguration and no scan results" +  
  15.                         c.configKey());  
  16.             } else {  
  17.                 // Clear the per BSSID failure count  
  18.                 result.numIpConfigFailures = 0;  
  19.                 // Clear the WHOLE BSSID blacklist, which means supplicant is free to retry  
  20.                 // any BSSID, even though it may already have a non zero ip failure count,  
  21.                 // this will typically happen if the user walks away and come back to his arrea  
  22.                 // TODO: implement blacklisting based on a timer, i.e. keep BSSID blacklisted  
  23.                 // in supplicant for a couple of hours or a day  
  24.                 mWifiConfigStore.clearBssidBlacklist();  
  25.             }  
  26.         }  
  27.     }  
sendConnectedState()主要做一些网络状态的更新操作,发送NETWORK_STATE_CHANGED_ACTION通知当前的网络状态已经变化。此广播会附加WifiStateMachine中三个常用的字段值:
[java] view plain copy
  1. /** 
  2.  * The link properties of the wifi interface. 
  3.  * Do not modify this directly; use updateLinkProperties instead. 
  4.  */  
  5. private LinkProperties mLinkProperties; //保存当前网络连接的配置信息,包括IP地址集、DNS地址集、路由地址集等  
[java] view plain copy
  1. // NOTE: Do not return to clients - use #getWiFiInfoForUid(int)  
  2. private WifiInfo mWifiInfo;//描述了一个Wifi连接的状态,通过该对象我们可以得知当前Wifi连接的SSID、netId、IP地址、Mac地址等信息  
  3. 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的人来说,是比较痛苦的。


你可能感兴趣的:(android,网络框架接入分析,android,wi-fi,Wifi连接,连接流程分析)