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 ...
随机推荐
- 教你用JavaScript实现调皮的字母
案例介绍 欢迎来到我的小院,我是霍大侠,恭喜你今天又要进步一点点了!我们来用JavaScript编程实战案例,制作提高打字速度的小游戏-调皮的字母.点击与屏幕上字母相对应的按键,若按键与出现的字母一致 ...
- 小知识:使用MOS下载Oracle介质快速参考
之前对选Release.Patch Set.PSU都有专门的文档,现在早已简化,针对这些以及之后RU.RUR等都包含在MOS文档:2118136.2 Assistant: Download Refer ...
- 【MFC学习一】BROWSEINFO选择路径导出文件
mfc中使用 BROWSEINFO,使用 bi.lpfn = BrowseCallbackProc; 回调指定默认当前程序所在目录,导出csv文件,注意处理文件内容中的逗号.单引号.数字字符串开头有0 ...
- java 注解结合 spring aop 实现日志traceId唯一标识
MDC 的必要性 日志框架 日志框架成熟的也比较多: slf4j log4j logback log4j2 我们没有必要重复造轮子,一般是建议和 slf4j 进行整合,便于后期替换为其他框架. 日志的 ...
- java 注解结合 spring aop 实现自动输出日志
auto-log auto-log 是一款为 java 设计的自动日志监控框架. 创作目的 经常会写一些工具,有时候手动加一些日志很麻烦,引入 spring 又过于大材小用. 所以希望从从简到繁实现一 ...
- Redis原理再学习02:数据结构-动态字符串sds
Redis原理再学习:动态字符串sds 字符 字符就是英文里的一个一个英文字母,比如:a.中文里的单个汉字,比如:好. 字符串就是多个字母或多个汉字组成,比如字符串:redis,中文字符串:你好吗. ...
- Mysql 插入timestamp没有使用默认值问题
在一次升级过程中,发现Mysql插入数据报了个错 Column 'create_time' cannot be null. 但是看了下这个字段虽然是非null,但是是有默认值的 `create_tim ...
- 第一篇博客——MarkDown语法
Markdown学习 标题 三级标提 四级标题 字体 Hello World ! 两个星号加粗 Hello World ! 一个星号斜体 Hello World ! Hello World ! 两个波 ...
- 【Filament】基于物理的光照(PBR)
1 前言 自定义Blinn Phong光照模型中实现了基础的自定义光照,与现实的光照还是有些差别,本文将实现更逼真的光照效果,即基于物理的光照(PBR). 读者如果对 Filament 不太熟 ...
- 关闭mysql上锁的表/数据
一.输入查询语句,查看是否有数据被上锁 select * from information_schema.innodb_trx; 取 trx_mysql_thread_id 字段值 kill < ...