26、权限与WiFi:ACCESS_WIFI_STATE、CHANGE_WIFI_STATE、附近设备权限
WiFi相关的权限,说实话,是Android权限体系里一个很有意思的缩影。你想想看,从最早的几个简单权限,到后来为了隐私保护不断拆分、加限制,这背后其实是整个行业对用户数据认知的升级。我个人习惯把WiFi权限分成两类来看:一类是“看看就行”的查询权限,另一类是“动手操作”的控制权限。嗯,咱们今天就把它彻底聊透。
一、ACCESS_WIFI_STATE:只读,但信息量不小
这个权限,说白了就是让你的App能“看一眼”当前WiFi的状态。它能拿到什么?
- WiFi是否开启
- 当前连接的SSID(也就是WiFi名字)
- BSSID(路由器的MAC地址)
- 信号强度(RSSI)
- IP地址、网关、DNS等信息
我在项目中遇到过一个问题:有个做室内定位的App,需要根据WiFi信号强度来推算用户位置。当时我们只申请了ACCESS_WIFI_STATE,结果发现Android 10之后,拿到的SSID全是null。为什么?因为系统要求必须同时拥有ACCESS_FINE_LOCATION权限才能获取真实的WiFi列表和SSID。这个坑,我印象特别深。
ACCESS_WIFI_STATE本身不再能获取到SSID/BSSID。你需要位置权限(ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION)才能拿到这些信息。
代码示例很简单,但要注意运行时检查:
// 检查WiFi状态
WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
if (checkSelfPermission(Manifest.permission.ACCESS_WIFI_STATE)
== PackageManager.PERMISSION_GRANTED) {
int rssi = wifiManager.getConnectionInfo().getRssi();
// 信号强度,单位dBm
Log.d("WiFiDemo", "当前信号强度: " + rssi);
} else {
// 申请权限
requestPermissions(new String[]{Manifest.permission.ACCESS_WIFI_STATE}, REQUEST_CODE);
}
二、CHANGE_WIFI_STATE:动手开关WiFi
这个权限比上一个“重”一些。它允许你的App去改变WiFi的状态——比如打开或关闭WiFi、连接某个热点、忘记某个网络。听起来挺强大,对吧?但实际使用中,限制也不少。
我曾经做过一个自动化工具App,功能是“到家自动连WiFi,出门自动关WiFi”。当时我天真地以为,有了CHANGE_WIFI_STATE就能为所欲为。结果发现,从Android 10开始,系统对WiFi的开关操作做了严格限制——你的App必须在前台,并且用户必须明确授权,才能通过WifiManager.setWifiEnabled()来开关WiFi。
setWifiEnabled()方法已经被标记为废弃。系统推荐使用Settings.Panel.ACTION_WIFI来引导用户手动操作,或者使用WifiNetworkSpecifier来连接特定网络。
来看看怎么连接一个指定的WiFi网络(Android 10+推荐方式):
// 使用WifiNetworkSpecifier连接特定WiFi
WifiNetworkSpecifier specifier = new WifiNetworkSpecifier.Builder()
.setSsid("MyHomeWiFi")
.setWpa2Passphrase("password123")
.build();
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(specifier)
.build();
ConnectivityManager connectivityManager =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(@NonNull Network network) {
// 连接成功
Log.d("WiFiDemo", "已连接到指定WiFi");
}
@Override
public void onUnavailable() {
// 连接失败
Log.d("WiFiDemo", "无法连接到指定WiFi");
}
};
connectivityManager.requestNetwork(request, callback);
嗯,这里要注意:这个方式会弹出一个系统对话框,让用户确认是否要连接这个网络。你不能在后台静默连接,这是为了保护用户隐私和安全。
三、附近设备权限(NEARBY_DEVICES):Android 12的新成员
Android 12引入了一个新权限——NEARBY_DEVICES(属于NEARBY_WIFI_DEVICES权限组)。这个权限的出现,说实话,解决了一个长期存在的尴尬问题。
你想想看,以前App想扫描附近的WiFi设备(比如做WiFi直连、热点发现),必须申请位置权限。但App其实并不需要知道用户的具体位置,它只是想找找周围有没有可用的设备。这就导致了一个矛盾:为了一个简单的设备发现功能,用户必须把位置信息交出去。很多用户不理解,觉得“你一个WiFi工具为什么要我的位置?”
所以Google在Android 12里做了拆分:如果你只是想做WiFi设备发现、WiFi直连、或者热点扫描,不需要位置信息,只需要申请NEARBY_DEVICES权限就够了。
| 场景 | Android 12之前 | Android 12+ |
|---|---|---|
| 扫描WiFi热点列表 | 需要位置权限 | 需要NEARBY_DEVICES权限 |
| 获取当前WiFi的SSID | 需要位置权限 | 需要位置权限(不变) |
| WiFi直连(P2P) | 需要位置权限 | 需要NEARBY_DEVICES权限 |
| 开关WiFi | 需要CHANGE_WIFI_STATE |
需要CHANGE_WIFI_STATE(不变) |
代码中声明这个权限的方式:
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<!-- 如果App的目标API < 33,需要添加以下声明 -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:maxSdkVersion="32" />
运行时申请:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES)
!= PackageManager.PERMISSION_GRANTED) {
requestPermissions(
new String[]{Manifest.permission.NEARBY_WIFI_DEVICES},
REQUEST_CODE_NEARBY
);
}
}
四、知识体系梳理
我把这三个权限的关系画了一张图,方便你理解它们各自负责的领域:
五、避坑指南与最佳实践
做了这么多年Android安全,我总结了几条关于WiFi权限的“血泪教训”:
- 不要过度申请权限。 如果你只需要知道WiFi是否开启,就别申请
CHANGE_WIFI_STATE。用户现在对权限很敏感,多一个权限就多一分被卸载的风险。 - 注意Android版本差异。 同一个功能,在不同Android版本上需要的权限可能完全不同。我建议你在代码里做版本判断,而不是一刀切。
- 权限申请要讲清楚理由。 用户拒绝权限时,不要反复弹窗。用
shouldShowRequestPermissionRationale()来判断是否需要解释。
NEARBY_DEVICES权限时,可以这样向用户解释:“我们需要发现附近的WiFi设备,以便帮您快速连接。这个权限不会获取您的位置信息。”——用户听到“不需要位置”,接受度会高很多。
我曾经遇到一个案例:某个App在Android 12上崩溃了,原因是它还在用旧的方式扫描WiFi列表,没有适配新的NEARBY_DEVICES权限。用户反馈说“一打开就闪退”,排查了半天才发现是权限问题。从那以后,我每次适配新系统版本,第一件事就是检查权限变化。
嗯,WiFi权限这块其实不难,但细节很多。你只要记住一个原则:按需申请,最小化权限,适配版本差异。做到这三点,基本不会出大问题。
公众号:蓝海资料掘金营,微信deep3321