讲师中心 微信公众号
AI工具推荐 视频效率加速

Android 16 权限默认授予机制与存储目录持久化访问完整实践指南

夏晨姑娘_3276

夏晨姑娘_3276

发布时间:2026-10-28 20:10:41

|

421人浏览过

|

来源于php中文网

原创

Android 16 权限默认授予机制与存储目录持久化访问完整实践指南

本文详解 Android 中通过 ACTION_OPEN_DOCUMENT_TREE 获取的目录访问权限为何在应用重启后失效,并系统性讲解如何调用 takePersistableUriPermission() 实现跨进程、跨生命周期的持久化文件访问,同时结合 Android 权限演进趋势给出合规适配建议。

本文详解 android 中通过 `action_open_document_tree` 获取的目录访问权限为何在应用重启后失效,并系统性讲解如何调用 `takepersistableuripermission()` 实现跨进程、跨生命周期的持久化文件访问,同时结合 android 权限演进趋势给出合规适配建议。

在 Android 开发中,使用 Intent.ACTION_OPEN_DOCUMENT_TREE 授权用户选择一个文件夹(如用于自动备份),是实现“作用域存储”下安全、可控外部存储访问的核心方式。但许多开发者会遇到这样一个典型问题:首次选择目录后功能正常,一旦强制关闭应用(Force Stop)或进程被系统回收,再次启动时 DocumentFile.fromTreeUri() 返回的对象中 findFile() 调用抛出 NullPointerException——根本原因在于:系统授予的 URI 访问权限默认是临时的,仅在当前任务栈存活期间有效;Force Stop 会彻底清除所有已授予的 URI 权限,且不会自动恢复。

✅ 正确做法:持久化 URI 权限(Persistable URI Permission)

Android 自 API 19(KitKat)起引入 ContentResolver.takePersistableUriPermission(),专为解决此问题而设计。它将用户一次授权的 content:// URI 权限“固化”到系统权限管理器中,使该 URI 在 App 重启、设备重 boot 后仍可安全访问(前提是未被用户手动撤销)。

你需要在 onActivityResult() 中、成功获取 URI 后立即调用该方法:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
    super.onActivityResult(requestCode, resultCode, resultData);
    if (requestCode == AppConstants.GRANT_ACCESS_TO_AUTO_BACKUP_FILE && resultCode == Activity.RESULT_OK) {
        if (resultData != null) {
            Uri uri = resultData.getData();
            // ✅ 关键步骤:持久化读写权限
            final int takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
            getContentResolver().takePersistableUriPermission(uri, takeFlags);

            // 保存 URI 字符串(推荐使用 SharedPreferences)
            spEditor.putString("auto_backup_uri", uri.toString()).apply();

            // 后续创建子目录逻辑保持不变...
            DocumentFile documentFile = DocumentFile.fromTreeUri(this, uri);
            DocumentFile dfFinalDir = documentFile.findFile(getString(R.string.app_name_no_space));
            if (dfFinalDir == null) {
                dfFinalDir = documentFile.createDirectory(getString(R.string.app_name_no_space));
            }
            // ...
        }
    }
}

⚠️ 注意事项:

Android SMS Gateway
Android SMS Gateway

自托管短信服务,使用 Android 手机作为网关提供 HTTP API,支持发送/接收短信,兼容主流短信网关。

下载
  • takePersistableUriPermission() 必须在 onActivityResult() 中、RESULT_OK 状态下执行,不能延迟到异步线程或后续生命周期回调中;
  • 权限标志需与实际需求匹配:若仅读取,传 FLAG_GRANT_READ_URI_PERMISSION;若需写入(如本例创建/修改文件),必须同时传入 FLAG_GRANT_WRITE_URI_PERMISSION;
  • 该操作无需用户二次确认,但仅对用户明确通过系统文件选择器授予的 URI 生效(即来自 ACTION_OPEN_DOCUMENT_TREE 或 ACTION_OPEN_DOCUMENT 的结果);
  • 持久化权限可被用户在「设置 → 应用 → [你的App] → 权限 → 存储访问」中手动撤销,代码中应始终做好空值校验。

? 读取时的安全调用模式

在 writeDataToFile() 中,不应假设 uriString 一定有效。务必先校验 URI 是否仍具访问权,并在失败时引导用户重新授权:

