6、特殊权限:SYSTEM_ALERT_WINDOW、WRITE_SETTINGS、MANAGE_EXTERNAL_STORAGE

普通权限和危险权限,说白了就是「申请一下,用户点同意」就能搞定的事。但今天聊的这三个特殊权限,可就没那么温柔了。它们需要用户手动去系统设置里翻找,甚至要跳转专门的授权页面。嗯,我当年第一次处理 SYSTEM_ALERT_WINDOW 时,就被这个「手动授权」的流程坑过——用户明明点了同意,权限却没生效,排查了半天才发现是流程没走对。

6.1 为什么要有「特殊权限」?

你想想看,如果一个 App 能随意在其他应用上悬浮显示,或者能修改系统设置,甚至能访问所有文件——这得多危险?所以 Android 系统把这几个权限单独拎出来,用更严格的机制保护。

特殊权限的核心特点就三个:

  • 不能直接申请:调用 requestPermissions() 没用,系统会直接忽略
  • 必须跳转设置:需要引导用户去「设置 → 特殊权限」里手动开启
  • 状态可查询:可以用 Settings.canDrawOverlays() 这类 API 检查是否已授权

核心原则:特殊权限的授权流程,本质上是「引导用户去系统设置里拨动开关」。你的代码只能做两件事——检查状态、跳转设置页。

6.2 SYSTEM_ALERT_WINDOW:悬浮窗权限

这个权限允许你的 App 在其他应用上方绘制内容。最常见的场景就是「悬浮球」「视频小窗」「聊天浮窗」。我个人习惯在需要显示悬浮窗之前,先检查权限状态,而不是一启动就弹窗。

6.2.1 权限声明

AndroidManifest.xml 中声明:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

6.2.2 检查与申请

// 检查是否已授权
if (Settings.canDrawOverlays(this)) {
    // 可以显示悬浮窗了
    showFloatingWindow();
} else {
    // 引导用户去设置页
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
            Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, REQUEST_CODE_OVERLAY);
}

注意ACTION_MANAGE_OVERLAY_PERMISSION 需要带上你的包名,否则系统会打开所有应用列表,用户还得手动找你的 App。我曾经见过一个 App 没传包名,用户找了半天没找到,直接差评走人。

6.2.3 处理授权结果

用户从设置页返回后,在 onActivityResult() 中重新检查权限:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_CODE_OVERLAY) {
        if (Settings.canDrawOverlays(this)) {
            // 用户已授权
            showFloatingWindow();
        } else {
            // 用户未授权,可以再次引导
            Toast.makeText(this, "需要悬浮窗权限才能使用此功能", Toast.LENGTH_LONG).show();
        }
    }
}

6.3 WRITE_SETTINGS:修改系统设置权限

这个权限允许你的 App 修改系统设置,比如屏幕亮度、音量、自动旋转等。说白了,就是能碰 Settings.System 里的那些值。我记得有一次做阅读器 App,需要根据环境光自动调节亮度,就用了这个权限。

6.3.1 权限声明

<uses-permission android:name="android.permission.WRITE_SETTINGS" />

6.3.2 检查与申请

// 检查是否已授权
if (Settings.System.canWrite(this)) {
    // 可以修改系统设置了
    setScreenBrightness(128);
} else {
    // 引导用户去设置页
    Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
            Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS);
}

小技巧:修改亮度时记得先保存原始值,用户退出功能后恢复。我曾经没做恢复逻辑,结果用户骂我「App 把我屏幕搞瞎了」。

6.3.3 实际修改示例

private void setScreenBrightness(int brightness) {
    ContentResolver resolver = getContentResolver();
    Settings.System.putInt(resolver, Settings.System.SCREEN_BRIGHTNESS, brightness);
}

6.4 MANAGE_EXTERNAL_STORAGE:管理所有文件权限

