Kivy框架读取Download目录图片遇权限拒绝问题求助
这不是单纯的EMUI或MIUI适配问题,核心原因是Android 10及以上的存储权限机制变化,叠加部分定制ROM的权限管控强化,具体分析和解决方法如下:
核心原因
Android 10+ Scoped Storage 限制
Android 10开始引入分区存储机制,即使申请了READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限,APP也不能直接通过绝对路径访问公共目录(如Download)下的非自身创建文件。摩托罗拉机型可能因ROM适配宽松暂时绕过了该限制,但这不符合Android官方规范。定制ROM权限管控升级
EMUI、MIUI在Android基础规则上进一步强化了权限管控,即使权限申请通过,也严格执行Scoped Storage限制,禁止APP直接读取公共目录文件。权限请求时序问题
当前代码仅在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

