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

Kivy应用中android.permissions导入失败,Plyer文件选择器Android端失效

解决Kivy+Plyer FileChooser在Android端无法获取文件路径的问题

问题核心在于Android 10及以上版本的Scoped Storage机制:Plyer的filechooser.open_file返回的不是直接的文件系统路径,而是content://开头的内容URI,直接用os.path.basename无法解析,导致获取不到文件名和路径。

解决方案步骤:

  1. 添加必要权限
    在你的buildozer.spec文件中添加权限配置:

    android.permissions = READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, MANAGE_EXTERNAL_STORAGE
    

    注:MANAGE_EXTERNAL_STORAGE仅针对Android 13及以上版本,用于访问所有文件。

  2. 添加Content URI转真实路径的工具函数
    编写函数解析Android的Content URI,借助jnius调用Android原生API获取实际文件路径。

  3. 修改文件选择回调逻辑
    在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 18:25:01