Android热点SoftAP使用方式
一、背景
最近项目中Android设备需要获取SoftAP信息(wifi账号、密码、IP等),然后传递到投屏器中,那么如何获取到SoftAP信息呢?我们知道可以通过WifiManager类里的方法可以获取到,但是这个类里的很多方法都是@hide的,那么只能通过反射才可以,当然还需要项目具有系统权限,正好我们的APP是系统级APP已经具有了系统权限。
二、反射WifiManager的基本方法
2.1 获取WifiManager对象
我们可以先通过Context.WIFI_SERVICE获取到WifiManager对象
WifiManager mWifiManager = (WifiManager) mContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
然后反射里面各个方法
2.2 启动SoftAP
public boolean startSoftAp(){
try {
Method startSoftAp = mWifiManager.getClass().getMethod("startSoftAp", WifiConfiguration.class);
return (boolean) startSoftAp.invoke(mWifiManager,getAPConfig());
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
2.3 关闭SoftAP
public boolean stopSoftAp(){
try {
Method startSoftAp = mWifiManager.getClass().getMethod("stopSoftAp");
return (boolean) startSoftAp.invoke(mWifiManager);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
2.4 是否开启WifiAP
public boolean isWifiApEnabled() {
boolean apState = false;
try {
// @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
apState = (boolean) mWifiManager.getClass().getMethod("isWifiApEnabled").invoke(mWifiManager);
Log.i(TAG, "isWifiApEnabled :" + apState + "");
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
return apState;
}
2.5 获取WifiAP状态
public int getWifiApState() {
try {
// @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
return (int) mWifiManager.getClass().getMethod("getWifiApState").invoke(mWifiManager);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
return -1;
}
三、获取AP的信道、频段
要获取AP的信道、频段等,则是需要反射WifiConfiguration类里的方法
3.1 获取WifiConfiguration实例
public WifiConfiguration getAPConfig() {
try {
if (mSoftApConfiguration == null)
mSoftApConfiguration = (WifiConfiguration) mWifiManager.getClass()
.getMethod("getWifiApConfiguration").invoke(mWifiManager);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
return mSoftApConfiguration;
}
3.2 获取AP信道
public int getApChannel() {
WifiConfiguration wifiConfiguration = getAPConfig();
try {
Field apChannelField = wifiConfiguration.getClass().getDeclaredField("apChannel");
int apChannel = apChannelField.getInt(wifiConfiguration);
Log.d(TAG,"getApChannel->apChannel:"+apChannel);
return apChannel;
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
3.3 获取AP频段
public int getApFrequencyBand() {
WifiConfiguration wifiConfiguration = getAPConfig();
try {
Field apBandField = wifiConfiguration.getClass().getDeclaredField("apBand");
return apBandField.getInt(wifiConfiguration);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
3.4 获取SoftAP加密模式/方式
/**
* 获取Soft AP 加密模式
* @return
*/
public int getApEncryptionMode() {
WifiConfiguration wifiConfiguration = getAPConfig();
if (wifiConfiguration.allowedAuthAlgorithms.get(WifiConfiguration.KeyMgmt.NONE)) {
// 无加密
return WifiConfiguration.KeyMgmt.NONE;
} else if (wifiConfiguration.allowedAuthAlgorithms.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
// 加密方式为WPA_PSK
return WifiConfiguration.KeyMgmt.WPA_PSK;
} else if (wifiConfiguration.allowedAuthAlgorithms.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
// 加密方式为WPA_PSK
return WifiConfiguration.KeyMgmt.WPA_EAP;
}
return WifiConfiguration.KeyMgmt.NONE;
}
/**
* 获取Soft AP 加密方式
* @return
*/
public boolean isApEncryption() {
WifiConfiguration wifiConfiguration = getAPConfig();
if (wifiConfiguration.allowedAuthAlgorithms.get(WifiConfiguration.KeyMgmt.NONE)) {
// 无加密
return false;
} else if (wifiConfiguration.allowedAuthAlgorithms.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
// 加密方式为WPA_PSK
return true;
}
return false;
}
3.5 设置SoftAP 频段
public void setApFrequencyBand(int apBand){
try {
WifiConfiguration wifiConfiguration = getAPConfig();
Field apBandField = wifiConfiguration.getClass().getDeclaredField("apBand");
apBandField.setAccessible(true);
apBandField.set(wifiConfiguration, apBand);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
3.6 设置SoftAP 信道
public void setApChannel(int apChannel){
try {
WifiConfiguration wifiConfiguration = getAPConfig();
Field apChannelField = wifiConfiguration.getClass().getDeclaredField("apChannel");
apChannelField.setAccessible(true);
Log.i(TAG,"set mApChannel:"+apChannel+"");
apChannelField.set(wifiConfiguration, apChannel);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
四、获取AP的IP
我们adb shell进入到Android设备中执行ifconfig命令

我们可以看到我这个当前Android设备的AP的ip地址是192.168.124.202,我们想在代码中获取这个ip,还不能直接获取,因此需要通过广播才可以。
4.1 注册广播
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.net.wifi.WIFI_AP_STATE_CHANGED");
intentFilter.addAction("android.net.conn.TETHER_STATE_CHANGED");
getApplicationContext().registerReceiver(mReceiver, intentFilter);
4.2 实现广播
public static final int WIFI_AP_STATE_ENABLED = 13;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@RequiresApi(api = Build.VERSION_CODES.R)
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.e(TAG,"Receiver action:"+action);
if ("android.net.conn.TETHER_STATE_CHANGED".equals(action)){
if (mApInfoHelper.getWifiApState() == WIFI_AP_STATE_ENABLED) {
new Thread(() -> mApInfoHelper.getApIP()).start();
}
}
}
};
4.3 获取ap0的IP
public String getApIP() {
try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()){
NetworkInterface ni = networkInterfaces.nextElement();
if(ni.isUp() && !ni.isPointToPoint() && !ni.isLoopback() && ("ap0".equals(ni.getName()) || "softap0".equals(ni.getName()))){
List<InterfaceAddress> interfaceAddresses = ni.getInterfaceAddresses();
for (InterfaceAddress interfaceAddress : interfaceAddresses) {
if(interfaceAddress.getAddress() != null){
Log.d(TAG,"address:"+interfaceAddress.getAddress().toString());
if(interfaceAddress.getAddress().toString().contains("/192.168")){
String softApIP = interfaceAddress.getAddress().toString().substring(1);
Log.d(TAG,"getApIP:"+softApIP);
return softApIP;
}
}
}
}
}
} catch (SocketException e) {
throw new RuntimeException(e);
}
return null;
}
五、获取SoftAP频率
需要获取SoftAP频率,没有直接后去的接口,只能通过将信道转换成频率,这里正好在ScanResult类里面提供的有这个方法,但是Android的每个方法还不一样,在Android12和Android13都是convertChannelToFrequencyMhzIfSupported方法(地址:/packages/modules/Wifi/framework/java/android/net/wifi/ScanResult.java),在Android11是convertChannelToFrequencyMhz方法(地址:/frameworks/base/wifi/java/android/net/wifi/ScanResult.java),但是在Android10以下查看了ScanResult类,发现有没有这个方法,正好我们的设备也有一个Android9的那怎么兼容呢?


我通过对比Android11和Android13的convertChannelToFrequencyMhz方法和convertChannelToFrequencyMhzIfSupported方法可以发现高版本的比低版本兼容的代码越多,那我直接在本地自己按照最高版本的实现去实现不就解决所有版本了。
/** framework层的ScanResult类的变量 start **/
public static final int UNSPECIFIED = -1;
public static final int WIFI_BAND_24_GHZ = 1;
public static final int WIFI_BAND_5_GHZ = 2;
public static final int BAND_24_GHZ_FIRST_CH_NUM = 1;
public static final int BAND_24_GHZ_LAST_CH_NUM = 14;
public static final int BAND_24_GHZ_START_FREQ_MHZ = 2412;
public static final int BAND_5_GHZ_FIRST_CH_NUM = 32;
public static final int BAND_5_GHZ_LAST_CH_NUM = 177;
public static final int BAND_60_GHZ_FIRST_CH_NUM = 1;
public static final int BAND_6_GHZ_FIRST_CH_NUM = 1;
public static final int BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ = 5935;
public static final int BAND_5_GHZ_START_FREQ_MHZ = 5160;
public static final int BAND_6_GHZ_LAST_CH_NUM = 233;
public static final int BAND_6_GHZ_START_FREQ_MHZ = 5955;
public static final int BAND_60_GHZ_LAST_CH_NUM = 6;
public static final int BAND_60_GHZ_START_FREQ_MHZ = 58320;
/** framework层的ScanResult类的变量 end **/
/**
* 将信道转换成频率
* Android 11是ScanResult类的convertChannelToFrequencyMhz方法
* @link http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/wifi/java/android/net/wifi/ScanResult.java
*
* Android 13是ScanResult类的convertChannelToFrequencyMhzIfSupported方法
* @link http://aospxref.com/android-13.0.0_r3/xref/packages/modules/Wifi/framework/java/android/net/wifi/ScanResult.java
*
* @param channel 信道
* @param band
* @return 频率
*/
public int apChannelToFrequency(int channel,int band){
Log.d(TAG,"apChannelToFrequency-->apChannel:"+channel+",band:"+band);
/*ScanResult scanResult = new ScanResult();
Log.e(TAG,"apChannelToFrequency-->scanResult:"+scanResult);
try {
Method convertChannelToFrequencyMhz = scanResult.getClass().getMethod("convertChannelToFrequencyMhzIfSupported", int.class, int.class);
return (int) convertChannelToFrequencyMhz.invoke(scanResult,channel, (channel > 14 ? ScanResult.WIFI_BAND_5_GHZ : ScanResult.WIFI_BAND_24_GHZ));
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
Log.e(TAG,"apChannelToFrequency-->err:"+e.getMessage());
throw new RuntimeException(e);
}*/
if (band == ScanResult.WIFI_BAND_24_GHZ) {
// Special case
if (channel == 14) {
return 2484;
} else if (channel >= BAND_24_GHZ_FIRST_CH_NUM && channel <= BAND_24_GHZ_LAST_CH_NUM) {
return ((channel - BAND_24_GHZ_FIRST_CH_NUM) * 5) + BAND_24_GHZ_START_FREQ_MHZ;
} else {
return UNSPECIFIED;
}
}
if (band == ScanResult.WIFI_BAND_5_GHZ) {
if (channel >= BAND_5_GHZ_FIRST_CH_NUM && channel <= BAND_5_GHZ_LAST_CH_NUM) {
return ((channel - BAND_5_GHZ_FIRST_CH_NUM) * 5) + BAND_5_GHZ_START_FREQ_MHZ;
} else {
return UNSPECIFIED;
}
}
if (band == ScanResult.WIFI_BAND_6_GHZ) {
if (channel >= BAND_6_GHZ_FIRST_CH_NUM && channel <= BAND_6_GHZ_LAST_CH_NUM) {
if (channel == 2) {
return BAND_6_GHZ_OP_CLASS_136_CH_2_FREQ_MHZ;
}
return ((channel - BAND_6_GHZ_FIRST_CH_NUM) * 5) + BAND_6_GHZ_START_FREQ_MHZ;
} else {
return UNSPECIFIED;
}
}
if (band == ScanResult.WIFI_BAND_60_GHZ) {
if (channel >= BAND_60_GHZ_FIRST_CH_NUM && channel <= BAND_60_GHZ_LAST_CH_NUM) {
return ((channel - BAND_60_GHZ_FIRST_CH_NUM) * 2160) + BAND_60_GHZ_START_FREQ_MHZ;
} else {
return UNSPECIFIED;
}
}
return UNSPECIFIED;
}
六、监听SoftAP的连接状态
要想监听SoftAP的连接状态,则WifiManager类有个注册方法registerSoftApCallback方法,但是这个方法又是@hide的,好像使用反射不好整咋办呢?其实可以通过动态代理实现。


我们通过系统源码可以发现在Android10以上(不包含Android10)跟Android10以下registerSoftApCallback方法的参数是不一样的,因此需要做一下适配
private Object mSoftApCallback;
InvocationHandler softApCallbackHandler = (proxy, method, args) -> {
String methodName = method.getName();
if ("onStateChanged".equals(methodName)) {
int state = (int) args[0];
int failureReason = (int) args[1];
Log.d(TAG, "onStateChanged: state=" + state + ", failureReason=" + failureReason);
}else if("onNumClientsChanged".equals(methodName)){
int numClients = (int) args[0];
Log.d(TAG, "onNumClientsChanged: state=" + numClients);
}else if("onConnectedClientsChanged".equals(methodName)){
List clients = (List) args[0];
Log.e(TAG,"onConnectedClientsChanged->size:"+clients.size());
}
if(method.getReturnType() == int.class){
return 0;
}
return null;
};
@SuppressLint("PrivateApi")
public void registerSoftApCallback() throws RuntimeException {
Log.e(TAG,"==registerSoftApCallback==");
// 定义一个SoftApCallback接口的名称,以便稍后使用
String softApCallbackClassName = "android.net.wifi.WifiManager$SoftApCallback";
// 获取SoftApCallback接口的Class对象
Class<?> softApCallbackClass;
try {
softApCallbackClass = Class.forName(softApCallbackClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
// 动态创建SoftApCallback接口的实现类
mSoftApCallback = Proxy.newProxyInstance(
softApCallbackClass.getClassLoader(),
new Class<?>[]{softApCallbackClass},
softApCallbackHandler);
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.Q){
Method registerSoftApCallbackMethod;
try {
registerSoftApCallbackMethod = WifiManager.class.getDeclaredMethod("registerSoftApCallback", Executor.class, softApCallbackClass);
} catch (NoSuchMethodException e) {
e.printStackTrace();
return;
}
// 创建一个Executor,将回调方法传递给主线程
Executor mainThreadExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper()));
try {
registerSoftApCallbackMethod.invoke(mWifiManager, mainThreadExecutor, mSoftApCallback);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}else {
Log.e(TAG,"registerSoftApCallback-->Android 10");
try {
Method registerSoftApCallbackMethod = WifiManager.class.getDeclaredMethod("registerSoftApCallback", softApCallbackClass,Handler.class);
registerSoftApCallbackMethod.invoke(mWifiManager, mSoftApCallback,null);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
public void unregisterSoftApCallback(){
if(mSoftApCallback != null){
String softApCallbackClassName = "android.net.wifi.WifiManager$SoftApCallback";
// 获取SoftApCallback接口的Class对象
Class<?> softApCallbackClass;
try {
softApCallbackClass = Class.forName(softApCallbackClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
try {
Method unregisterSoftApCallbackMethod = WifiManager.class.getDeclaredMethod("unregisterSoftApCallback", softApCallbackClass,Handler.class);
unregisterSoftApCallbackMethod.invoke(mWifiManager,mSoftApCallback);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
public static class HandlerExecutor implements Executor {
private final Handler handler;
public HandlerExecutor(Handler handler) {
this.handler = handler;
}
@Override
public void execute(Runnable command) {
handler.post(command);
}
}
Android热点SoftAP使用方式的更多相关文章
- android 三种定位方式
http://www.cnblogs.com/oudi/archive/2012/03/22/2411509.html 最近在看android关于定位的方式,查了很多资料,也做了相关实验,在手机上做了 ...
- Android 修改屏幕解锁方式
Android 修改屏幕解锁方式 问题 在手机第一次开机的时候,运行手机激活的APP 在激活APP允许过程中,当用户按电源键的时候,屏幕黑掉,进入锁屏状态 手机默认的锁屏是滑动解锁 用户这个时候再一次 ...
- Android] Android XML解析学习——方式比较
[Android] Android XML解析学习——方式比较 (ZT) 分类: 嵌入式 (From:http://blog.csdn.net/ichliebephone/article/deta ...
- Android几种视频播放方式,VideoView、SurfaceView+MediaPlayer、TextureView+MediaPlayer,以及主流视频播放器开源项目
简单的说下一Android的几种视频播放功能: 1.VideoView:最简单的视频播放 <FrameLayout xmlns:android="http://schemas.andr ...
- Android热点回顾第六期
Android热点回顾第五期 http://www.importnew.com/9274.html Android热点回顾第四期http://www.importnew.com/8997.html A ...
- [转]Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件
Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件. 1.为了防止拖动ListView时,在列表末尾重复数据显示.需要加入 HashMap<In ...
- 理解Android绘制视图的方式
在创建自定义ViewGroup前,读者首先需要理解Android绘制视图的方式.我不会涉及过多细节,但是需要读者理解Android开发文档(见3.5节)中的一段话,这段话解释如何绘制一个布局.内容如下 ...
- Android 查看Apk签名方式V1和V2
Android 查看Apk签名方式V1和V2 java -jar apksigner.jar verify -v my.apk -- Verifies Verified using v1 scheme ...
- Android 进程间通讯方式
Android 进程间通讯方式 1.通过单向数据管道传递数据 管道(使用PipedWriter/ 创建PipedReader)是java.io包的一部分.也就是说,它们是一般的Java功能,而不是An ...
- android服务之启动方式
服务有两种启动方式 通过startService方法来启动 通过bindService来开启服务 布局文件 在布局文件中我们定义了四个按键来测试这两种方式来开启服务的不同 <?xml versi ...
随机推荐
- PHP操作数据分页
PHP操作数据分页 一.数据库安全 string addslashes ( string $str ) 返回字符串,该字符串为了数据库查询语句等的需要在某些字符前加上了反斜线.这些字符是单引号('). ...
- ElasticSearch7.3学习(三十四)----生产环境集群部署总结
1.集群部署 2.结点的三个角色 主结点:master节点主要用于集群的管理及索引 比如新增结点.分片分配.索引的新增和删除等. 数据结点:data 节点上保存了数据分片,它负责索引和搜索操作. 客户 ...
- 如何快速获取AWR中涉及到的表
最近遇到一个很少见的需求,是关于应用测试方面的. 具体来说,这个应用的测试需求要基于一个固定的时间点数据,而且只能测试一轮,再测试就需要还原到测试前状态. 因为我们使用的存储是分层的(热数据在Flas ...
- [MyArch]我的Archlinux与bspwm的重生之途
0x00 前言碎语 2023.8.19 好久不见.这些日子一直在和bspwm和archlinux打交道.自从上次NepCTF的前几天和CuB3y0nd小师傅的bspwm配置打交道之后我一发不可收拾.中 ...
- 基于tensorflow的RBF神经网络案例
1 前言 在使用RBF神经网络实现函数逼近中,笔者介绍了使用 Matlab 训练RBF神经网络.本博客将介绍使用 tensorflow 训练RBF神经网络.代码资源见:RBF案例(更新版) 这几天,笔 ...
- 使用BP神经网络实现函数逼近
1 一元函数逼近 1.1 待逼近函数 1.2 代码 clear,clc p=[-4:0.1:4]; %神经网络输入值 t=sin(0.5*pi*p)+sin(pi*p); %神经网络目标值 n=15; ...
- Optional 详解
1 前言 Optional 是 Java 8 的新特性,专治空指针异常(NullPointerException, 简称 NPE)问题,它是一个容器类,里面只存储一个元素(这点不同于 Conllect ...
- 玩转SpringBoot:动态排除Starter配置,轻松部署
引言 在软件开发中,进行本地单元测试是一项常规且必要的任务.然而,在进行单元测试时,有时需要启动一些中间件服务,如Kafka.Elasticjob等.举例来说,我曾经遇到过一个问题:项目中使用了Red ...
- Dubbo使用APISIX作为网关
为什么使用网关 Dubbo服务本身没有暴露HTTP接口,客户端(如:Web,APP)无法直接调用其提供的方法. 而APISIX可以通过dubbo-proxy插件为Dubbo服务提供外部访问的HTTP接 ...
- chrome浏览器配置自定义搜索引擎
chrome谷歌浏览器配置自定义搜索引擎 放弃百度搜索已经酝酿许久,现在搜索结果简直不忍直视.如果你想放弃使用百度搜索,并转向其他搜索引擎,头条搜索可能是一个不错的选择. 使用以下方式可以丝滑的使用其 ...