Kivy Android 应用文件读写权限完整解决方案

1次阅读

Kivy Android 应用文件读写权限完整解决方案

本文详解如何在 kivy + python for android 项目中正确申请并使用外部存储读写权限,解决 permissionerror: [errno 13] permission denied 问题,涵盖声明、运行时请求、路径适配及最佳实践。

本文详解如何在 kivy + python for android 项目中正确申请并使用外部存储读写权限,解决 permissionerror: [errno 13] permission denied 问题,涵盖声明、运行时请求、路径适配及最佳实践。

在 Android 6.0(API 23)及以上系统中,即使已在 buildozer.spec 中声明了 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE 权限,仍需在应用运行时显式请求用户授权——这是导致你的 Kivy 应用启动即崩溃的根本原因。错误日志 PermissionError: [Errno 13] Permission denied 明确表明:代码尝试在未获授权时直接访问 /storage/emulated/0/(即公共外部存储),而 Android 拒绝了该操作。

✅ 正确做法:运行时权限请求必须早于任何文件 I/O 操作

关键原则是:权限请求必须在 App 实例创建和任何文件系统调用之前完成,且需处理异步授权结果。你原代码中将 request_permissions() 放在 build() 方法内(即 UI 构建阶段)存在两大缺陷:

  • build() 可能被多次调用,但权限只需请求一次;
  • 更严重的是:mkdir() 和 open() 在 request_permissions() 返回前就已执行(该函数是异步的),此时权限尚未授予,必然失败。

以下是修正后的标准实现:

import kivy from kivy.app import App from kivy.uix.label import Label from kivy.utils import platform from pathlib import Path  # 仅在 Android 平台导入并请求权限(避免桌面端报错) if platform == "android":     from android.permissions import request_permissions, Permission, check_permission     from android.storage import primary_external_storage_path  class MyApp(App):     def build(self):         text = f"User data dir: {App.get_running_app().user_data_dir}n"         self.label = Label(text=text)          if platform == "android":             # ✅ 步骤1:检查是否已获权限(可选,提升体验)             if not (check_permission(Permission.READ_EXTERNAL_STORAGE) and                      check_permission(Permission.WRITE_EXTERNAL_STORAGE)):                 # ✅ 步骤2:在 build() 开头请求权限(但注意:仍需异步处理)                 # ⚠️ 注意:request_permissions 是异步的,后续操作必须等待回调!                 self.request_storage_permissions()             else:                 self.create_file_safely()         else:             self.create_file_safely()          return self.label      def request_storage_permissions(self):         """安全地请求存储权限,并在授权后执行文件操作"""         from android.permissions import request_permissions, Permission         from android import mActivity          def callback(permissions, results):             # permissions 和 results 是并行列表,results[i] 表示 permissions[i] 是否被授予             if all(results):                 self.create_file_safely()             else:                 self.label.text += "❌ 权限被拒绝,无法创建文件。n"          # 异步请求,callback 将在用户响应后触发         request_permissions(             [Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE],             callback         )      def create_file_safely(self):         """在确认获得权限后执行文件操作"""         try:             folder_path = Path(primary_external_storage_path(), "my_kivy_app")             folder_path.mkdir(parents=True, exist_ok=True)              file_path = folder_path / "test.txt"             with open(file_path, "w", encoding="utf-8") as f:                 f.write("Hello from Kivy on Android! ?n")              self.label.text += f"✅ 文件已创建:{file_path}n"         except Exception as e:             self.label.text += f"❌ 文件操作失败:{e}n"  # ✅ 最佳实践:主入口处提前请求权限(推荐方案) if __name__ == '__main__':     if platform == "android":         from android.permissions import request_permissions, Permission         # 在 App 启动前同步请求(阻塞至用户响应完毕)         # ⚠️ 注意:此方式会短暂显示权限弹窗再启动 UI,用户体验更可控         request_permissions([Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE])      MyApp().run()

? 配置补充说明(buildozer.spec)

确保你的 buildozer.spec 包含以下配置(注意格式与权限名称大小写):

# 必须声明(用于生成 AndroidManifest.xml) android.permissions = READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, INTERNET  # 推荐添加(适配 Android 11+ Scoped Storage 的过渡兼容) android.api = 33 android.ndk = 25c p4a.branch = develop  # 使用较新版本以更好支持新 API

? 重要提示(Android 11+):从 Android 11(API 30)起,WRITE_EXTERNAL_STORAGE 对公共目录(如 /sdcard/Download)的访问被大幅限制。若需长期存储,建议改用 app_storage_path()(应用私有目录,无需权限)或使用 MediaStore API。本方案适用于兼容旧版及调试场景。

? 总结要点

  • 权限声明 ≠ 权限授予:buildozer.spec 中的配置仅生成清单,运行时请求不可或缺;
  • 请求时机决定成败:权限请求必须在任何 open()、mkdir() 等 I/O 操作之前完成,且需通过回调确认结果;
  • 避免线程阻塞:request_permissions() 是异步的,切勿假设调用后立即生效;
  • 路径选择有讲究:优先使用 app_storage_path()(无需权限,数据随 App 卸载清除);若必须用共享存储,请确保目标路径符合 Scoped Storage 规则;
  • 始终异常捕获:Android 权限模型动态性强,务必用 try/except 包裹文件操作。

遵循以上方案,你的 Kivy Android 应用即可稳定实现文件读写功能。

text=ZqhQzanResources