Android -- Wifi热点的打开与关闭流程简介
在Android手机中,热点也是一个较为常用的功能。对于framework开发者来说,要开发、维护SoftAp,了解framework中热点开关的具体流程是非常有必要的。下面就对这部分内容做一些介绍,以供后续查阅。
一、SoftAp打开流程
当我们在设置中打开热点时,会调用WifiManager::setWifiApEnabled(),参数enabled为true;间接调用同名的WifiServiceImpl::setWifiApEnabled():
参数中的wifiConfig对象保存了在Settings中操作时保留的热点信息,如热点名称、密钥和加密方式等等。与Wifi本身的打开和关闭类似,Wifi热点的打开流程也是通过WifiController状态机向WifiStateMachine转发消息的。与前面介绍的Wifi打开流程类似,CMD_SET_AP消息在ApStaDisabledState状态处理:
由此转入WifiStateMachine进行打开流程:
WifiStateMachine::InitialState会处理该消息:
首先肯定是先加载驱动,驱动加载成功后通过enableSoftAp()配置Wifi热点:
Wifi热点首先需要绑定端口信息,再以AP模式通过NetworkManagementService在wlan0端口下加载固件;同时热点功能也需要HAL层的支持。
- /**
- * Start AccessPoint mode with the specified
- * configuration. If the radio is already running in
- * AP mode, update the new configuration
- * Note that starting in access point mode disables station
- * mode operation
- * @param wifiConfig SSID, security and channel details as
- * part of WifiConfiguration
- * @return {@code true} if the operation succeeds, {@code false} otherwise
- *
- * @hide Dont open up yet
- */
- public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
- try {
- mService.setWifiApEnabled(wifiConfig, enabled);
- return true;
- } catch (RemoteException e) {
- return false;
- }
- }
- /**
- * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
- * @param wifiConfig SSID, security and channel details as
- * part of WifiConfiguration
- * @param enabled true to enable and false to disable
- */
- public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
- enforceChangePermission();
- ConnectivityManager.enforceTetherChangePermission(mContext);
- if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
- throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user.");
- }
- // null wifiConfig is a meaningful input for CMD_SET_AP
- if (wifiConfig == null || isValid(wifiConfig)) {
- mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
- } else {
- Slog.e(TAG, "Invalid WifiConfiguration");
- }
- }
- case CMD_SET_AP:
- if (msg.arg1 == 1) {
- mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
- true);
- transitionTo(mApEnabledState);//此时WifiController的状态停留在ApEnabledState
- }
- /**
- * TODO: doc
- */
- public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
- if (enable) {
- sendMessage(CMD_START_AP, wifiConfig);
- } else {
- sendMessage(CMD_STOP_AP);
- }
- }
- case CMD_START_AP:
- if (mWifiNative.loadDriver() == false) {
- loge("Failed to load driver for softap");
- } else {
- if (enableSoftAp() == true) {
- setWifiApState(WIFI_AP_STATE_ENABLING, 0);//该函数中会发送广播,告知外界当前热点的启动阶段
- transitionTo(mSoftApStartingState);//切换状态
- } else {
- setWifiApState(WIFI_AP_STATE_FAILED,
- WifiManager.SAP_START_FAILURE_GENERAL);
- transitionTo(mInitialState);
- }
- }
- break;
- /* SoftAP configuration */
- private boolean enableSoftAp() {
- if (WifiNative.getInterfaces() != 0) {
- if (!mWifiNative.toggleInterface(0)) {
- if (DBG) Log.e(TAG, "toggleInterface failed");
- return false;
- }
- } else {
- if (DBG) Log.d(TAG, "No interfaces to toggle");
- }
- try {
- mNwService.wifiFirmwareReload(mInterfaceName, "AP");//加载固件
- if (DBG) Log.d(TAG, "Firmware reloaded in AP mode");
- } catch (Exception e) {
- Log.e(TAG, "Failed to reload AP firmware " + e);
- }
- if (WifiNative.startHal() == false) {//启动HAL层
- /* starting HAL is optional */
- Log.e(TAG, "Failed to start HAL");
- }
- return true;
- }
setWifiApState()会发送广播,告知当前热点打开的过程信息;同理,也有setWifiState(),告知外界当前Wifi打开的过程信息;如果我们有必要知道当前热点打开的过程进行到什么阶段了,可以监听WifiManager.WIFI_AP_STATE_CHANGED_ACTION广播。最后状态切换到SoftApStartingState,如果流程有误,则会重新进入InitialState。
接着看SoftApStartingState::enter():
首先会判断打开热点时传入的WifiConfiguration对象是否为null;如果为空,则会向WifiApConfigStore发送CMD_REQUEST_AP_CONFIG消息,请求一个热点配置信息
- public void enter() {
- final Message message = getCurrentMessage();
- if (message.what == CMD_START_AP) {
- final WifiConfiguration config = (WifiConfiguration) message.obj;
- if (config == null) {
- mWifiApConfigChannel.sendMessage(CMD_REQUEST_AP_CONFIG);//1 - 获取先前或者默认的配置信息
- } else {
- mWifiApConfigChannel.sendMessage(CMD_SET_AP_CONFIG, config);//2 - 将上层传入的配置信息写到本地文件
- startSoftApWithConfig(config);//开启热点
- }
- } else {
- throw new RuntimeException("Illegal transition to SoftApStartingState: " + message);
- }
- }
。我们一起介绍这两个分支过程。回过头看InitialState状态的enter():
在创建完mWifiApConfigStore对象后,会调用mWifiApConfigStore.loadApConfiguration()加载热点配置信息:
主要是从/misc/wifi/softap.conf文件中读取其中的信息,并赋给WifiApConfigStore的成员变量mWifiApConfig,这个变量保存的就是当前SoftAp的配置信息。该文件一开始会有默认的信息保存其中,如果我们从没配置过热点,拿到的就是系统默认的信息;如果,上层配置了热点;我们也会将新的配置信息更新到softap.conf中,以供下载再次加载。再看消息处理过程:
向WifiStateMachine回复CMD_RESPONSE_AP_CONFIG消息,并附带mWifiApConfig对象。在SoftApStartingState::enter()中,如果config不为空,我们直接去调用startSoftApWithConfig()启动SoftAP;如果一开始config为null,通过处理CMD_RESPONSE_AP_CONFIG,获取到新的config对象,也应该去开启SoftAP了:
如果最后热点打开成功,发送CMD_START_AP_SUCCESS,看处理过程,SoftApStartingState:
最终状态在SoftApStartedState:
- public void enter() {
- WifiNative.stopHal();
- mWifiNative.unloadDriver();
- if (mWifiP2pChannel == null) {
- mWifiP2pChannel = new AsyncChannel();
- mWifiP2pChannel.connect(mContext, getHandler(),
- mWifiP2pServiceImpl.getP2pStateMachineMessenger());
- }
- if (mWifiApConfigChannel == null) {
- mWifiApConfigChannel = new AsyncChannel();
- mWifiApConfigStore = WifiApConfigStore.makeWifiApConfigStore(
- mContext, getHandler());//WifiApConfigStore也是一个小的状态机,此时会构建mWifiApConfigStore对戏,并启动状态机
- mWifiApConfigStore.loadApConfiguration();//在WifiApConfigStore中加载默认的热点配置信息
- mWifiApConfigChannel.connectSync(mContext, getHandler(),
- mWifiApConfigStore.getMessenger());//创建AsyncChannel对象,以供向WifiApConfigStore发送消息
- }
- if (mWifiConfigStore.enableHalBasedPno.get()) {
- // make sure developer Settings are in sync with the config option
- mHalBasedPnoEnableInDevSettings = true;
- }
- }
- void loadApConfiguration() {
- DataInputStream in = null;
- try {
- WifiConfiguration config = new WifiConfiguration();
- in = new DataInputStream(new BufferedInputStream(new FileInputStream(
- AP_CONFIG_FILE)));
- int version = in.readInt();
- if ((version != 1) && (version != 2)) {
- Log.e(TAG, "Bad version on hotspot configuration file, set defaults");
- setDefaultApConfiguration();
- return;
- }
- config.SSID = in.readUTF();
- if (version >= 2) {
- config.apBand = in.readInt();
- config.apChannel = in.readInt();
- }
- int authType = in.readInt();
- config.allowedKeyManagement.set(authType);
- if (authType != KeyMgmt.NONE) {
- config.preSharedKey = in.readUTF();
- }
- mWifiApConfig = config;
- } catch (IOException ignore) {
- setDefaultApConfiguration();
- } finally {
- if (in != null) {
- try {
- in.close();
- } catch (IOException e) {}
- }
- }
- }
- case WifiStateMachine.CMD_REQUEST_AP_CONFIG:
- mReplyChannel.replyToMessage(message,
- WifiStateMachine.CMD_RESPONSE_AP_CONFIG, mWifiApConfig);
- case WifiStateMachine.CMD_RESPONSE_AP_CONFIG:
- WifiConfiguration config = (WifiConfiguration) message.obj;
- if (config != null) {
- startSoftApWithConfig(config);
- } else {
- loge("Softap config is null!");//config依然为null,则热点打开失败
- sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL);//SoftApStartingState处理,状态重新切换到InitialState
- }
- break;
如果一开始的config对象不为空,从代码可知我们会先发送CMD_SET_AP_CONFIG消息,通知WifiApConfigStore更新配置信息,看处理流程:
首先将传入的配置对象保存到mWifiApConfig,接着切换状态:
enter()函数中,会调用writeApConfiguration()将mWifiApConfig的信息更新到/misc/wifi/softap.conf文件中,供下次加载使用:
处理比较简单,接着给自己发送CMD_SET_AP_CONFIG_COMPLETED消息,告知配置信息更新已经完毕,并重新进入InactiveState,重新等待下次配置信息的更新处理。
我们再返回到WifiStateMachine::SoftApStartingState处理CMD_RESPONSE_AP_CONFIG,如果再次获取后的config依然为null,则通知热点打开失败。接着就是真正开启热点的函数处理:
- class InactiveState extends State {
- public boolean processMessage(Message message) {
- switch (message.what) {
- case WifiStateMachine.CMD_SET_AP_CONFIG:
- WifiConfiguration config = (WifiConfiguration)message.obj;
- if (config.SSID != null) {
- mWifiApConfig = config;//将上层传入的配置信息先保存到成员变量中
- transitionTo(mActiveState);//切换状态
- } else {
- Log.e(TAG, "Try to setup AP config without SSID: " + message);
- }
- class ActiveState extends State {
- public void enter() {
- new Thread(new Runnable() {
- public void run() {
- writeApConfiguration(mWifiApConfig);//更新配置信息到本地
- sendMessage(WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED);//发送更新完成消息
- }
- }).start();
- }
- public boolean processMessage(Message message) {
- switch (message.what) {
- //TODO: have feedback to the user when we do this
- //to indicate the write is currently in progress
- case WifiStateMachine.CMD_SET_AP_CONFIG:
- deferMessage(message);
- break;
- case WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED:
- transitionTo(mInactiveState);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
- private void writeApConfiguration(final WifiConfiguration config) {
- DataOutputStream out = null;
- try {
- out = new DataOutputStream(new BufferedOutputStream(
- new FileOutputStream(AP_CONFIG_FILE)));
- out.writeInt(AP_CONFIG_FILE_VERSION);
- out.writeUTF(config.SSID);
- out.writeInt(config.apBand);
- out.writeInt(config.apChannel);
- int authType = config.getAuthType();
- out.writeInt(authType);
- if(authType != KeyMgmt.NONE) {
- out.writeUTF(config.preSharedKey);
- }
- } catch (IOException e) {
- Log.e(TAG, "Error writing hotspot configuration" + e);
- } finally {
- if (out != null) {
- try {
- out.close();
- } catch (IOException e) {}
- }
- }
- }
- /* Current design is to not set the config on a running hostapd but instead
- * stop and start tethering when user changes config on a running access point
- *
- * TODO: Add control channel setup through hostapd that allows changing config
- * on a running daemon
- */
- private void startSoftApWithConfig(final WifiConfiguration configuration) {
- // set channel
- final WifiConfiguration config = new WifiConfiguration(configuration);
- if (DBG) {
- Log.d(TAG, "SoftAp config channel is: " + config.apChannel);
- }
- //We need HAL support to set country code and get available channel list, if HAL is
- //not available, like razor, we regress to original implementaion (2GHz, channel 6)
- if (mWifiNative.isHalStarted()) {//因为SoftAp需要HAL层的支持,所有首先要进行确定,再继续配置
- //set country code through HAL Here
- if (mSetCountryCode != null) {
- if (!mWifiNative.setCountryCodeHal(mSetCountryCode.toUpperCase(Locale.ROOT))) {
- if (config.apBand != 0) {
- Log.e(TAG, "Fail to set country code. Can not setup Softap on 5GHz");
- //countrycode is mandatory for 5GHz
- sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL);
- return;
- }
- }
- } else {
- if (config.apBand != 0) {
- //countrycode is mandatory for 5GHz
- Log.e(TAG, "Can not setup softAp on 5GHz without country code!");
- sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL);
- return;
- }
- }
- if (config.apChannel == 0) {
- config.apChannel = chooseApChannel(config.apBand);
- if (config.apChannel == 0) {
- if(mWifiNative.isGetChannelsForBandSupported()) {
- //fail to get available channel
- sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_NO_CHANNEL);
- return;
- } else {
- //for some old device, wifiHal may not be supportedget valid channels are not
- //supported
- config.apBand = 0;
- config.apChannel = 6;
- }
- }
- }
- } else {
- //for some old device, wifiHal may not be supported
- config.apBand = 0;
- config.apChannel = 6;
- }
- // Start hostapd on a separate thread
- new Thread(new Runnable() {//开启一个新线程,来启动hostapd;我们支持wpa_s是支持Wifi的,hostapd则是支持SoftAP的
- public void run() {
- try {
- mNwService.startAccessPoint(config, mInterfaceName);//通过NetworkManagerService,在无线端口上,按传入的配置信息开启SoftAP;
- } catch (Exception e) {
- loge("Exception in softap start " + e);
- try {
- mNwService.stopAccessPoint(mInterfaceName);
- mNwService.startAccessPoint(config, mInterfaceName);
- } catch (Exception e1) {
- loge("Exception in softap re-start " + e1);
- sendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL);//打开失败,状态会重新切换到InitialState;等待下一次过程
- return;
- }
- }
- if (DBG) log("Soft AP start successful");
- sendMessage(CMD_START_AP_SUCCESS);//打开成功
- }
- }).start();
- }
- case CMD_START_AP_SUCCESS:
- setWifiApState(WIFI_AP_STATE_ENABLED, 0);//发送广播,告知SoftAp已经成功打开
- transitionTo(mSoftApStartedState);//切换状态
- break;
- case CMD_START_AP_FAILURE:
- setWifiApState(WIFI_AP_STATE_FAILED, message.arg1);//发送广播,告知SoftAp未成功打开
- transitionTo(mInitialState);//切换到初始状态
- class SoftApStartedState extends State {
- @Override
- public boolean processMessage(Message message) {
- logStateAndMessage(message, getClass().getSimpleName());
- switch(message.what) {
- case CMD_STOP_AP:
- if (DBG) log("Stopping Soft AP");
- /* We have not tethered at this point, so we just shutdown soft Ap */
- try {
- mNwService.stopAccessPoint(mInterfaceName);
- } catch(Exception e) {
- loge("Exception in stopAccessPoint()");
- }
- setWifiApState(WIFI_AP_STATE_DISABLED, 0);
- transitionTo(mInitialState);
- break;
- case CMD_START_AP:
- // Ignore a start on a running access point
- break;
- // Fail client mode operation when soft AP is enabled
- case CMD_START_SUPPLICANT:
- loge("Cannot start supplicant with a running soft AP");
- setWifiState(WIFI_STATE_UNKNOWN);
- break;
- case CMD_TETHER_STATE_CHANGE:
- TetherStateChange stateChange = (TetherStateChange) message.obj;
- if (startTethering(stateChange.available)) {
- transitionTo(mTetheringState);
- }
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
到这里,一个完整的SoftAp打开流程就结束了。
二、SoftAp关闭流程
关闭SoftAp的方法调用与打开SoftAp一致,不过enabled此时是为false:
由第一部分的内容可知WifiController状态机在处理完SoftAp打开后,停在ApEnabledState状态,那么我们看它是怎么处理CMD_SET_AP的:
有前述可知,如果参数enabled为false,mag.arg1就应该为0,调用setHostApRunning()走关闭流程,并将WifiController中的状态重置为ApStaDisabledState,等待下一次流程的开始。看setHostApRunning():
发送CMD_STOP_AP消息;已知SoftAp成功打开后,WifiStateMachine停留在SoftApStartedState,看其处理:
首先,通过NetworkManagermentService关闭SoftAp,并发送广播通知SoftAp的状态改变;最后WifiStateMachine切换到InitialState:
停掉HAL层,卸载驱动;重新等待下一次Wifi/SoftAp的启动过程。到此,热点关闭的动作就结束了。
- public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled)
- case CMD_SET_AP:
- if (msg.arg1 == 0) {
- mWifiStateMachine.setHostApRunning(null, false);//在WifiStateMachine中开始热点关闭流程
- transitionTo(mApStaDisabledState);//切换到初始状态
- }
- break;
- /**
- * TODO: doc
- */
- public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
- if (enable) {
- sendMessage(CMD_START_AP, wifiConfig);
- } else {
- sendMessage(CMD_STOP_AP);
- }
- }
- case CMD_STOP_AP:
- if (DBG) log("Stopping Soft AP");
- /* We have not tethered at this point, so we just shutdown soft Ap */
- try {
- mNwService.stopAccessPoint(mInterfaceName);//直接关闭SoftAp
- } catch(Exception e) {
- loge("Exception in stopAccessPoint()");
- }
- setWifiApState(WIFI_AP_STATE_DISABLED, 0);//发送广播,告知外界SoftAp的状态
- transitionTo(mInitialState);//切换到初始状态
- public void enter() {
- WifiNative.stopHal();
- mWifiNative.unloadDriver();
- if (mWifiP2pChannel == null) {
- mWifiP2pChannel = new AsyncChannel();
- mWifiP2pChannel.connect(mContext, getHandler(),
- mWifiP2pServiceImpl.getP2pStateMachineMessenger());
- }
- if (mWifiApConfigChannel == null) {
- mWifiApConfigChannel = new AsyncChannel();
- mWifiApConfigStore = WifiApConfigStore.makeWifiApConfigStore(
- mContext, getHandler());
- mWifiApConfigStore.loadApConfiguration();
- mWifiApConfigChannel.connectSync(mContext, getHandler(),
- mWifiApConfigStore.getMessenger());
- }
- if (mWifiConfigStore.enableHalBasedPno.get()) {
- // make sure developer Settings are in sync with the config option
- mHalBasedPnoEnableInDevSettings = true;
- }
- }
PS:
WifiManager中提供了两个关于SoftAp的操作函数:
1、设置SoftAP的配置信息
设置Wi-Fi AP的配置信息,它真正的处理过程是向WifiApConfigStore发送CMD_SET_AP_CONFIG消息,告知其要更新配置信息了。这一部分处理在第一部分已经分析过。
- /**
- * Sets the Wi-Fi AP Configuration.
- * @return {@code true} if the operation succeeded, {@code false} otherwise
- *
- * @hide Dont open yet
- */
- public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) {
- try {
- mService.setWifiApConfiguration(wifiConfig);
- return true;
- } catch (RemoteException e) {
- return false;
- }
- }
2、获取当前SoftAp正在使用的配置信息
它真正的处理过程是向WifiApConfigStore发送CMD_REQUEST_AP_CONFIG消息,请求WifiApConfigStore::mWifiApConfig成员,第一部分也已经说过,该变量保存的就是当前SoftAp正在使用的配置信息。
- /**
- * Gets the Wi-Fi AP Configuration.
- * @return AP details in WifiConfiguration
- *
- * @hide Dont open yet
- */
- public WifiConfiguration getWifiApConfiguration() {
- try {
- return mService.getWifiApConfiguration();
- } catch (RemoteException e) {
- return null;
- }
- }