private void writeDataToFile(Intent intent) {
    String uriString = sp.getString("auto_backup_uri", "");
    if (uriString.isEmpty()) {
        // 引导用户重新选择目录
        requestBackupDirectory();
        return;
    }

    Uri uri = Uri.parse(uriString);
    // ✅ 验证持久化权限是否仍然有效(推荐)
    if (!isUriPermissionGranted(uri)) {
        Log.w("TAG", "Persisted permission lost for: " + uri);
        requestBackupDirectory();
        return;
    }

    DocumentFile dfBeforeFinalDir = DocumentFile.fromTreeUri(this, uri);
    DocumentFile dfFinalDir = dfBeforeFinalDir.findFile(getString(R.string.app_name_no_space));
    if (dfFinalDir == null) {
        // 目录可能被用户删除,需重建
        dfFinalDir = dfBeforeFinalDir.createDirectory(getString(R.string.app_name_no_space));
    }

    DocumentFile dfFinalAutoBackupFile = dfFinalDir.findFile("notatki_a");
    if (dfFinalAutoBackupFile == null) {
        dfFinalAutoBackupFile = dfFinalDir.createFile("application/json", "notatki_a");
    }

    try (OutputStream os = getContentResolver().openOutputStream(dfFinalAutoBackupFile.getUri())) {
        os.write(jsonNotes.getBytes(StandardCharsets.UTF_8));
        intent.putExtra("auto_save_status", AppConstants.SUCCESS);
    } catch (IOException e) {
        doWhenException(e, "Failed to write backup file", intent);
    }
}

// 辅助方法:检查 URI 权限是否仍存在
private boolean isUriPermissionGranted(Uri uri) {
    int flags = getContentResolver().getPersistedUriPermissions().stream()
            .filter(p -> p.getUri().equals(uri))
            .mapToInt(p -> p.getModeFlags())
            .findFirst()
            .orElse(0);
    return (flags & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) 
           == (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}

? 与 Android 权限演进趋势的一致性

从 Android 6.0 运行时权限,到 Android 10+ 的作用域存储(Scoped Storage),再到 Android 14+ 的照片选择器(Photo Picker),Google 持续强化“最小权限原则”和“用户可控性”。ACTION_OPEN_DOCUMENT_TREE + takePersistableUriPermission() 正是这一理念下的标准、合规、面向未来的方案——它不依赖 MANAGE_EXTERNAL_STORAGE(已在 Android 11+ 被限制为特殊用途)、不请求全局存储权限,而是让用户精确控制“哪个文件夹、给谁用、用多久”。

✅ 最佳实践总结:

  • 永远不要假设 URI 权限永久有效:每次使用前做存在性与有效性校验;
  • 权限获取与持久化必须同步完成:onActivityResult() 是唯一可靠时机;
  • 优雅降级:权限丢失时提供清晰 UI 引导,而非崩溃;
  • 避免滥用:仅对真正需要长期访问的目录(如备份根目录)持久化,普通临时文件用 getCacheDir() 即可。

通过以上改造,你的应用即可在 Android 10–16+ 全系列版本中稳定实现“一次选择、长期可用”的目录访问能力,既符合平台规范,也大幅提升用户体验与数据可靠性。

热门AI工具

更多
切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

相关专题

更多
android开发三大框架
android开发三大框架

android开发三大框架是XUtil框架、volley框架、ImageLoader框架。本专题为大家提供android开发三大框架相关的各种文章、以及下载和课程。

902

2023.08.14

android是什么系统
android是什么系统

Android是一种功能强大、灵活可定制、应用丰富、多任务处理能力强、兼容性好、网络连接能力强的操作系统。本专题为大家提供android相关的文章、下载、课程内容,供大家免费下载体验。

2707

2023.08.22

android权限限制怎么解开
android权限限制怎么解开

android权限限制可以使用Root权限、第三方权限管理应用程序、ADB命令和Xposed框架解开。详细介绍:1、Root权限,通过获取Root权限,用户可以解锁所有权限,并对系统进行自定义和修改;2、第三方权限管理应用程序,用户可以轻松地控制和管理应用程序的权限;3、ADB命令,用户可以在设备上执行各种操作,包括解锁权限;4、Xposed框架,用户可以在不修改系统文件的情况下修改应用程序的行为和权限。

5337

2023.09.19

android重启应用的方法有哪些
android重启应用的方法有哪些

android重启应用有通过Intent、PendingIntent、系统服务、Runtime等方法。本专题为大家提供Android相关的文章、下载、课程内容,供大家免费下载体验。

685

2023.10.18

Android语音播放功能实现方法
Android语音播放功能实现方法

实现方法有使用MediaPlayer实现、使用SoundPool实现两种。可以根据具体的需求选择适合的方法进行实现。想了解更多语音播放的相关内容,可以阅读本专题下面的文章。

964

2024.03.01

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

120

2026.10.22

Gradle Wrapper配置教程
Gradle Wrapper配置教程

本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。

60

2026.10.22

Golangci-lint检查器使用教程
Golangci-lint检查器使用教程

本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。

60

2026.10.22

Gradle怎么构建Java项目
Gradle怎么构建Java项目

本专题介绍Gradle Java项目构建方法,涵盖Java插件配置、依赖声明、源码编译、测试执行和打包检查,帮助你理解各阶段的任务关系,逐步完成项目构建。

40

2026.10.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Excel 教程
Excel 教程

共162课时 | 44.8万人学习

Java 教程
Java 教程

共578课时 | 194.1万人学习

Uniapp从零开始实现新闻资讯应用
Uniapp从零开始实现新闻资讯应用

共64课时 | 9.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn