3、WiFi权限声明:AndroidManifest中WiFi相关权限

做WiFi开发,第一关就是权限。我见过太多新手,代码写得飞起,结果一运行就崩——哦,忘了加权限。这玩意儿说简单也简单,说坑也真坑。尤其是Android 10之后,规则变了好几次,稍不注意就踩雷。

3.1 静态权限:AndroidManifest里必须写的

先看最基础的。无论你的App跑在哪个Android版本,下面这几个权限声明是跑不掉的:

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

简单解释一下:

  • ACCESS_WIFI_STATE:读取WiFi状态。比如当前连没连上、信号强度多少、SSID是什么。这个权限是普通权限,系统自动授予,不需要用户弹窗同意。
  • CHANGE_WIFI_STATE:修改WiFi状态。比如打开/关闭WiFi、连接某个热点。也是普通权限。
  • ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION:这两个是危险权限。为什么WiFi要定位权限?因为Android系统认为,扫描WiFi热点可以推断出你的位置。说白了,Google把WiFi扫描和定位绑死了。
⚠️ 注意:从Android 8.0(API 26)开始,如果你只扫描WiFi但不获取位置,系统仍然要求你声明定位权限。否则startScan()返回的结果列表永远是空的。我当年第一次踩这个坑时,排查了整整一个下午。

3.2 运行时权限:Android 6.0+ 必须动态申请

从Android 6.0(API 23)开始,危险权限不能只写在Manifest里了。你得在代码里弹窗让用户同意。WiFi相关的定位权限就是典型的危险权限。

我个人习惯这样写:

// 检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        != PackageManager.PERMISSION_GRANTED) {
    // 请求权限
    ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
            REQUEST_CODE_LOCATION);
} else {
    // 已有权限,开始扫描
    startWifiScan();
}

然后在回调里处理结果:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_CODE_LOCATION) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            startWifiScan();
        } else {
            // 用户拒绝了,你可以提示或者引导去设置
            Toast.makeText(this, "需要定位权限才能扫描WiFi", Toast.LENGTH_SHORT).show();
        }
    }
}
💡 小技巧:如果用户点了「拒绝并不再询问」,下次再弹窗就没用了。我建议在这种情况下,直接跳转到App的设置页面,让用户手动开启。用Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)就能做到。

3.3 Android 10+ 的重大变化:精确定位 vs 模糊定位

嗯,这里要注意。Android 10(API 29)引入了一个新概念:精确定位模糊定位

以前你只要声明ACCESS_FINE_LOCATION就够了。但现在,Google希望用户有更多选择权:

  • ACCESS_FINE_LOCATION:精确定位。能获取到WiFi的详细扫描结果,包括SSID、BSSID、信号强度等。
  • ACCESS_COARSE_LOCATION:模糊定位。只能知道大概位置,WiFi扫描结果会被阉割——你拿不到具体的SSID和BSSID。

为什么会这样?说白了,Google在隐私保护上越来越严。如果你的App只是想知道「用户是不是在家附近」,那模糊定位就够了,没必要拿精确位置。

我建议的做法是:

// 同时请求两个权限,让用户选择
String[] permissions = new String[]{
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
};
ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE_LOCATION);

然后在代码里判断用户给了哪个:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        == PackageManager.PERMISSION_GRANTED) {
    // 精确定位,可以获取完整WiFi信息
} else if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
        == PackageManager.PERMISSION_GRANTED) {
    // 模糊定位,只能获取有限信息
} else {
    // 没有权限,提示用户
}
📌 核心要点:如果你要做WiFi扫描并显示SSID名称,必须申请ACCESS_FINE_LOCATION。模糊定位拿不到这些数据。我曾经有个项目,测试时发现某些手机上WiFi列表全是「未知网络」,排查半天才发现是用户只给了模糊定位。

3.4 Android 12+ 的进一步收紧

Android 12(API 31)又加了一条:ACCESS_FINE_LOCATION现在需要用户在系统设置里单独开启「精确位置」开关。也就是说,即使你申请了精确定位权限,用户也可以在设置里把它降级为模糊定位。

这意味着什么?你的App必须做好两手准备:

  1. 检查是否有ACCESS_FINE_LOCATION权限
  2. 如果没有,检查是否有ACCESS_COARSE_LOCATION权限
  3. 如果都没有,引导用户去设置开启

我个人习惯用一个工具方法来判断:

public static boolean hasLocationPermission(Context context) {
    return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED;
}

public static boolean hasCoarseLocationPermission(Context context) {
    return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
            == PackageManager.PERMISSION_GRANTED;
}

3.5 一张图看懂权限体系

下面这张图总结了WiFi权限的层级关系,你一看就明白:

Android WiFi 权限体系结构图 AndroidManifest 静态声明 普通权限(自动授予) vs 危险权限(需动态申请) 普通权限 ACCESS_WIFI_STATE CHANGE_WIFI_STATE 危险权限(需动态申请) ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION Android 10+ 变化 精确定位 → 完整WiFi信息 模糊定位 → 有限WiFi信息

3.6 避坑指南:我踩过的那些坑

做WiFi开发这几年,我总结了几条血泪教训:

  • 别在Android 6.0以下设备上动态申请权限——系统会直接崩溃。记得先判断Build.VERSION.SDK_INT
  • 用户拒绝权限后不要一直弹窗——用户体验极差。我建议拒绝两次后,直接引导去设置页。
  • Android 10以上,只声明ACCESS_COARSE_LOCATION是拿不到SSID的——别问我怎么知道的,问就是踩过坑。
  • 测试时记得用真机——模拟器上WiFi权限行为有时和真机不一样,尤其是定位权限。
⚠️ 重要提醒:从Android 12开始,如果你在Manifest里声明了ACCESS_FINE_LOCATION,但用户在实际使用中只给了模糊定位,你的checkSelfPermission返回的仍然是PERMISSION_GRANTED。你需要额外调用LocationManager.isLocationEnabled()来判断精确位置是否开启。

3.7 总结一下

WiFi权限这块,说白了就是三件事:

  1. Manifest里写全——普通权限和危险权限都写上,别漏。
  2. 运行时动态申请——危险权限必须弹窗让用户同意。
  3. 适配不同版本——Android 10关注精确定位,Android 12关注精确开关。

把这些搞定了,你的WiFi功能就成功了一半。剩下的就是业务逻辑了。


公众号:蓝海资料掘金营,微信deep3321