一、背景
最近项目中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 { |
| |
| 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 { |
| |
| 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加密模式/方式
| |
| |
| |
| |
| 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)) { |
| |
| return WifiConfiguration.KeyMgmt.WPA_PSK; |
| } else if (wifiConfiguration.allowedAuthAlgorithms.get(WifiConfiguration.KeyMgmt.WPA_EAP)) { |
| |
| return WifiConfiguration.KeyMgmt.WPA_EAP; |
| } |
| return WifiConfiguration.KeyMgmt.NONE; |
| } |
| |
| |
| |
| |
| |
| public boolean isApEncryption() { |
| WifiConfiguration wifiConfiguration = getAPConfig(); |
| if (wifiConfiguration.allowedAuthAlgorithms.get(WifiConfiguration.KeyMgmt.NONE)) { |
| |
| return false; |
| } else if (wifiConfiguration.allowedAuthAlgorithms.get(WifiConfiguration.KeyMgmt.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
方法可以发现高版本的比低版本兼容的代码越多,那我直接在本地自己按照最高版本的实现去实现不就解决所有版本了。
| |
| 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; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public int apChannelToFrequency(int channel,int band){ |
| Log.d(TAG,"apChannelToFrequency-->apChannel:"+channel+",band:"+band); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (band == ScanResult.WIFI_BAND_24_GHZ) { |
| |
| 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=="); |
| |
| String softApCallbackClassName = "android.net.wifi.WifiManager$SoftApCallback"; |
| |
| |
| Class<?> softApCallbackClass; |
| try { |
| softApCallbackClass = Class.forName(softApCallbackClassName); |
| } catch (ClassNotFoundException e) { |
| e.printStackTrace(); |
| return; |
| } |
| |
| |
| 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 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"; |
| |
| |
| 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); |
| } |
| } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库