本文简单介绍下WiFi连接与断开流程,参考源码Android P。
一、WiFi连接流程
WifiManager提供了两个connect接口,一个是connect(WifiConfiguration config, ActionListener listener)一个是connect(int networkId, ActionListener listener),也就是添加一个新的网络或者是连接一个已经保存的网络。
/**
* Connect to a network with the given configuration. The network also
* gets added to the list of configured networks for the foreground user.
*
* For a new network, this function is used instead of a
* sequence of addNetwork(), enableNetwork(), 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
*/
@SystemApi
public void connect(WifiConfiguration config, ActionListener listener) {
if (config == null) throw new IllegalArgumentException("config cannot be null");
// Use INVALID_NETWORK_ID for arg1 when passing a config object
// arg1 is used to pass network id when the network already exists
getChannel().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() and reconnect()
*
* @param networkId the ID of the network as returned by {@link #addNetwork} or {@link
* getConfiguredNetworks}.
* @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");
getChannel().sendMessage(CONNECT_NETWORK, networkId, putListener(listener));
}
都是发送消息CONNECT_NETWORK,WifiServiceImpl中会继续向WifiStateMachine发送消息,
case WifiManager.CONNECT_NETWORK: {
if (checkChangePermissionAndReplyIfNotAuthorized(
msg, WifiManager.CONNECT_NETWORK_FAILED)) {
WifiConfiguration config = (WifiConfiguration) msg.obj;
int networkId = msg.arg1;
Slog.d(TAG, "CONNECT "
+ " nid=" + Integer.toString(networkId)
+ " config=" + config
+ " uid=" + msg.sendingUid
+ " name="
+ mContext.getPackageManager().getNameForUid(msg.sendingUid));
if (config != null) {
/* Command is forwarded to state machine */
mWifiStateMachine.sendMessage(Message.obtain(msg));
} else if (config == null
&& networkId != WifiConfiguration.INVALID_NETWORK_ID) {
mWifiStateMachine.sendMessage(Message.obtain(msg));
} else {
Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.INVALID_ARGS);
}
}
break;
}
在WifiStateMachine ConnectModeState状态中处理CONNECT_NETWORK,
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;
boolean hasCredentialChanged = false;
// New network addition.
if (config != null) { //传入的config为null,不会进入
result = mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);//添加网络配置
if (!result.isSuccess()) {
loge("CONNECT_NETWORK adding/updating config=" + config + " failed");
messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.ERROR);
break;
}
netId = result.getNetworkId();
hasCredentialChanged = result.hasCredentialChanged();
}
if (!connectToUserSelectNetwork(
netId, message.sendingUid, hasCredentialChanged)) {
messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.NOT_AUTHORIZED);
break;
}
mWifiMetrics.logStaEvent(StaEvent.TYPE_CONNECT_NETWORK, config);
broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
break;
此后将继续调用connectToUserSelectNetwork,去连接用户点击去连接的网络。
/**
* Initiates connection to a network specified by the user/app. This method checks if the
* requesting app holds the NETWORK_SETTINGS permission.
*
* @param netId Id network to initiate connection.
* @param uid UID of the app requesting the connection.
* @param forceReconnect Whether to force a connection even if we're connected to the same
* network currently.
*/
private boolean connectToUserSelectNetwork(int netId, int uid, boolean forceReconnect) {
logd("connectToUserSelectNetwork netId " + netId + ", uid " + uid
+ ", forceReconnect = " + forceReconnect);
if (mWifiConfigManager.getConfiguredNetwork(netId) == null) {
loge("connectToUserSelectNetwork Invalid network Id=" + netId);
return false;
}
if (!mWifiConfigManager.enableNetwork(netId, true, uid)
|| !mWifiConfigManager.updateLastConnectUid(netId, uid)) {
logi("connectToUserSelectNetwork Allowing uid " + uid
+ " with insufficient permissions to connect=" + netId);
} else if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
// Note user connect choice here, so that it will be considered in the next network
// selection.
mWifiConnectivityManager.setUserConnectChoice(netId);
}
if (!forceReconnect && mWifiInfo.getNetworkId() == netId) {
// We're already connected to the user specified network, don't trigger a
// reconnection unless it was forced.
logi("connectToUserSelectNetwork already connecting/connected=" + netId);
} else {
mWifiConnectivityManager.prepareForForcedConnection(netId);
startConnectToNetwork(netId, uid, SUPPLICANT_BSSID_ANY);
}
return true;
}
/**
* Automatically connect to the network specified
*
* @param networkId ID of the network to connect to
* @param uid UID of the app triggering the connection.
* @param bssid BSSID of the network
*/
public void startConnectToNetwork(int networkId, int uid, String bssid) {
sendMessage(CMD_START_CONNECT, networkId, uid, bssid);
}
走到startConnectToNetwork()方法中,CMD_START_CONNECT消息再一次在ConnectModeState中处理。
case CMD_START_CONNECT:
/* connect command coming from auto-join */
netId = message.arg1;
int uid = message.arg2;
bssid = (String) message.obj;
synchronized (mWifiReqCountLock) {
if (!hasConnectionRequests()) {
if (mNetworkAgent == null) {
loge("CMD_START_CONNECT but no requests and not connected,"
+ " bailing");
break;
} else if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
loge("CMD_START_CONNECT but no requests and connected, but app "
+ "does not have sufficient permissions, bailing");
break;
}
}
}
config = mWifiConfigManager.getConfiguredNetworkWithoutMasking(netId);
logd("CMD_START_CONNECT sup state "
+ mSupplicantStateTracker.getSupplicantStateName()
+ " my state " + getCurrentState().getName()
+ " nid=" + Integer.toString(netId)
+ " roam=" + Boolean.toString(mIsAutoRoaming));
if (config == null) {
loge("CMD_START_CONNECT and no config, bail out...");
break;
}
mTargetNetworkId = netId;
setTargetBssid(config, bssid);
if (mEnableConnectedMacRandomization.get()) {
configureRandomizedMacAddress(config);
}
String currentMacAddress = mWifiNative.getMacAddress(mInterfaceName);
mWifiInfo.setMacAddress(currentMacAddress);
Log.i(TAG, "Connecting with " + currentMacAddress + " as the mac address");
reportConnectionAttemptStart(config, mTargetRoamBSSID,
WifiMetricsProto.ConnectionEvent.ROAM_UNRELATED);
if (mWifiNative.connectToNetwork(mInterfaceName, config)) {//启动连接
mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT, config);
lastConnectAttemptTimestamp = mClock.getWallClockMillis();
targetWificonfiguration = config;
mIsAutoRoaming = false;
if (getCurrentState() != mDisconnectedState) {
transitionTo(mDisconnectingState);
}
} else {//无法启动连接
loge("CMD_START_CONNECT Failed to start connection to network " + config);
reportConnectionAttemptEnd(
WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
WifiMetricsProto.ConnectionEvent.HLF_NONE);
replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.ERROR);
break;
}
break;
判断中的启动连接是调的WifiNative的connectToNetwork()方法,该方法提供的网络配置添加到wpa_supplicant并启动它的连接。
/**
* Add the provided network configuration to wpa_supplicant and initiate connection to it.
* This method does the following:
* 1. Abort any ongoing scan to unblock the connection request.
* 2. Remove any existing network in wpa_supplicant(This implicitly triggers disconnect).
* 3. Add a new network to wpa_supplicant.
* 4. Save the provided configuration to wpa_supplicant.
* 5. Select the new network in wpa_supplicant.
* 6. Triggers reconnect command to wpa_supplicant.
*
* @param ifaceName Name of the interface.
* @param configuration WifiConfiguration parameters for the provided network.
* @return {@code true} if it succeeds, {@code false} otherwise
*/
public boolean connectToNetwork(@NonNull String ifaceName, WifiConfiguration configuration) {
// Abort ongoing scan before connect() to unblock connection request.
mWificondControl.abortScan(ifaceName);//中止正在进行的单次扫描。
return mSupplicantStaIfaceHal.connectToNetwork(ifaceName, configuration);//添加网络配置
}
SupplicantStaIfaceHal中继续调用connectToNetwork,在其中调用select()。
/**
* Add the provided network configuration to wpa_supplicant and initiate connection to it.
* This method does the following:
* 1. If |config| is different to the current supplicant network, removes all supplicant
* networks and saves |config|.
* 2. Select the new network in wpa_supplicant.
*
* @param ifaceName Name of the interface.
* @param config WifiConfiguration parameters for the provided network.
* @return {@code true} if it succeeds, {@code false} otherwise
*/
public boolean connectToNetwork(@NonNull String ifaceName, @NonNull WifiConfiguration config) {
synchronized (mLock) {
logd("connectToNetwork " + config.configKey());
WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName);
if (WifiConfigurationUtil.isSameNetwork(config, currentConfig)) {
String networkSelectionBSSID = config.getNetworkSelectionStatus()
.getNetworkSelectionBSSID();
String networkSelectionBSSIDCurrent =
currentConfig.getNetworkSelectionStatus().getNetworkSelectionBSSID();
if (Objects.equals(networkSelectionBSSID, networkSelectionBSSIDCurrent)) {
logd("Network is already saved, will not trigger remove and add operation.");
} else {
logd("Network is already saved, but need to update BSSID.");
if (!setCurrentNetworkBssid(
ifaceName,
config.getNetworkSelectionStatus().getNetworkSelectionBSSID())) {
loge("Failed to set current network BSSID.");
return false;
}
mCurrentNetworkLocalConfigs.put(ifaceName, new WifiConfiguration(config));
}
} else {
mCurrentNetworkRemoteHandles.remove(ifaceName);
mCurrentNetworkLocalConfigs.remove(ifaceName);
if (!removeAllNetworks(ifaceName)) {
loge("Failed to remove existing networks");
return false;
}
Pair pair =
addNetworkAndSaveConfig(ifaceName, config);
if (pair == null) {
loge("Failed to add/save network configuration: " + config.configKey());
return false;
}
mCurrentNetworkRemoteHandles.put(ifaceName, pair.first);
mCurrentNetworkLocalConfigs.put(ifaceName, pair.second);
}
SupplicantStaNetworkHal networkHandle =
checkSupplicantStaNetworkAndLogFailure(ifaceName, "connectToNetwork");
if (networkHandle == null || !networkHandle.select()) {
loge("Failed to select network configuration: " + config.configKey());
return false;
}
return true;
}
}
在SupplicantStaNetworkHal中调用select触发与此网络的连接。
/**
* Trigger a connection to this network.
*
* @return true if it succeeds, false otherwise.
*/
public boolean select() {
synchronized (mLock) {
final String methodStr = "select";
if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
try {
SupplicantStatus status = mISupplicantStaNetwork.select();
return checkStatusAndLogFailure(status, methodStr);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
return false;
}
}
}
ISupplicantStaNetwork.hal中调用select。
/**
* Initiate connection to this network.
*
* @return status Status of the operation.
* Possible status codes:
* |SupplicantStatusCode.SUCCESS|,
* |SupplicantStatusCode.FAILURE_UNKNOWN|,
* |SupplicantStatusCode.FAILURE_NETWORK_INVALID|
*/
select() generates (SupplicantStatus status);
sta_network.cpp中调用select。
Return StaNetwork::select(select_cb _hidl_cb)
{
return validateAndCall(
this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
&StaNetwork::selectInternal, _hidl_cb);
}
SupplicantStatus StaNetwork::selectInternal()
{
struct wpa_ssid *wpa_ssid = retrieveNetworkPtr();
if (wpa_ssid->disabled == 2) {
return {SupplicantStatusCode::FAILURE_UNKNOWN, ""};
}
struct wpa_supplicant *wpa_s = retrieveIfacePtr();
wpa_s->scan_min_time.sec = 0;
wpa_s->scan_min_time.usec = 0;
wpa_supplicant_select_network(wpa_s, wpa_ssid);
return {SupplicantStatusCode::SUCCESS, ""};
}
wpa_supplicant.c中调用wpa_supplicant_select_network。
/**
* wpa_supplicant_select_network - Attempt association with a network
* @wpa_s: wpa_supplicant structure for a network interface
* @ssid: wpa_ssid structure for a configured network or %NULL for any network
*/
void wpa_supplicant_select_network(struct wpa_supplicant *wpa_s,
struct wpa_ssid *ssid)
{
struct wpa_ssid *other_ssid;
int disconnected = 0;
if (ssid && ssid != wpa_s->current_ssid && wpa_s->current_ssid) {
if (wpa_s->wpa_state >= WPA_AUTHENTICATING)
wpa_s->own_disconnect_req = 1;
wpa_supplicant_deauthenticate(
wpa_s, WLAN_REASON_DEAUTH_LEAVING);
disconnected = 1;
}
if (ssid)
wpas_clear_temp_disabled(wpa_s, ssid, 1);
/*
* Mark all other networks disabled or mark all networks enabled if no
* network specified.
*/
for (other_ssid = wpa_s->conf->ssid; other_ssid;
other_ssid = other_ssid->next) {
int was_disabled = other_ssid->disabled;
if (was_disabled == 2)
continue; /* do not change persistent P2P group data */
other_ssid->disabled = ssid ? (ssid->id != other_ssid->id) : 0;
if (was_disabled && !other_ssid->disabled)
wpas_clear_temp_disabled(wpa_s, other_ssid, 0);
if (was_disabled != other_ssid->disabled)
wpas_notify_network_enabled_changed(wpa_s, other_ssid);
}
if (ssid && ssid == wpa_s->current_ssid && wpa_s->current_ssid &&
wpa_s->wpa_state >= WPA_AUTHENTICATING) {
/* We are already associated with the selected network */
wpa_printf(MSG_DEBUG, "Already associated with the "
"selected network - do nothing");
return;
}
if (ssid) {
wpa_s->current_ssid = ssid;
eapol_sm_notify_config(wpa_s->eapol, NULL, NULL);
wpa_s->connect_without_scan =
(ssid->mode == WPAS_MODE_MESH) ? ssid : NULL;
/*
* Don't optimize next scan freqs since a new ESS has been
* selected.
*/
os_free(wpa_s->next_scan_freqs);
wpa_s->next_scan_freqs = NULL;
} else {
wpa_s->connect_without_scan = NULL;
}
wpa_s->disconnected = 0;
wpa_s->reassociate = 1;
if (wpa_s->connect_without_scan ||
wpa_supplicant_fast_associate(wpa_s) != 1) {
wpa_s->scan_req = NORMAL_SCAN_REQ;
wpas_scan_reset_sched_scan(wpa_s);
wpa_supplicant_req_scan(wpa_s, 0, disconnected ? 100000 : 0);
}
if (ssid)
wpas_notify_network_selected(wpa_s, ssid);
}
二、WiFi断开流程
WifiManager中提供断开当前网络连接的接口disconnect()。
/**
* Disassociate from the currently active access point. This may result
* in the asynchronous delivery of state change events.
* @return {@code true} if the operation succeeded
*/
public boolean disconnect() {
try {
mService.disconnect(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
WifiServiceImpl中调用disconnect。
/**
* see {@link android.net.wifi.WifiManager#disconnect()}
*/
@Override
public void disconnect(String packageName) {
if (enforceChangePermission(packageName) != MODE_ALLOWED) {
return;
}
mLog.info("disconnect uid=%").c(Binder.getCallingUid()).flush();
mWifiStateMachine.disconnectCommand();
}
WifiStateMachine中发送消息CMD_DISCONNECT。
/**
* Disconnect from Access Point
*/
public void disconnectCommand() {
sendMessage(CMD_DISCONNECT);
}
L2ConnectedState状态中则处理断开消息。
case CMD_DISCONNECT:
mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
StaEvent.DISCONNECT_GENERIC);
mWifiNative.disconnect(mInterfaceName);
transitionTo(mDisconnectingState);
break;
WifiNative
/**
* Trigger a disconnection from the currently connected network.
*
* @param ifaceName Name of the interface.
* @return true if request is sent successfully, false otherwise.
*/
public boolean disconnect(@NonNull String ifaceName) {
return mSupplicantStaIfaceHal.disconnect(ifaceName);
}
SupplicantStaIfaceHal
/**
* Trigger a disconnection from the currently connected network.
*
* @param ifaceName Name of the interface.
* @return true if request is sent successfully, false otherwise.
*/
public boolean disconnect(@NonNull String ifaceName) {
synchronized (mLock) {
final String methodStr = "disconnect";
ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
if (iface == null) return false;
try {
SupplicantStatus status = iface.disconnect();
return checkStatusAndLogFailure(status, methodStr);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
return false;
}
}
}
ISupplicantStaIface
/**
* Disconnect from the current active network.
*
* @return status Status of the operation.
* Possible status codes:
* |SupplicantStatusCode.SUCCESS|,
* |SupplicantStatusCode.FAILURE_UNKNOWN|,
* |SupplicantStatusCode.FAILURE_IFACE_INVALID|,
* |SupplicantStatusCode.FAILURE_IFACE_DISABLED|
*/
disconnect() generates (SupplicantStatus status);
sta_iface
Return StaIface::disconnect(disconnect_cb _hidl_cb)
{
return validateAndCall(
this, SupplicantStatusCode::FAILURE_IFACE_INVALID,
&StaIface::disconnectInternal, _hidl_cb);
}
SupplicantStatus StaIface::disconnectInternal()
{
struct wpa_supplicant *wpa_s = retrieveIfacePtr();
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) {
return {SupplicantStatusCode::FAILURE_IFACE_DISABLED, ""};
}
wpas_request_disconnection(wpa_s);
return {SupplicantStatusCode::SUCCESS, ""};
}
wpa_supplicant
/**
* wpas_request_disconnection - Request disconnection
* @wpa_s: Pointer to the network interface
*
* This function is used to request disconnection from the currently connected
* network. This will stop any ongoing scans and initiate deauthentication.
*/
void wpas_request_disconnection(struct wpa_supplicant *wpa_s)
{
#ifdef CONFIG_SME
wpa_s->sme.prev_bssid_set = 0;
#endif /* CONFIG_SME */
wpa_s->reassociate = 0;
wpa_s->disconnected = 1;
wpa_supplicant_cancel_sched_scan(wpa_s);
wpa_supplicant_cancel_scan(wpa_s);
wpa_supplicant_deauthenticate(wpa_s, WLAN_REASON_DEAUTH_LEAVING);
eloop_cancel_timeout(wpas_network_reenabled, wpa_s, NULL);
}