Android WiFi — Ap功能实现与源码分析
0. 前言
??WiFi热点通常说的是wifiAp相关,如果是源码开发的话,这个WifiAp算是一个搜索代码的关键字,含义是Wifi Access point,wifi接入点。所以下文中的wifi热点统一用wifiAp代替。
- wifiAp打开方式: 设置 → 更多 → 移动网络共享 → 便携式wlan热点。
- wifiAp打开条件: 任何情况下均可。只是有内网外网之分。造成内外网之分的影响条件有sim卡和wifi的连接状态。注意,这里所说的是wifi的连接状态,而不是wifi热点的连接状态
- wifiAp开发中用处: 可用于局域网内的通信
- wifiAp开发中相关问题:
- 跟WiFiAp相关的有wifiAp的网关Ip,以及ip范围
- wifiAp的config:包括初始创建时的defaultvalue:名字(ssid)和密码(preSharedKey),以及后续修改config
- wifiAp的enable状态
- wifiAp的设备连接列表:一是保证能获取到当前连接设备列表,二是当有设备连接时能够实时的更新
- wifiAp的连接限制:包括最大连接数限制,以及黑白名单机制
1. wifiAp的ip
??既然是要局域网内通信,那就要用到ip地址和端口号了(关于端口号的设定属于开发通信时的问题,是用户自定义的可变的,在我的程序里我规定端口号为80。而ip地址是有规定的,所以只讲关于ip的问题)。ip地址是在Android源码中规定好的,平常所买的路由器的ip地址一般都是192.168.0.1。Android源码中所规定的手机的wifiAp的ip地址为192.168.43.1,这个代码中可以看到
- 创建wifiAp时的ip:在创建wifiAp时相当于网关ip,/frameworks/opt/net/wifi/service/java/com/android/server/wifi/SoftApManager.java中开启wifiAp时规定了ip地址(Android 7.0中在该文件中,如果是其他Android系统(Android 4.4)可以在WifiStateMachine),具体方法在startThering中
- wifiAp的ip地址的分配区间:在/frameworks/base/services/core/java/com/android/server/connectivity/Tethering.java中有规定
private String[] mDhcpRange;
private static final int TETHER_RETRY_UPSTREAM_LIMIT = 5;
private static final String[] DHCP_DEFAULT_RANGE = {
"192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
"192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
"192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
"192.168.48.2", "192.168.48.254",
};
2. WifiAp的config分析
2.1 默认的config
- 代码位置
在恢复出厂设置后打开WifiAp,初始的wifiAp的名称是一定的,但是wifiAp的密码是随机,这个可以自行测试,实现代码位于一个叫做 WifiApConfigStore.java的文件中,文件路径为/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiApConfigStore.java - 代码实现
private WifiConfiguration getDefaultApConfiguration() {
WifiConfiguration config = new WifiConfiguration();
config.SSID = mContext.getResources().getString(
R.string.wifi_tether_configure_ssid_default);
config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
String randomUUID = UUID.randomUUID().toString();
config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
return config;
}
}
2.2 修改wifiAp的config配置流程
??如果想要修改wifiAp的config配置需要注意,在修改config时,config会直接设置下去,但是并不会立即生效,必须要重启wifiAp之后才有效。这个可以先拿自己的手机演示确认。
-
首先获取到wifiManager对象 WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-
然后获取到config对象 WifiConfiguration config = wifiManager.getWifiApConfiguration();
-
有了config之后,就可以对参数进行设置了,比如设置用户名和密码 if (config != null) {
config.SSID = mWifiInfoBean.getApSsid();
config.preSharedKey = mWifiInfoBean.getPskKey();
}
当然,你还可以做其他设置,具体的可以参考WifiConfiguration.java源码,到这一步,对于wifiAp的用户名和密码已经设置成功了,此时若手动重启wifiAp后config即可生效。如果你想要立刻生效,那就必须要重启wifiAp了。 -
重启wifiAp,将所设置的config设置进去,并重启热点,流程是首先判断WiFi热点是否处于开启状态,如果是,则重启wifiAp。如果当前wifiAp不处于开启状态,则只需要把config设置下去即可 if (wifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {
wifiManager.setWifiApEnabled(null, false);
return wifiManager.setWifiApEnabled(config, true);
} else {
return wifiManager.setWifiApConfiguration(config);
}
3. 开启/关闭WifiAp热点状态流程
??在对wifiAp进行config修改时已经涉及到了对于wifiAp的开和关,在进行wifiAp进行开关的过程中需要传入config,如果传入的为null,则沿用上一次的 config,如果上一次的config不存在,则会去加载默认的config。当开启wifiAp时会先去判断wifi的状态,如果wifi处于开启状态则需要关闭WiFi状态,然后开启wifiAp。
-
获取WiFimanager对象(参考上文) -
判断目前wififAp的开关状态,如果处于开启状态,则不进行任何操作。当然,如果你想自己设置config,那么就照着上文中配置config的步骤来
if (wifiManager.isWifiApEnabled()) {
return true;
}
-
判断wifi的状态,如果处于开启状态,则关闭wifi状态
int wifiState = wifiManager.getWifiState();
if (enable && ((wifiState == WifiManager.WIFI_STATE_ENABLING) ||
(wifiState == WifiManager.WIFI_STATE_ENABLED))) {
wifiManager.setWifiEnabled(false);
}
-
接下来就可以调用wifiAp开启的方法了 wifiManager.setWifiApEnabled(null, enable);
4. 已连接设备列表
4.1 读取wifiAp的已连接设备列表
??这个很纠结,关于wifiAp的这些东西不存在什么jni接口,只能是通过读文件或者是监听广播来和底层通信。Android源码中提供了一个读取已连接设别列表的方法——读取特定文件“/proc/net/arp” 来获取已连接设备信息。
File file = new File("/proc/net/arp");
try {
reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
String[] tokens = line.split("[ ]+");
if (tokens.length < 6 || tokens[3].length() < 8) {
continue;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
??该文件包含的数据有sscanf(buf, “%s 0x%x 0x%x %s %s %s\n”, ip, &h_type, &flag, hw_addr, mask, dev ) ??也就是说tokens 长度为6,可以看到包含已连接设备的ip和addr,但是设备名却没有说明,这个需要自己根据mac地址来获取对应的厂商和设备名。当然,方案提供商也许自己会集成这部分工作,所以具体情况具体考虑
4.2 设备列表实时更新
??这个目前Android源码中也没提供任何解决方案,如果是系统开发的,可以在设备连接时加个广播,当有设备连接成功后发送广播,然后上层应用可以通过监听广播来实时更新设备列表。
4.3 设备连接限制
??设备连接限制包括最大连接数,以及黑白名单。我只能说目前上层是没有直接可以调用的接口来实现。目前大致只能通过调用adb shell命令来实现了。(如果平台支持的话)
5. 源码分析
??文中上半部分介绍了wifiAp相关的功能开发,接下来就从源码的角度出发,分析为什么我们可以用这种方式来实现wifiAp的功能
??关于Android的WiFiAp的源码研究基于andriod 4.4
5.1 WifiAp始于UI
??wifiAp代码处于package/apps/Settings中,wifiAp开启的入口在 /packages/apps/Settings/src/com/android/settings/TetherSettings.java中的onCreateDialog。在TetherSettings中包括蓝牙热点,WiFi热点,usb热点的相关问题。
@Override
public Dialog onCreateDialog(int id) {
if (id == DIALOG_AP_SETTINGS) {
final Activity activity = getActivity();
mDialog = new WifiApDialog(activity, this, mWifiConfig);
return mDialog;
}
return null;
}
??wifiAp的设置弹出框为WifiAPDialog,目录为: /packages/apps/Settings/src/com/android/settings/wifi/WifiApDialog.java ??WiFiAp的设置框所加载的xml布局文件为wifi_ap_dialog.xml。wifiAp的设置包括四部分:
- wifi_ssid:wifiAp的名称:输入长度最大限制为32个字符
- wifi_security:wifiAp的安全性:提供spinner列表进行选择,可选项如下
- wifi_password:wifiAp的密码,最大长度限制为63
- wifi_ap_band_config:wifiAp的Ap频段
频段有spinner列表可选,频段可选为2.4g和5g。该array是在WifiApDialog代码中添加的(Android 4.4原生SDK不支持频段选择,需要扩展功能)
??所以,如果想要修改wifiApDialog布局相关的可以修改wifi_ap_dialog.xml布局文件。由布局文件也可以看出,Android源码上层中,wifiAp相关的配置 WifiConfiguration包括四部分,用户名、密码 、安全性、频段(扩展功能)。
5.2 WifiConfiguration配置
??在创建WifiApDialog时会传入一个WifiConfiguration对象,wifiApDialog中显示的WiFiAp信息就是从该config中获取的。在第一次开启wifiAp对象时所获取的config对像是系统默认的配置,当用户进行了修改之后wifiAp的config会被保存到手机,等下次获取到的就是修改后的config。
5.2.1 获取wifiConfig
??先来找到创建dialog的地方来看一下config对象,来看一下代码是如何在第一次使用时获取系统默认以及在修改后如何获取用户修改的config的:
private void initWifiTethering() {
final Activity activity = getActivity();
if (mWifiManager == null)
mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
mWifiConfig = mWifiManager.getWifiApConfiguration();
mSecurityType = getResources().getStringArray(R.array.wifi_ap_security);
mCreateNetwork = findPreference(WIFI_AP_SSID_AND_SECURITY);
if (mWifiConfig == null) {
final String s = activity.getString(
com.android.internal.R.string.wifi_tether_configure_ssid_default);
mCreateNetwork.setSummary(String.format(activity.getString(CONFIG_SUBTEXT),
s, mSecurityType[WifiApDialog.OPEN_INDEX]));
} else {
int index = WifiApDialog.getSecurityTypeIndex(mWifiConfig);
mCreateNetwork.setSummary(String.format(activity.getString(CONFIG_SUBTEXT),
mWifiConfig.SSID,
mSecurityType[index]));
}
}
??wifiAp的config对象是在TetherSettings的initWifiTethering的方法中获取的。可以看到,mWifiConfig对像通过wifiManager调用getWifiApConfiguration()来获取,当然,源码设计有套路,manager只是client的一个中转站,真正的还是找的是service,所以找到WifiServiceImpl.java,紧接着是WifiStateMachine.java,一级一级的都是调用,最终的实现在WifiApConfigStore.java文件中,该文件包含了系统默认的config以及用户设置的config。
从这个代码可以看出两个信息
- 第一,wifiAp的config信息存储在文件中
- 第二,优先加载文件中存储的config信息。其中loadApConfiguration用于从文件中加载wifiAp的配置信息,如果所加载的config为null—-即表示用户未对wifiAp进行过信息设置,则会去调用getDefaultApConfiguration来获取系统的默认设置,并且将获取到的config写入到存储wifiAp的config的文件
??总结来说就是当wifiManager想要获取config时,会先加载文件中所保存的config信息,如果config信息从未进行过保存,则获取默认的config,并且将config写入到文件中去。 ??config文件保存目录在wifiApConfigStore中已经声明了,位于data目录下:
5.2.2 设置wifiConfig
??WifiApDialog弹窗可以修改WiFi的配置信息,按下确定按钮即可保存,接下来看一下对config的保存设置。 ??对于dialog的确认按钮的点击事件是在TetherSettings.java中处理的 Androd 4.4修改设置后自动重启AP:
public void onClick(DialogInterface dialogInterface, int button) {
if (button == DialogInterface.BUTTON_POSITIVE) {
mWifiConfig = mDialog.getConfig();
if (mWifiConfig != null) {
if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {
mWifiManager.setWifiApEnabled(null, false);
mWifiManager.setWifiApEnabled(mWifiConfig, true);
} else {
mWifiManager.setWifiApEnabled(null, false);
mWifiManager.setWifiApConfiguration(mWifiConfig);
}
int index = WifiApDialog.getSecurityTypeIndex(mWifiConfig);
mCreateNetwork.setSummary(String.format(getActivity().getString(CONFIG_SUBTEXT),
mWifiConfig.SSID,
mSecurityType[index]));
}
}
}
Android 7.1.1 SDK源码: 这段代码做了以下操作
- 获取到dialog中填写的用户名、密码、加密方式、频段这些WiFiap的config:
mDialog.getConfig() - 如果获取到的config不为null,则将wifiAp的config保存起来:
mWifiManager.setWifiApConfiguration(mWifiConfig),和get时类似,该方法是一路往下调用 WifiManager->WifiServiceImpl->WifiStateMachine->WifiApConfigStore,最终的实现就是在WifiApConfigStore中进行将config写入到文件。config是要下一次开启wifiAp时才会生效,所以此时如果wifiAp处于开启状态,则关闭wifiAp。 - 注意,从这里也可以看到,Android 7.1.1源码的实现是在修改wifiAp的config之后,会将ap关闭,并不会自动重启。 在这里关闭wifiAp调用的是ConnectivityManager的实例方法:mCm.stopTethering(int type),该方法经过ConnectivityManager→ConnectivityService→Tethering.java,最终是在Tethering中的stopTethering进行实现
Android 4.4 的定制SDK通过调用wifimanager封装的setWifiApEnabled函数,实现自动重启生效
??基本上config的设置和获取就这些了。大致分析完成之后,也可以看到WifiAP相关的类主要有这么几个
-
WifiApDialog.java:用户交互界面,直观呈现出wifiAp的配置信息,提供用户修改config的ui交互,继承自AlertDialog,在构造该dialog对象时会传入DialogInterface.OnClickListener和WifiConfiguration,所以也可以看出按钮点击事件的处理以及所显示的config内容信息都是在创建dialog时获取的,所以总结下来,该类其实就做了两件事
- 把所获取的config加载出来
- 提供编辑框供user编辑
其他对于config的read&write一律不进行处理。代码目录为: /packages/apps/Settings/src/com/android/settings/wifi/WifiApDialog.java -
TetherSettings.java:用户交互界面,呈现手机所支持的便携式热点的开关交互,代码目录为: /packages/apps/Settings/src/com/android/settings/TetherSettings.java -
Tethering.java:逻辑实现类,该类中拥有很多业务处理逻辑来支持Android设备作为BT\USB\WIFI作为网关,即设备作为便携式热点代码的业务逻辑实现。该类中包含网络共享和便携式热点信息,即
- bluetooth_tethering:蓝牙网络共享,涉及到BluetoothPan协议
- usb_tethering:usb网络共享,涉及到设备连接usb时状态切换,即是否是充当大容量存储设备
- wifiAp便携式热点
代码中对这三种模式的开关状态进行了监听以及更新。代码目录为: /frameworks/base/services/core/java/com/android/server/connectivity/Tethering.java -
WifiManager.java :该类提供了管理wifi连接的主要的api接口,这里所说的wifi连接包括WiFiAp和WiFi。三方应用开发者在对wifiap进行相关的操作时可以调用wifiManager类下的接口。developer需要注意的是在获取wifiManager对象时必是要应用程序的context,以防止memory leaks内存泄漏。代码目录为: /frameworks/base/wifi/java/android/net/wifi/WifiManager.java .wifimanager相关的有以下几种情况
- list of configured network:已经配置过的网络列表,即手机中以保存的 WiFi列表,对已经配置过的wifi可以进行增删改查的操作viewed、update、modify
- current active wifi:当前正在运行的WiFi,即可用WiFi列表。列表中的wifi接入点access point 可以连接或者是断开连接
- result of access point scans:wifi接入点扫描结果,包含足够的信息来决定连接哪一个WiFi热点
- 定义了当wifi状态发生改变时所要发送的广播
-
WifiServiceImpl.java :作为一个binder代理形式的存在,衔接binder的client和server,主要是中间人的作用,该类不对三方应用开发者开放,不存在sdk中。代码目录为: /frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java -
WifiStateMachine.java :顾名思义,状态机,用来监测WiFi的各种连接状态。代码目录为: /frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java -
WifiApConfigStore.java :这个也很显然,用于wifiAp的config信息的存取即reading&writing,大部分的代码在文中已经分析过,所以不再分析。代码目录为: /frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiApConfigStore.java
5.3 wifiAp设备连接
??wifi设备连接有一个息息相关的类NativeDaemonConnector.java,具体了解可以参考Android NativeDaemonConnector源码解析。
5.4 wifiAp打开流程
先大致说一下追的流程:如下,
- WifiManager.java中setWifiApEnabled调用service方法
- WifiServiceImpl.java中setWifiApEnabled借助controller发送message,msg.what=com.android.server.wifi.WifiController.CMD_SET_AP;
- WifiController.java中ApStaDisabledState的processMessage去处理CMD_SET_AP的msg,并触发mWifiStateMachine.setHostApRunning
- WifiStateMachine.java中发送msg,msg.what=CMD_START_AP,并在该类中的SoftApState的enter的方法中处理msg:调用 mSoftApManager.start(config);
- SoftApManager.java中IdleState的processmessage处理,调用startSoftAp、紧接着调用 mNmService.startAccessPoint
- NetworkManagementService.java中 executeOrLogWithMessage执行开启wifiap的命令
5.4.1 WifiManager
??由上文可知,WifiManager是Android源码提供给应用开发者使用的,提供API接口。如果上层应用想要打开wifiAp,那么就需要调用wifiManager的api→setWifiApEnabled(),那么该方法具体做了什么呢??
@SystemApi
public boolean setWifiApEnabled(WifiConfiguration wifiConfig,
boolean enabled) {
try {
mService.setWifiApEnabled(wifiConfig, enabled);
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
??既然是走的service,那就找到service
5.4.2 WifiService
IWifiManager mService;
??可以看到这里用到了binder机制,service中方法实际实现是在继承字WifiManager.Stub的类中,所以找到所需要的类:
public final class WifiService extends IWifiManager.Stub
??也就是说service所对应的代理类为WifiService,所以去看该类中的具体方法实现
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.");
}
if (wifiConfig == null || isValid(wifiConfig)) {
mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
} else {
Slog.e(TAG, "Invalid WifiConfiguration");
}
}
??先是进行一系列的权限判断,在允许的条件下发送msg,可以看到,所发送的message携带的信息有:
- msg.what : CMD_SET_AP
- msg.arg1:如果是开启ap则为1,如果是关闭ap则为0
- msg.arg2:传入为0
- msg.obj:WifiConfiguration对象
??到这里,WifiServiceImpl的任务就完成了,接下里就是WifiController来处理了
5.4.3 WifiController
??WifiController继承与StateMachine状态机,用来管理各种操作(airplane,WiFi hotspot)在wifiStateMachine中的on/off状态。 既然是状态机,那么会有一个特点,一旦注册了状态处理,那么就会按照所添加的状态类去顺序执行。 ??在StateMachine中有一个方法,叫做addState,用于添加状态:
public void addState(State state) {}
........
public void addState(State fromState, State toState) {}
??状态机默认的是线性模型,即按照add(State)的顺序执行,但如果使用了addState(fromState, toState),那么就相当与指明了状态机的执行顺序。 ??关于状态机的介绍就是后话了,接下来看接受到msg后的wifiController的处理:wifiController总结起来就做了两件事
-
保存wifiAp的开关状态: mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
该代码用于向settingsdb中存储wifiAp的状态,所存储的字段为: Settings.Global.WIFI_SAVED_STATE -
并执行wifiAp的开关操作: mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
true);
该代码用于进行wifiAp的开关操作,调用的是WifiStateMachine的setHostApRunning方法,并将wifiConfiguration传给WifiStateMachine,当然这里如果是要关闭ap传入的boolean值为false
??所以可以看到wifiController只是起一个当状态改变时传递msg的作用,接下来进入到WifiStateMachine中
5.4.4 WifiStateMachine
??WifiStateMachine继承自StateMachine,该类用于跟踪WiFi的连接状态,所有的事件处理都在这里,所有连接状态的改变也是在这里进行的初始化。Android7.1.1所支持的WiFi操作包括三种:
- Clients:设备作为客户端连接其他wifi
- p2p:wifi直连
- softAp: wifi热点
??目前WiFiStateMachine用于处理wifi作为Clients以及WiFi作为softAp,而p2p则交由WifiP2pService进行处理。 ??接下来直接进去到setHostApRunning方法:
public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
if (enable) {
sendMessage(CMD_START_AP, wifiConfig);
} else {
sendMessage(CMD_STOP_AP);
}
}
??很明显,该方法也是sendmsg,只不过这个msg是在WifiStateMachine这个类中自己处理的,此时Android 4.4与Android 7.1.1又有所区别。
Android 4.4: 开启AP:判断是否loaddriver,若为true,setWifiApState enable,之后状态转换transitionTo(mSoftApStartingState); 在SoftApStartingState中若为CMD_START_AP,则会调用startSoftApWithConfig(config); startSoftApWithConfig函数实现如下: 同理,关闭ap: 从定义我们可以看出mUntetheringState为 SoftApStartedState 类型 继续找到SoftApStartedState类
Android 7.1.1: 可以看到从此时开始,start/stop wifiAp的msg.what开始不同,而不是仅仅依靠boolean值来区分,因为如果是start的话,需要进行两步的处理,包括
- 加载softAp的hal:setupDriverForSoftAp
- 在保证hal加载完成的情况下将要进行的操作以及config传递给softAp,让其开始wifiAp: mSoftApManager.start(config)
??而如果是stop的话,则只需要将wifiAp关闭即可,即调用 mSoftApManager.stop()。接下来就是SoftApManager中的start和stop了
5.4.5 SoftApManager(Android 7.1.1)
??因为Android 4.4 为公司代码,涉及许多定制改动,因此这里改为Android 7.1.1 的流程继续细致分析,后面大体流程类似。
??start和stop对比分析
public void start(WifiConfiguration config) {
mStateMachine.sendMessage(SoftApStateMachine.CMD_START, config);
}
public void stop() {
mStateMachine.sendMessage(SoftApStateMachine.CMD_STOP);
}
??可以看到SoftApManager的start和stop只是send了msg
startAp
??start时send的msg为
- msg.what : SoftApStateMachine.CMD_START
- msg.arg1:config对象,这个值就是从wifiApDialog传过来的,在传递过程中如果有为null的情况就加载默认的或者文件中存储的,具体可参见上文。但是从传到这里开始,config就不会再做任何的修改,所以如果config有为null的情况,则返回
??在接收到CMD_START这个msg之后,SoftApManager最终会在startSoftAp方法中进行处理:
private int startSoftAp(WifiConfiguration config) {
if (config == null) {
Log.e(TAG, "Unable to start soft AP without configuration");
return ERROR_GENERIC;
}
WifiConfiguration localConfig = new WifiConfiguration(config);
int result = ApConfigUtil.updateApChannelConfig(
mWifiNative, mCountryCode, mAllowed2GChannels, localConfig);
if (result != SUCCESS) {
Log.e(TAG, "Failed to update AP band and channel");
return result;
}
if (mCountryCode != null) {
if (!mWifiNative.setCountryCodeHal(mCountryCode.toUpperCase(Locale.ROOT))&& config.apBand == WifiConfiguration.AP_BAND_5GHZ) {
Log.e(TAG, "Failed to set country code, required for setting up " + "soft ap in 5GHz");
return ERROR_GENERIC;
}
}
try {
mNmService.startAccessPoint(localConfig, mInterfaceName);
} catch (Exception e) {
Log.e(TAG, "Exception in starting soft AP: " + e);
return ERROR_GENERIC;
}
Log.d(TAG, "Soft AP is started");
return SUCCESS;
}
??开启wifiAp接着会去调用mNmService.startAccessPoint:方法的实现在NetworkManagementService.java中,内容如下
@Override
public void startAccessPoint(WifiConfiguration wifiConfig, String wlanIface) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
Object[] args;
String logMsg = "startAccessPoint Error setting up softap";
try {
if (wifiConfig == null) {
args = new Object[] {"set", wlanIface};
} else {
args = new Object[] {"set", wlanIface, wifiConfig.SSID,
"broadcast",Integer.toString(wifiConfig.apChannel),getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey)};
}
executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,SOFT_AP_COMMAND_SUCCESS, logMsg);
logMsg = "startAccessPoint Error starting softap";
args = new Object[] {"startap"};
executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,SOFT_AP_COMMAND_SUCCESS, logMsg);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
??该方法首先是拼接command字符串,并调用方法去执行命令,executeOrLogWithMessage方法是NetworkManagementService的private方法,其实就是利用NativeDaemonConnector这个runnable对象来执行command命令
private void executeOrLogWithMessage(String command, Object[] args,int expectedResponseCode, String expectedResponseMessage, String logMsg) throws NativeDaemonConnectorException {
NativeDaemonEvent event = mConnector.execute(command, args);
if (event.getCode() != expectedResponseCode || !event.getMessage().equals(expectedResponseMessage)) {
Log.e(TAG, logMsg + ": event = " + event);
}
}
??可以看到,构造了个NativeDaemonConnector–mConnector用于执行命令,先看命令执行的传入参数arguments:
-
String command : SOFT_AP_COMMAND = “softap” :要执行的command -
Object[] args:command的附加参数。这个命令稍微有一点复杂,会根据所传入的config是否为null而有所不同: if (wifiConfig == null) {
args = new Object[] {"set", wlanIface};
} else {
args = new Object[] {"set", wlanIface, wifiConfig.SSID,"broadcast", Integer.toString(wifiConfig.apChannel), getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey)};
}
-
wlanIface的取值: 首先这里的wlanIface是在构建softApManager对象时借助WifiNative对象获取的,WifiNative中获取wlanIface的地方位于: private static WifiNative wlanNativeInterface = new WifiNative(SystemProperties.get("wifi.interface", "wlan0"), true);
很显然,wlanIface的值取决于属性字段wifi.interface的对应值,可以看到,如果未定义即默认取值为wlan0,源码中设置的也是wlan0. -
preSharedKey:指的是wifiAp的密码,之所以列出来是因为源码用一层类SensitiveArg将他包装了起来,该类的作用就是告诉开发者:该字段属于敏感内容,禁止使用log打印出来,该类所重写的toString方法也是将构造时传入的obj对象转换成string输出。 -
apChannel:定义如下
public int apChannel = 0;
channel是根据wifiConfig所配置的频段(2.4g或者是5.0,默认是2.4g)来决定的 -
securitytype:wifiAp的安全保密类型: 经过对以上问题的分析,可以看出args的取值如下: if(config == null){
args = new Object[] {"set","wlan0"};
}else{
args = new Object[] {"set","wlan0","YourNetwork name","broadcast","your network apchannel","your network security type","your network password"}
}
-
int expectedResponseCode:执行结果期望值(int),即执行成功时的响应。NetdResponseCode.SoftapStatusResult = 214: -
String expectedResponseMessage :执行结果期望值(string类型) SOFT_AP_COMMAND_SUCCESS = “Ok” -
String logMsg:如果命令执行失败,会打印该log: String logMsg = "startAccessPoint Error setting up softap";
??接下来看一下execute命令的对象—–mConnector对象: 在NetworkManagementService的构造时会构造mConnector对象
mConnector = new NativeDaemonConnector(new NetdCallbackReceiver(), socket, 10, NETD_TAG,160,wl,FgThread.get().getLooper());
传入参数有7个
-
INativeDaemonConnectorCallbacks callbacks执行结果回调 -
String socket:关于命令的执行都是借助socket的输出流进行处理的,在创建networkManagerService时会声明,值为: String NETD_SERVICE_NAME = "netd";
-
int responseQueueSize:响应队列的大小(message queue,looper) -
String logTag -
int maxLogSize -
PowerManager.WakeLock wl:在这里,传入的值为null,因为不再需要唤醒锁。 -
Looper looper:使消息借助handler循环处理
??接下里就是execute方法,最终会是去调用NativeDeamonConnector中的executeForList(long timeoutMs, String cmd, Object… args)方法进行处理,如下,可以看到executeForList方法会返回一个event的列表,而execute方法只返回列表的第一个event元素
public NativeDaemonEvent[] executeForList(long timeout, String cmd, Object... args) throws NativeDaemonConnectorException {
final long startTime = SystemClock.elapsedRealtime();
final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
final StringBuilder rawBuilder = new StringBuilder();
final StringBuilder logBuilder = new StringBuilder();
final int sequenceNumber = mSequenceNumber.incrementAndGet();
makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
final String rawCmd = rawBuilder.toString();
final String logCmd = logBuilder.toString();
log("SND -> {" + logCmd + "}");
synchronized (mDaemonLock) {
if (mOutputStream == null) {
throw new NativeDaemonConnectorException("missing output stream");
} else {
try {
mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new NativeDaemonConnectorException("problem sending command", e);
}
}
}
NativeDaemonEvent event = null;
do {
event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
if (event == null) {
loge("timed-out waiting for response to " + logCmd);
throw new NativeDaemonTimeoutException(logCmd, event);
}
log("RMV <- {" + event + "}");
events.add(event);
} while (event.isClassContinue());
final long endTime = SystemClock.elapsedRealtime();
if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
}
if (event.isClassClientError()) {
throw new NativeDaemonArgumentException(logCmd, event);
}
if (event.isClassServerError()) {
throw new NativeDaemonFailureException(logCmd, event);
}
return events.toArray(new NativeDaemonEvent[events.size()]);
}
stopAp
??而stop时send的msg的信息为
- msg.what : SoftApStateMachine.CMD_STOP
??对于msg的处理也是在SoftApManager中,
private void stopSoftAp() {
try {
mNmService.stopAccessPoint(mInterfaceName);
} catch (Exception e) {
Log.e(TAG, "Exception in stopping soft AP: " + e);
return;
}
Log.d(TAG, "Soft AP is stopped");
}
??同样,也是调用NetworkManagerMentService中的方法进行处理,分析基本类似startAccessPonint,传入的cmd与start一致,只不过arguments不同,stop时传入的args为:
Object[] args = {"stopap"};
??请求错误时的logmsg为:
String logMsg = "stopAccessPoint Error stopping softap";
??执行命令后要去重新加载wifi firmware,即切换了wifi的模式到sta.(wifi总共有三种模式ap,sta,p2p)
|