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

Kivy+Buildozer安卓应用加载本地存储图片显示黑屏问题

问题核心成因
  • 安卓权限适配缺失:安卓6.0以上危险权限需要运行时动态申请,仅在buildozer.spec里声明不会自动生效;安卓10以上默认开启分区存储,安卓13以上READ_EXTERNAL_STORAGE权限废弃,读取公共存储图片需要单独申请READ_MEDIA_IMAGES权限,未正确授权时App根本读不到存储内的图片文件。
  • 文件路径解析错误:调用系统文件选择器返回的selection[0]在高版本安卓上是content://开头的Uri,不是本地文件系统的绝对路径,直接传给PIL.Image.open()或者Kivy Image的source属性无法读取到真实文件流。
  • 临时文件保存路径非法:你注释了拼接程序运行目录的代码,直接使用相对路径保存临时文件,安卓打包后App的源码目录是只读的,相对路径写入会直接失败,且你没有做异常捕获,感知不到写入失败的问题。
  • 组件未触发重载:Kivy Image组件在移动端动态替换source时,不会自动刷新纹理资源,没有主动调用reload()方法时,就算文件路径正确也可能显示黑屏。
  • 加载器配置顺序错误:os.environ['KIVY_IMAGE'] = 'pil,sdl2'如果放在Kivy模块导入之后设置,Pil图片加载器不会生效,也会导致加载失败。
修复步骤

1. 修改buildozer.spec配置

调整权限和兼容配置,适配全版本安卓:

# 补充安卓13需要的图片读取权限,开启安卓10旧版存储兼容
android.permissions = INTERNET,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE,READ_MEDIA_IMAGES
android.requestLegacyExternalStorage = True
# 依赖不需要调整,确认包含pillow、android、jnius即可
requirements = kivy,python3,pillow,plyer,jnius,android,datetime
source.include_exts = py,png,jpg,kv,atlas

2. 修正初始化逻辑

  • 把os.environ['KIVY_IMAGE'] = 'pil,sdl2'放在所有Kivy相关导入的最顶部
  • App启动时主动申请运行时权限
  • 不要使用源码相对路径读写文件,统一使用安卓提供的App私有目录

参考导入和初始化代码:

# 注意:这行必须放在所有kivy导入之前
import os
os.environ['KIVY_IMAGE'] = 'pil,sdl2'

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from android.storage import app_storage_path
from android.permissions import request_permissions, Permission
from jnius import autoclass
import PIL.Image as PIL_Image
from kivy.clock import Clock
from io import BytesIO
import random, string
from datetime import datetime

# 启动时申请运行时权限
request_permissions([
    Permission.READ_EXTERNAL_STORAGE,
    Permission.WRITE_EXTERNAL_STORAGE,
    Permission.READ_MEDIA_IMAGES
])

class MainPage(FloatLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.image_selected.source = ""
        self.label_selected.text = ""
        # 打包后随包安装的资源可正常加载,这部分逻辑不需要改
        self.image_selected.source = "Raindowntothestreet.png"

3. 重写图片选中处理逻辑

区分content Uri和普通文件路径,将图片复制到App私有缓存目录后再加载,主动触发组件重载:

def selected(self, selection):
        if not selection:
            return
        selected_uri = selection[0]
        # 获取App私有缓存目录,该目录App自带读写权限,不需要额外申请
        cache_dir = os.path.join(app_storage_path(), "cache")
        os.makedirs(cache_dir, exist_ok=True)

        # 生成唯一临时文件名
        current_dt = datetime.now()
        date_str = current_dt.strftime("%Y%m%d")
        time_str = current_dt.strftime("%H%M%S")
        c1 = random.choice(string.ascii_letters)
        c2 = random.choice(string.ascii_letters)
        temp_path = os.path.join(cache_dir, f"temp{date_str}{time_str}{c1}{c2}.png")

        # 处理content://格式的Uri
        if selected_uri.startswith("content://"):
            Uri = autoclass("android.net.Uri")
            PythonActivity = autoclass("org.kivy.android.PythonActivity")
            activity = PythonActivity.mActivity
            resolver = activity.getContentResolver()
            input_stream = resolver.openInputStream(Uri.parse(selected_uri))
            
            # 从输入流读取图片字节
            buffer = BytesIO()
            read_chunk = bytearray(4096)
            while True:
                read_len = input_stream.read(read_chunk)
                if read_len == -1:
                    break
                buffer.write(read_chunk[:read_len])
            input_stream.close()
            buffer.seek(0)
            img = PIL_Image.open(buffer)
        else:
            # 普通文件路径直接读取
            img = PIL_Image.open(selected_uri)
        
        # 保存图片到私有缓存目录
        img.save(temp_path)

        # 清空原有资源后重新赋值,主动触发重载避免黑屏
        self.image_selected.source = ""
        def refresh_img(dt):
            self.image_selected.source = temp_path
            self.image_selected.reload()
        Clock.schedule_once(refresh_img, 0.05)

        self.label_selected.text = f"Source: {selected_uri}"

4. 额外注意事项

  • 所有文件IO操作建议加try-except捕获异常并打印日志,方便定位读写失败问题
  • 临时文件不用时记得定期清理,避免占用用户存储空间
  • 如果需要适配安卓13以上的分区存储,不需要开启requestLegacyExternalStorage,直接使用READ_MEDIA_IMAGES权限+MediaStore API访问即可

内容的提问来源于stack exchange,提问作者杰多舍

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 10:30:35