You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kivy框架读取Download目录图片遇权限拒绝问题求助

Android端Kivy读取Download目录权限问题排查

这不是单纯的EMUI或MIUI适配问题,核心原因是Android 10及以上的存储权限机制变化,叠加部分定制ROM的权限管控强化,具体分析和解决方法如下:

核心原因

  1. Android 10+ Scoped Storage 限制
    Android 10开始引入分区存储机制,即使申请了READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限,APP也不能直接通过绝对路径访问公共目录(如Download)下的非自身创建文件。摩托罗拉机型可能因ROM适配宽松暂时绕过了该限制,但这不符合Android官方规范。

  2. 定制ROM权限管控升级
    EMUI、MIUI在Android基础规则上进一步强化了权限管控,即使权限申请通过,也严格执行Scoped Storage限制,禁止APP直接读取公共目录文件。

  3. 权限请求时序问题
    当前代码仅在on_start中发起权限请求,但request_permissions是异步操作,若在用户尚未授权时就执行open操作,必然会触发权限拒绝错误。

解决方法

1. 遵循Scoped Storage规则,用MediaStore查询文件

通过Android的MediaStore API查询Download目录下的目标文件,获取其可访问路径,示例代码:

from kivy.utils import platform
if platform == 'android':
    from android import mActivity
    from android.content import ContentResolver
    from android.database import Cursor
    from android.net import Uri

    def get_download_file_path(file_name):
        resolver = mActivity.getContentResolver()
        uri = Uri.parse("content://downloads/public_downloads")
        projection = ["_data"]
        selection = "_display_name = ?"
        selection_args = [file_name]
        cursor = resolver.query(uri, projection, selection, selection_args, None)
        if cursor is not None and cursor.moveToFirst():
            file_path = cursor.getString(cursor.getColumnIndex("_data"))
            cursor.close()
            return file_path
        return None

    # 使用示例
    file_path = get_download_file_path("encoded.png")
    if file_path:
        with open(file_path, 'rb') as f:
            encode_bmp = f.read()

2. 采用存储访问框架(SAF)让用户选择文件

调用系统文件选择器,由用户主动选择目标文件,避免权限限制:

from kivy.utils import platform
if platform == 'android':
    from android import mActivity
    from android.content import Intent
    from android.net import Uri

    def select_file():
        intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
        intent.addCategory(Intent.CATEGORY_OPENABLE)
        intent.setType("image/*")
        mActivity.startActivityForResult(intent, 1001)

    # 需要重写on_activity_result方法处理返回结果
    def on_activity_result(self, request_code, result_code, intent):
        if request_code == 1001 and result_code == mActivity.RESULT_OK:
            uri = intent.getData()
            # 通过URI读取文件内容
            resolver = mActivity.getContentResolver()
            with resolver.openInputStream(uri) as f:
                encode_bmp = f.read()

3. 临时兼容方案(仅Android 10-12)

在AndroidManifest.xml中添加android:requestLegacyExternalStorage="true",强制使用旧版存储机制,但该属性在Android 13及以上失效,不推荐长期使用:

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

4. 修正权限请求逻辑

确保在权限授予后再执行文件操作,添加权限回调处理:

def on_start(self):
    if platform == 'android':
        from android.permissions import request_permissions, Permission, check_permission
        if not check_permission(Permission.READ_EXTERNAL_STORAGE) or not check_permission(Permission.WRITE_EXTERNAL_STORAGE):
            request_permissions([Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE], self.on_permission_result)
        else:
            self.load_image_from_download()

def on_permission_result(self, permissions, grant_results):
    if all(grant_results):
        self.load_image_from_download()
    else:
        # 提示用户授予权限才能继续
        pass

def load_image_from_download(self):
    # 这里执行读取文件的逻辑
    pass

内容的提问来源于stack exchange,提问作者VitaliiShpinat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 01:05:08