Android -- Wifi热点的打开与关闭流程简介

在Android手机中,热点也是一个较为常用的功能。对于framework开发者来说,要开发、维护SoftAp,了解framework中热点开关的具体流程是非常有必要的。下面就对这部分内容做一些介绍,以供后续查阅。

一、SoftAp打开流程

当我们在设置中打开热点时,会调用WifiManager::setWifiApEnabled(),参数enabled为true;间接调用同名的WifiServiceImpl::setWifiApEnabled():

/*** 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_APif (wifiConfig == null || isValid(wifiConfig)) {mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();} else {Slog.e(TAG, "Invalid WifiConfiguration");}}

参数中的wifiConfig对象保存了在Settings中操作时保留的热点信息,如热点名称、密钥和加密方式等等。与Wifi本身的打开和关闭类似,Wifi热点的打开流程也是通过WifiController状态机向WifiStateMachine转发消息的。与前面介绍的Wifi打开流程类似,CMD_SET_AP消息在ApStaDisabledState状态处理:

                case CMD_SET_AP:if (msg.arg1 == 1) {mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,true);transitionTo(mApEnabledState);//此时WifiController的状态停留在ApEnabledState}

由此转入WifiStateMachine进行打开流程:

    /*** TODO: doc*/public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {if (enable) {sendMessage(CMD_START_AP, wifiConfig);} else {sendMessage(CMD_STOP_AP);}}

WifiStateMachine::InitialState会处理该消息:

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;

首先肯定是先加载驱动,驱动加载成功后通过enableSoftAp()配置Wifi热点:

    /* 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;}

Wifi热点首先需要绑定端口信息,再以AP模式通过NetworkManagementService在wlan0端口下加载固件;同时热点功能也需要HAL层的支持。

setWifiApState()会发送广播,告知当前热点打开的过程信息;同理,也有setWifiState(),告知外界当前Wifi打开的过程信息;如果我们有必要知道当前热点打开的过程进行到什么阶段了,可以监听WifiManager.WIFI_AP_STATE_CHANGED_ACTION广播。最后状态切换到SoftApStartingState,如果流程有误,则会重新进入InitialState。
接着看SoftApStartingState::enter():

        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);}}

首先会判断打开热点时传入的WifiConfiguration对象是否为null;如果为空,则会向WifiApConfigStore发送CMD_REQUEST_AP_CONFIG消息,请求一个热点配置信息

。我们一起介绍这两个分支过程。回过头看InitialState状态的enter():

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 optionmHalBasedPnoEnableInDevSettings = true;}}

在创建完mWifiApConfigStore对象后,会调用mWifiApConfigStore.loadApConfiguration()加载热点配置信息:

    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) {}}}}

主要是从/misc/wifi/softap.conf文件中读取其中的信息,并赋给WifiApConfigStore的成员变量mWifiApConfig,这个变量保存的就是当前SoftAp的配置信息。该文件一开始会有默认的信息保存其中,如果我们从没配置过热点,拿到的就是系统默认的信息;如果,上层配置了热点;我们也会将新的配置信息更新到softap.conf中,以供下载再次加载。再看消息处理过程:

case WifiStateMachine.CMD_REQUEST_AP_CONFIG:mReplyChannel.replyToMessage(message,WifiStateMachine.CMD_RESPONSE_AP_CONFIG, mWifiApConfig);

向WifiStateMachine回复CMD_RESPONSE_AP_CONFIG消息,并附带mWifiApConfig对象。在SoftApStartingState::enter()中,如果config不为空,我们直接去调用startSoftApWithConfig()启动SoftAP;如果一开始config为null,通过处理CMD_RESPONSE_AP_CONFIG,获取到新的config对象,也应该去开启SoftAP了:

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更新配置信息,看处理流程:

    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);}

首先将传入的配置对象保存到mWifiApConfig,接着切换状态:

    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 progresscase WifiStateMachine.CMD_SET_AP_CONFIG:deferMessage(message);break;case WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED:transitionTo(mInactiveState);break;default:return NOT_HANDLED;}return HANDLED;}}

enter()函数中,会调用writeApConfiguration()将mWifiApConfig的信息更新到/misc/wifi/softap.conf文件中,供下次加载使用:

    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) {}}}}

处理比较简单,接着给自己发送CMD_SET_AP_CONFIG_COMPLETED消息,告知配置信息更新已经完毕,并重新进入InactiveState,重新等待下次配置信息的更新处理。

我们再返回到WifiStateMachine::SoftApStartingState处理CMD_RESPONSE_AP_CONFIG,如果再次获取后的config依然为null,则通知热点打开失败。接着就是真正开启热点的函数处理:

    /* 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 channelfinal 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 Hereif (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 5GHzsendMessage(CMD_START_AP_FAILURE, WifiManager.SAP_START_FAILURE_GENERAL);return;}}} else {if (config.apBand != 0) {//countrycode is mandatory for 5GHzLog.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 channelsendMessage(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//supportedconfig.apBand = 0;config.apChannel = 6;}}}} else {//for some old device, wifiHal may not be supportedconfig.apBand = 0;config.apChannel = 6;}// Start hostapd on a separate threadnew 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();}

如果最后热点打开成功,发送CMD_START_AP_SUCCESS,看处理过程,SoftApStartingState:

                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);//切换到初始状态

最终状态在SoftApStartedState:

    class SoftApStartedState extends State {@Overridepublic 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 pointbreak;// Fail client mode operation when soft AP is enabledcase 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:

public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) 

由第一部分的内容可知WifiController状态机在处理完SoftAp打开后,停在ApEnabledState状态,那么我们看它是怎么处理CMD_SET_AP的:

 case CMD_SET_AP:if (msg.arg1 == 0) {mWifiStateMachine.setHostApRunning(null, false);//在WifiStateMachine中开始热点关闭流程transitionTo(mApStaDisabledState);//切换到初始状态}break;

有前述可知,如果参数enabled为false,mag.arg1就应该为0,调用setHostApRunning()走关闭流程,并将WifiController中的状态重置为ApStaDisabledState,等待下一次流程的开始。看setHostApRunning():

    /*** TODO: doc*/public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {if (enable) {sendMessage(CMD_START_AP, wifiConfig);} else {sendMessage(CMD_STOP_AP);}}

发送CMD_STOP_AP消息;已知SoftAp成功打开后,WifiStateMachine停留在SoftApStartedState,看其处理:

                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);//切换到初始状态

首先,通过NetworkManagermentService关闭SoftAp,并发送广播通知SoftAp的状态改变;最后WifiStateMachine切换到InitialState:

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 optionmHalBasedPnoEnableInDevSettings = true;}}

停掉HAL层,卸载驱动;重新等待下一次Wifi/SoftAp的启动过程。到此,热点关闭的动作就结束了。

PS:
WifiManager中提供了两个关于SoftAp的操作函数:
1、设置SoftAP的配置信息

    /*** 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;}}

设置Wi-Fi AP的配置信息,它真正的处理过程是向WifiApConfigStore发送CMD_SET_AP_CONFIG消息,告知其要更新配置信息了。这一部分处理在第一部分已经分析过。

2、获取当前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;}}

它真正的处理过程是向WifiApConfigStore发送CMD_REQUEST_AP_CONFIG消息,请求WifiApConfigStore::mWifiApConfig成员,第一部分也已经说过,该变量保存的就是当前SoftAp正在使用的配置信息。

Android -- Wifi热点的打开与关闭流程简介相关推荐

  1. 【转】Android 设置Wifi热点、打开与关闭的监听

    原文地址:http://blog.csdn.net/u011520181/article/details/46496377 用过360的面对面快传,快牙的朋友应该都知道,它们在两台设备间实现文件传输都 ...

  2. Android 设置Wifi热点、打开与关闭的监听

    用过360的面对面快传,快牙的朋友应该都知道,它们在两台设备间实现文件传输都是通过WiFi热点实现的,下面我们就来探讨一下如何设置热点名和密码,并自动创建一个wifi热点吧,以及如何监听热点的打开与关 ...

  3. android wifi热点广播,Android WiFi热点开发的示例代码

    上次写了Android连接匿名WiFi的内容.WiFI开发对于应用层开发是比较小众的知识点,不过既然用到了就在此记录下. 创建热点 1.根据加密类型.密码.是否隐藏等参数来创建热点 static Wi ...

  4. android wifi热点的创建以及连接通信(华为T8951 Google GALAXY Nexus 测试通过)

                   参考网上的很多的资料,初步实现了wifi热点的创建.连接以及聊天通信,以下是网上广为流传的Wifi 三种配置: public WifiConfiguration crea ...

  5. Android WiFi热点完全研究(自定义创建、跳转系统界面设置、读取配置、切换,Android6.0适配)...

    前言: WiFi热点设置页面的安全性选项在Android 4.x上有"无"."WPA PSK"."WPA2 PSK"三个选项,在Androi ...

  6. android wifi热点setting

    目录 一.wifi原生setting的入口在WifiTetherSettings.java 二.改热点Setting的时候可以直接只编译Settings.apk,并且替换, 三.wifi热点貌似官方支 ...

  7. android 代理 wifi热点,android wifi热点默认网关

    原贴:https://blog.csdn.net/jingzitakk66/article/details/89146696 项目需求,android端创建热点,电脑端连接此热点后用socket实现数 ...

  8. android wifi热点项目总结,高通Android wifi移植和wifi热点问题总结

    由于开发环境和系统的差异,wifi移植,包括wifi热点开启时有时还会出现其他问题,我这里先总结下: 1.netd问题 Netd 就是Network Daemon 的缩写,表示Network守护进程​ ...

  9. 没有无线网卡的服务器开wifi,电脑没无线网卡怎么开wifi热点 160wifi一键上网方法流程...

    160wifi一键上网方法流程: 现在市面上,无论是台式机还是笔记本,都带有内置无线网卡,但其利用率却不高,一台机器的无线网卡,只能为一台机器提供无线wifi网络.其实你的内置无线网卡,比你想象的更有 ...

最新文章

  1. 【转】“无法在Web服务器上启动调试。您不具备调试此应用程序的权限,此项目的URL位于Internet区域”错误提示的解决...
  2. idea中applicationContext-dao.xml文件中Cannot resolve file***** :spring xml model validation问题
  3. tomcat命令无法启动 the catalina_home environment variable is not defined correctly this environment variab
  4. 会话跟踪之Session
  5. elasticsearch】org.elasticsearch.bootstrap.StartupException: java.lang.NullPointerException
  6. 阿里资深技术专家:35岁IT职场人的8个经验总结!
  7. A4张的尺寸大小是多大?像素又是多少呢?
  8. VirtualBox如何添加ISO文件
  9. java 读取文件inputstream_使用Inputstream读取文件
  10. 转载:欧姆社 漫画学习统计学
  11. ubuntu安装discourse论坛----结合在apache服务上建立虚拟主机
  12. 双线性插值算法的详细总结
  13. 复旦陈果老师关于孤独、寂寞、朋友和人际的课堂笔记
  14. XenApp应用虚拟化介绍
  15. 【啃书】【阿里云天池大赛赛题解析】目录
  16. 隐马尔科夫模型(HMM)浅见
  17. android 床头闹钟,史上最强"叫床"闹钟!让你再也睡不着啦~~(支持 Android)
  18. Mac 编译运行ET6.0Demo
  19. Stimulsoft Forms.WEB 23.2.6 Crack
  20. html5第十一课时,汇总

热门文章

  1. Python异常名称、警告
  2. 英语专业有必要学python吗-我英语不好,能学会编程吗?
  3. Presto、Spark 和 Hive 即席查询性能对比
  4. Java手机号码校验工具类
  5. 深入理解设计模式-单例模式(饿汉单例模式、懒汉单例模式、双锁单例模式)
  6. 英特尔又做了个“违背祖宗的决定”
  7. 学习计算机需要要了解与掌握的计算机基础
  8. 拼多多店群AB链玩法,无货源店群全套玩法培训
  9. Windows Sockets网络编程(5)完成端口模型(IOCP)
  10. word2vec最全理论和代码