
本文详解如何在 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 应用即可稳定实现文件读写功能。









