一、背景

最近项目中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使用方式的更多相关文章

  1. android 三种定位方式

    http://www.cnblogs.com/oudi/archive/2012/03/22/2411509.html 最近在看android关于定位的方式,查了很多资料,也做了相关实验,在手机上做了 ...

  2. Android 修改屏幕解锁方式

    Android 修改屏幕解锁方式 问题 在手机第一次开机的时候,运行手机激活的APP 在激活APP允许过程中,当用户按电源键的时候,屏幕黑掉,进入锁屏状态 手机默认的锁屏是滑动解锁 用户这个时候再一次 ...

  3. Android] Android XML解析学习——方式比较

     [Android] Android XML解析学习——方式比较 (ZT)  分类: 嵌入式 (From:http://blog.csdn.net/ichliebephone/article/deta ...

  4. Android几种视频播放方式,VideoView、SurfaceView+MediaPlayer、TextureView+MediaPlayer,以及主流视频播放器开源项目

    简单的说下一Android的几种视频播放功能: 1.VideoView:最简单的视频播放 <FrameLayout xmlns:android="http://schemas.andr ...

  5. Android热点回顾第六期

    Android热点回顾第五期 http://www.importnew.com/9274.html Android热点回顾第四期http://www.importnew.com/8997.html A ...

  6. [转]Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件

      Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件. 1.为了防止拖动ListView时,在列表末尾重复数据显示.需要加入 HashMap<In ...

  7. 理解Android绘制视图的方式

    在创建自定义ViewGroup前,读者首先需要理解Android绘制视图的方式.我不会涉及过多细节,但是需要读者理解Android开发文档(见3.5节)中的一段话,这段话解释如何绘制一个布局.内容如下 ...

  8. Android 查看Apk签名方式V1和V2

    Android 查看Apk签名方式V1和V2 java -jar apksigner.jar verify -v my.apk -- Verifies Verified using v1 scheme ...

  9. Android 进程间通讯方式

    Android 进程间通讯方式 1.通过单向数据管道传递数据 管道(使用PipedWriter/ 创建PipedReader)是java.io包的一部分.也就是说,它们是一般的Java功能,而不是An ...

  10. android服务之启动方式

    服务有两种启动方式 通过startService方法来启动 通过bindService来开启服务 布局文件 在布局文件中我们定义了四个按键来测试这两种方式来开启服务的不同 <?xml versi ...

随机推荐

  1. 详细了解Transformer:Attention Is All You Need

    1. 背景 在机器翻译任务下,RNN.LSTM.GRU等序列模型在NLP中取得了巨大的成功,但是这些模型的训练是通常沿着输入和输出序列的符号位置进行计算的顺序计算,无法并行. 文中提出了名为Trans ...

  2. HarmonyOS 开发入门(三)

    HarmonyOS 开发入门(三) 日常逼逼叨 在开发入门(一)和开发入门(二)中我们描述了 HarmonyOS 开发的语言ArKTs以及Ts简单的入门级语法操作以及开发环境的搭建,接下来我们进入第三 ...

  3. 分享实用小工具:JAVA版本位运算工具类

    将二进制数中的每位数字1或0代表着某种开关标记,1为是,0为否,则一个数字可以代表N位的开关标记值,可有效减少过多的变量定义 或 过多的表字段,同时也能在一些复杂的组合判断场景下利用位与.位或.异或等 ...

  4. 哈希表(HashMap)与字符串哈希

    哈希表 哈希表是一种通过映射来快速查找的数据结构.其通过键值对(key-value)来存储.一个数据通过哈希函数的运算来生成一个属于他自己的键值,尔后将其与键值绑定.当我们想查找这个数据时,就可以直接 ...

  5. 小知识:Exadata平台去掉密码输错延迟10分钟登录

    生产环境不评价,若是测试环境实在受不了偶尔一次因为密码输错就要等待10分钟才能登陆的限制. 那测试环境下,如何关闭这个限制呢?很简单: # vi /etc/pam.d/sshd --找到并注释掉下面这 ...

  6. 编译pjsip源码

    操作系统 : Windows 10_x64 [版本 10.0.19042.685] pjsip版本 : 2.10 pjsip官网:https://www.pjsip.org/ 1. 下载pjsip源代 ...

  7. NC22593 签到题

    题目链接 题目 题目描述 恭喜你找到了本场比赛的签到题! 为了让大家都有抽奖的机会,只需要复制粘贴以下代码(并且稍微填下空)即可 AC: (我超良心的) #include <algorithm& ...

  8. 【Unity3D】空间和变换

    1 空间 1.1 左右手坐标系及其法则 1.1.1 左右手坐标系 左手坐标系与右手坐标系 ​ Unity 局部空间.世界空间.裁剪空间.屏幕空间都采用左手坐标系,只有观察空间采用右手坐标系. ​ 左右 ...

  9. 解决unable to find valid certification path to requested target

    问题描述 最近java程序去调用远程服务器接口时报错了: I/O error on POST request for "https://XXX.xyz/create": sun.s ...

  10. Springboot thymeleaf实战总结

    介绍 以下总结了使用Thymeleaf做项目过程中碰到的有价值的知识点.拿出来分享! 1.配置context-path 在公共模板中添加: <script type="text/jav ...