一、背景
最近项目中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); | |
} | |
} |
JAVA 复制 全屏
四、获取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); | |
} | |
} |