Android WiFi热点
项目中有需要对WiFi热点需要进行某些操作,所以记录下一些有关WiFi热点的知识点
开启或者关闭热点
网上的大部分例子,都是通过反射,调用WifiManager 的setWifiApEnabled 方法,来开启或者关闭热点,如:
WifiConfiguration apConfig = new WifiConfiguration();
apConfig.SSID = "YRCCONNECTION";
apConfig.preSharedKey="12122112";
Method method = mWifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, Boolean.TYPE);
但上面的方法,貌似只对安卓7.0或7.0以下版本有效,在我Android 11的手机上,并没有调用成功
通过Android系统源码中的WifiTetherSettings ,可以知道,现在启用热点,调用的是ConnectivityManager 中的 startTethering 方法
@SystemApi
@Deprecated
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void startTethering(int type, boolean showProvisioningUi,
final OnStartTetheringCallback callback, Handler handler) {
Objects.requireNonNull(callback, "OnStartTetheringCallback cannot be null.");
final Executor executor = new Executor() {
@Override
public void execute(Runnable command) {
if (handler == null) {
command.run();
} else {
handler.post(command);
}
}
};
final StartTetheringCallback tetheringCallback = new StartTetheringCallback() {
@Override
public void onTetheringStarted() {
callback.onTetheringStarted();
}
@Override
public void onTetheringFailed(final int error) {
callback.onTetheringFailed();
}
};
final TetheringRequest request = new TetheringRequest.Builder(type)
.setShouldShowEntitlementUi(showProvisioningUi).build();
getTetheringManager().startTethering(request, executor, tetheringCallback);
}
这个方法也被标注为@Deprecated ,表示已废弃 提示我们使用TetheringManager#startTethering
TetheringManager 中的startTethering 方法
public void startTethering(@NonNull final TetheringRequest request,
@NonNull final Executor executor, @NonNull final StartTetheringCallback callback) {
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "startTethering caller:" + callerPkg);
final IIntResultListener listener = new IIntResultListener.Stub() {
@Override
public void onResult(final int resultCode) {
executor.execute(() -> {
if (resultCode == TETHER_ERROR_NO_ERROR) {
callback.onTetheringStarted();
} else {
callback.onTetheringFailed(resultCode);
}
});
}
};
getConnector(c -> c.startTethering(request.getParcel(), callerPkg,
getAttributionTag(), listener));
}
所以参考How do I enable/disable hotspot or tethering mode programmatically on Android?
如果Android Version >= 11,则使用TetheringManager#startTethering 如果Android Version < 11,则使用ConnectivityManager#startTethering
最终,对最新的Android的版本,在网络上最后找到了如下的几个解决方案:
如果直接运行上面的代码,可能会报错,如
java.lang.IllegalArgumentException: dexcache == null (and no default could be found; consider setting the ‘dexmaker.dexcache’ system property)
可能的解决办法是:
1.修改依赖的dexmaker-mockito 版本为2.2.0 2.ProxyBuilder 要调用dexCache() 方法,不要问我为什么
使用的时候需要注意: 1.需要WRITE_SETTINGS 权限,在手机权限里面设置下,打开这个权限 2.是否真的启用了”个人热点“,需要去设置中查看,这个开关是否打开
粗略代码如下,2个方法,大同小异
private void startTethering() {
ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
try {
File outputDir = getCodeCacheDir();
Class classOnStartTetheringCallback = Class.forName("android.net.ConnectivityManager$OnStartTetheringCallback");
Method startTethering = connectivityManager.getClass().getDeclaredMethod("startTethering", int.class, boolean.class, classOnStartTetheringCallback);
Object proxy = ProxyBuilder.forClass(classOnStartTetheringCallback).dexCache(outputDir).handler(new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
return null;
}
}).build();
startTethering.invoke(connectivityManager, 0, false, proxy);
} catch (Exception e) {
Log.e(TAG,"打开热点失败");
e.printStackTrace();
}
}
public boolean enableTetheringNew() {
File outputDir = getCodeCacheDir();
Object proxy = new Object();
try {
proxy = ProxyBuilder.forClass(classOnStartTetheringCallback())
.dexCache(outputDir).handler(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
case "onTetheringStarted":
Log.d(TAG, "onTetheringStarted");
break;
case "onTetheringFailed":
Log.d(TAG, "onTetheringFailed");
break;
default:
ProxyBuilder.callSuper(proxy, method, args);
}
return null;
}
}).build();
} catch (Exception e) {
e.printStackTrace();
}
ConnectivityManager manager = (ConnectivityManager)getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
Method method = null;
try {
method = manager.getClass().getDeclaredMethod("startTethering", int.class, boolean.class, classOnStartTetheringCallback(), Handler.class);
if (method == null) {
Log.e(TAG, "startTetheringMethod is null");
} else {
method.invoke(manager, 0, false, proxy, null);
}
return true;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return false;
}
private Class classOnStartTetheringCallback() {
try {
return Class.forName("android.net.ConnectivityManager$OnStartTetheringCallback");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
在本人oppo Android11的手机上,测试是有效果的
关闭WiFi热点
private void stopTethering() {
ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
try {
Method stopTethering = connectivityManager.getClass().getDeclaredMethod("stopTethering", int.class);
stopTethering.invoke(connectivityManager,0);
} catch (Exception e) {
Log.e(TAG,"关闭热点失败");
e.printStackTrace();
}
}
在本人手机上测试也是有效果的
参考文档:
WiFi热点状态
获取WiFi热点的状态需要使用WifiManager 如,获取WiFi热点的状态:
@SystemApi
@RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
public int getWifiApState() {
try {
return mService.getWifiApEnabledState();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
getWifiApState() 也是系统的API,返回值有5种状态:
WIFI_AP_STATE_DISABLED - 值为11WIFI_AP_STATE_DISABLING - 值为10WIFI_AP_STATE_ENABLED - 值为13WIFI_AP_STATE_ENABLING - 值为12WIFI_AP_STATE_FAILED - 值为14
通过isWifiApEnabled() 方法,获取热点是否开启
@SystemApi
@RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
public boolean isWifiApEnabled() {
return getWifiApState() == WIFI_AP_STATE_ENABLED;
}
获取WiFi热点配置
上面参考的例子中有获取热点名称的方法,我测试后发现会发生异常:
java.lang.SecurityException: App not allowed to read or update stored WiFi Ap config (uid = 10488)
网上说法是需要权限
<permission android:name="android.permission.OVERRIDE_WIFI_CONFIG"
android:protectionLevel="signature|privileged" />
另外在MyOreoWifiManager中有配置热点的代码,说需要android.permission.TETHER_PRIVILEGED 权限:
public void configureHotspot(String name, String password) {
WifiConfiguration apConfig = new WifiConfiguration();
apConfig.SSID = name;
apConfig.preSharedKey = password;
apConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
try {
Method setConfigMethod = mWifiManager.getClass().getMethod("setWifiApConfiguration", WifiConfiguration.class);
boolean status = (boolean) setConfigMethod.invoke(mWifiManager, apConfig);
Log.d(TAG, "setWifiApConfiguration - success? " + status);
} catch (Exception e) {
Log.e(TAG, "Error in configureHotspot");
e.printStackTrace();
}
}
本人也没有开发系统app的权限,所以有提示如下的异常:
WifiManager 中有关热点配置的方法
public WifiConfiguration getWifiApConfiguration()
获取Wi-Fi AP的配置,注意此API已废弃,建议使用getSoftApConfiguration()
public boolean setWifiApConfiguration(WifiConfiguration wifiConfig)
设置Wi-Fi AP的配置,注意此API已废弃,建议使用setSoftApConfiguration(SoftApConfiguration)
public SoftApConfiguration getSoftApConfiguration()
Gets the Wi-Fi tethered AP Configuration.
@SystemApi
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public boolean setSoftApConfiguration(@NonNull SoftApConfiguration softApConfig) {
try {
return mService.setSoftApConfiguration(
softApConfig, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
Sets the tethered Wi-Fi AP Configuration
连接到热点的设备信息
网上有通过访问系统文件/proc/net/arp 的方式,来获取连接到热点的设备的ip信息, 可参考:
在Android 11尝试后,有如下的提示,看来是不再有效了:
java.io.FileNotFoundException: /proc/net/arp: open failed: EACCES (Permission denied)
在java android Q read ip from hotspot /proc/net/arp: open failed: EACCES (Permission denied)处有给出新的方法,尝试之后也行不通
不过,在新版本中,Android又提供了一个SoftApCallback 接口,通过WifiManager#registerSoftApCallback(Executor, SoftApCallback) 设置,参考:
@SystemApi
public interface SoftApCallback {
default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {}
default void onConnectedClientsChanged(@NonNull List<WifiClient> clients) {}
registerSoftApCallback 方法说明:
@SystemApi
@RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public void registerSoftApCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull SoftApCallback callback) {
if (executor == null) throw new IllegalArgumentException("executor cannot be null");
if (callback == null) throw new IllegalArgumentException("callback cannot be null");
Log.v(TAG, "registerSoftApCallback: callback=" + callback + ", executor=" + executor);
Binder binder = new Binder();
try {
mService.registerSoftApCallback(
binder, new SoftApCallbackProxy(executor, callback), callback.hashCode());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
SoftApConfiguration 相关的源码,可查看Here
|