这个权限是 Android 11 引入的,用来替代旧的 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE。它允许 App 访问设备上的所有文件,包括其他应用的文件。嗯,这个权限争议很大,Google 对它的审核非常严格。

6.4.1 权限声明

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

同时需要在 application 标签中添加:

<application
    android:requestLegacyExternalStorage="true"
    ...>

6.4.2 检查与申请

// 检查是否已授权
if (Environment.isExternalStorageManager()) {
    // 可以访问所有文件了
    listAllFiles();
} else {
    // 引导用户去设置页
    Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
            Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, REQUEST_CODE_MANAGE_STORAGE);
}

重要提醒:Google Play 对 MANAGE_EXTERNAL_STORAGE 有严格限制。如果你的 App 不是文件管理器、杀毒软件这类核心场景,基本不会通过审核。我有个朋友做了一款图片编辑 App,想用这个权限直接访问所有图片,结果被 Google 拒了三次。

6.5 三种特殊权限对比

权限 检查 API 跳转 Action 典型场景
SYSTEM_ALERT_WINDOW Settings.canDrawOverlays() ACTION_MANAGE_OVERLAY_PERMISSION 悬浮窗、视频小窗
WRITE_SETTINGS Settings.System.canWrite() ACTION_MANAGE_WRITE_SETTINGS 亮度调节、音量控制
MANAGE_EXTERNAL_STORAGE Environment.isExternalStorageManager() ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION 文件管理器、备份工具

6.6 特殊权限的通用处理流程

这三种权限虽然各自不同,但处理流程高度一致。我总结了一个通用模板,你可以直接套用:

// 1. 检查权限
if (checkSpecialPermission()) {
    // 权限已授予,执行功能
    doSomething();
} else {
    // 2. 权限未授予,跳转设置页
    Intent intent = new Intent(getSettingsAction());
    intent.setData(Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, REQUEST_CODE);
}

// 3. 在 onActivityResult 中重新检查
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_CODE) {
        if (checkSpecialPermission()) {
            doSomething();
        } else {
            // 用户拒绝,给出提示
            showPermissionDeniedDialog();
        }
    }
}

避坑指南:我曾经遇到一个 Bug——用户从设置页返回后,onActivityResult() 没有被调用。原因是用户按了返回键,而不是直接关闭设置页。所以建议在 onResume() 中也做一次权限检查,双重保障。

6.7 知识体系图

下面这张图展示了特殊权限的完整知识结构,从分类到处理流程一目了然:

特殊权限知识体系 特殊权限 SYSTEM_ALERT_WINDOW WRITE_SETTINGS MANAGE_EXTERNAL_STORAGE 悬浮窗权限 • 检查:canDrawOverlays() • 跳转:ACTION_MANAGE_ OVERLAY_PERMISSION • 场景:悬浮球、小窗 • 注意:需传包名 • 返回:onActivityResult 修改系统设置权限 • 检查:Settings.System.canWrite() • 跳转:ACTION_MANAGE_ WRITE_SETTINGS • 场景:亮度、音量调节 • 注意:需保存原始值 • 返回:onActivityResult 管理所有文件权限 • 检查:isExternalStorage Manager() • 跳转:ACTION_MANAGE_APP_ ALL_FILES_ACCESS • 场景:文件管理器 • 注意:Google审核严格 • 返回:onActivityResult 通用流程:检查权限 → 跳转设置 → 返回检查 → 执行功能

6.8 总结

特殊权限的处理,说白了就是三步走:检查、跳转、再检查。和危险权限最大的区别在于——你不能直接弹窗申请,必须把用户「请」到系统设置里去手动开关。

我个人建议你在代码里做好两件事:一是权限状态的实时监听(比如在 onResume 里检查),二是用户拒绝后的友好引导(别一直弹窗,给个明确的说明)。

嗯,特殊权限这块就聊到这儿。记住一点:权限越特殊,责任越大。滥用这些权限,轻则被用户卸载,重则被应用商店下架。


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