Kivy应用中android.permissions导入失败,Plyer文件选择器Android端失效
解决Kivy+Plyer FileChooser在Android端无法获取文件路径的问题
问题核心在于Android 10及以上版本的Scoped Storage机制:Plyer的filechooser.open_file返回的不是直接的文件系统路径,而是content://开头的内容URI,直接用os.path.basename无法解析,导致获取不到文件名和路径。
解决方案步骤:
添加必要权限
在你的buildozer.spec文件中添加权限配置:android.permissions = READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, MANAGE_EXTERNAL_STORAGE注:
MANAGE_EXTERNAL_STORAGE仅针对Android 13及以上版本,用于访问所有文件。添加Content URI转真实路径的工具函数
编写函数解析Android的Content URI,借助jnius调用Android原生API获取实际文件路径。修改文件选择回调逻辑
在handle_selection中先将返回的Content URI转换为真实路径,再处理文件名和路径。
修改后的完整代码:
from kivy.app import App from kivy.uix.button import Button from kivy.uix.boxlayout import BoxLayout from kivy.uix.textinput import TextInput from plyer import filechooser import os from jnius import autoclass, cast # 初始化Android相关类 Context = autoclass('android.content.Context') DocumentsContract = autoclass('android.provider.DocumentsContract') MediaStore = autoclass('android.provider.MediaStore') Uri = autoclass('android.net.Uri') def get_real_path_from_uri(uri_str): """将Android的Content URI转换为真实文件路径""" uri = Uri.parse(uri_str) context = cast('android.content.Context', App.get_running_app()._android_activity) # 处理Document类型URI if DocumentsContract.isDocumentUri(context, uri): doc_id = DocumentsContract.getDocumentId(uri) # 处理媒体文件 if "com.android.providers.media.documents" == uri.getAuthority(): media_type, media_id = doc_id.split(":") content_uri = None if media_type == "image": content_uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI elif media_type == "video": content_uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI elif media_type == "audio": content_uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI selection = "_id=?" selection_args = [media_id] return get_data_column(context, content_uri, selection, selection_args) # 处理下载文件 elif "com.android.providers.downloads.documents" == uri.getAuthority(): content_uri = Uri.parse("content://downloads/public_downloads") selection = "_id=?" selection_args = [doc_id] return get_data_column(context, content_uri, selection, selection_args) # 处理普通Content URI elif "content" in uri.getScheme(): return get_data_column(context, uri, None, None) # 处理File URI elif "file" in uri.getScheme(): return uri.getPath() return None def get_data_column(context, uri, selection, selection_args): """从Cursor中获取数据列的值""" cursor = None column = "_data" projection = [column] try: cursor = context.getContentResolver().query(uri, projection, selection, selection_args, None) if cursor and cursor.moveToFirst(): index = cursor.getColumnIndexOrThrow(column) return cursor.getString(index) finally: if cursor: cursor.close() return None class MyApp(App): def __init__(self, **kwargs): super().__init__(**kwargs) self.file_paths = [] self.file_names = [] def build(self): layout = BoxLayout(orientation='vertical') button = Button(text='打开文件选择器', on_release=self.show_file_chooser) self.text_input = TextInput(readonly=True) layout.add_widget(button) layout.add_widget(self.text_input) return layout def show_file_chooser(self, *args): filechooser.open_file(on_selection=self.handle_selection, multiple=True) def handle_selection(self, selection): self.file_paths = [] self.file_names = [] for uri in selection: real_path = get_real_path_from_uri(uri) if real_path: self.file_paths.append(real_path) self.file_names.append(os.path.basename(real_path)) else: # 无法转换时用URI末尾作为文件名 self.file_paths.append(uri) self.file_names.append(uri.split('/')[-1]) print(self.file_paths) print(self.file_names) self.text_input.text = '\n'.join(self.file_names) if __name__ == '__main__': MyApp().run()
额外注意事项:
- 确保buildozer配置的
requirements包含jnius:requirements = python3,kivy,plyer,jnius - Android 13+中,
MANAGE_EXTERNAL_STORAGE权限需用户手动在应用设置中开启,可添加权限申请逻辑引导用户操作。
内容的提问来源于stack exchange,提问作者Edward Gyan
相关产品推荐
相关产品推荐

