Python 到 Android 的媒体访问权限

问题描述 投票:0回答:1

上周我一直在使用 Kivy 和 KivyMD 进行编程,我尝试开发一个具有图像选择器的应用程序。我遇到的问题是,无论我使用什么,它都不会显示我手机的媒体,也不会请求访问权限。

我使用 FileChooserIconView 创建一个显示手机文件的新屏幕。我在 MDApp 的 on_start 中尝试使用此代码:

if platform == 'android':
    from android.permissions import request_permissions, Permission
    request_permissions([Permission.READ_EXTERNAL_STORAGE, Permission.READ_MEDIA_IMAGES])

但它一直只显示文件夹而不显示文件。 我希望它显示所有内容,目前它只显示文件夹。

这是main.py:

from kivy.uix.screenmanager import ScreenManager
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.label import MDLabel
from kivy.uix.image import Image
from kivy.uix.gridlayout import GridLayout
from kivy import platform
import json
import os
import unicodedata


class MainScreen(MDScreen):
    def normalizar_texto(self, texto):
        # Normaliza el texto: elimina tildes y convierte a minúsculas
        texto_sin_tildes = unicodedata.normalize('NFKD', texto).encode('ASCII', 'ignore').decode('utf-8')
        return texto_sin_tildes.lower().strip()
    def load_beasts(self):
        # Load characters from a JSON file and add them to the screen
        if os.path.exists('beasts.json'):
            with open('beasts.json', 'r') as f:
                beasts = json.load(f)
                return beasts
        else:
            return [{"id": 0, "name": "Error", "image": "", "description": "No existe archivo"}]

    def on_search_button(self):
        # Get the text from TextInput when the button is pressed
        search_text = self.ids.search_input.text
        dex = self.load_beasts()
        if search_text.strip().isdigit():
            result = [beast for beast in dex if beast['id'] == int(search_text)]
        elif self.normalizar_texto(search_text) == 'abel':
            result = [{"id": "???", "image": "images/abel.jpeg","name": "Abel Cid Outlaw", 
                "description": "La criatura más cruel y despiadada de todo el multiverso. No teme a nada, no aprecia a nadie. Solo desea ver como el mundo arde bajo sus pies."}]
        else:
            result = [beast for beast in dex if self.normalizar_texto(beast['name']) == self.normalizar_texto(search_text)]

        if result:
            self.mostrar_informacion(result[0])

    def mostrar_informacion(self, beast):
        # Asumimos que hay etiquetas para mostrar los datos en la interfaz
        self.ids.name_label.text = f"{beast['id']}. {beast['name']}"
        self.ids.beast_img.source = beast['image']
        self.ids.beast_img.height = 500
        #self.ids.beast_img.reload()
        self.ids.descrip_label.text= f"Descripción: {beast['description']}"

    def load_beasts_db(self):
        db_screen = self.manager.get_screen('db')
        beasts = self.load_beasts()
        for beast in beasts:
            db_screen.add_beast_widget(beast)

class DBScreen(MDScreen):
    def add_beast_widget(self, beast_data):
        main_screen = self.manager.get_screen('main')
        label = MDLabel(
            text= f"{beast_data['id']}.{beast_data['name']}",
            font_style="H5",
            halign="center",
            theme_text_color="Custom",
            text_color= (0, 0, 0, 1) 
        )
        img = Image(
            source=beast_data['image'],
            size=(200,200),
            allow_stretch=True,
            keep_ratio=False,
            size_hint_y=None,
            height=200
        )
        grid=GridLayout(
            cols= 1,
            spacing= 30,
            padding= 20,
            size_hint_y=None,
            height=250
        )
        grid.add_widget(img)
        grid.add_widget(label)
        main_screen.ids.beast_list.add_widget(grid)

class AddScreen(MDScreen):
    def open_file_chooser(self):
        # Cambia a la pantalla de FileChooser
        main_screen = self.manager.get_screen('main')
        main_screen.ids.beast_name.text = main_screen.ids.beast_name.text
        main_screen.ids.beast_description.text = main_screen.ids.beast_description.text
        self.manager.current = "file_chooser"

    def save_beast(self):
        db_screen = self.manager.get_screen('db')
        main_screen = self.manager.get_screen('main')
        id = len(main_screen.load_beasts()) + 1
        name = main_screen.ids.beast_name.text
        image = main_screen.ids.image_display.source
        description = main_screen.ids.beast_description.text

        beast_data = {
            'id': id,
            'name': name,
            'image': image,
            'description': description
        }

        # Save character to the list
        self.save_beasts_to_file(beast_data)

        # Add button to the main screen dynamically
        db_screen.add_beast_widget(beast_data)

        # Clear the inputs after saving
        main_screen.ids.beast_name.text = ''
        main_screen.ids.image_display.source = ''
        main_screen.ids.beast_description.text = ''

        # Switch back to the main screen
        self.manager.current = 'main'

    def save_beasts_to_file(self, new_beast):
        beasts = []

        # Load existing characters from file if it exists
        if os.path.exists('beasts.json'):
            with open('beasts.json', 'r') as f:
                beasts = json.load(f)

        # Add the new character
        beasts.append(new_beast)

        # Save updated character list to file
        with open('beasts.json', 'w') as f:
            json.dump(beasts, f)

class FileChooserScreen(MDScreen):
    def select_image(self, selection):
        # Verifica si hay un archivo seleccionado
        if selection:
            main_screen = self.manager.get_screen("main")
            main_screen.ids.image_display.source = selection[0]
            self.manager.current = "main"

class BeastDexApp(MDApp):
    def on_start(self):
        # Llama a load_beasts_db solo al iniciar la aplicación
        main_screen = self.root.get_screen('main')
        main_screen.load_beasts_db()
        if platform == 'android':
            from android.permissions import request_permissions, Permission
            request_permissions([Permission.READ_EXTERNAL_STORAGE, Permission.READ_MEDIA_IMAGES])

    def build(self):
        sm = ScreenManager()
        sm.add_widget(MainScreen(name='main'))
        sm.add_widget(DBScreen(name='db'))
        sm.add_widget(AddScreen(name='add'))
        sm.add_widget(FileChooserScreen(name='file_chooser'))
        return sm

if __name__ == '__main__':
    BeastDexApp().run()

这是.kv 文件:

#:import platform kivy.utils.platform

<MainScreen>:
    canvas.before:
        Color:
            rgba: 1, 1, 1, 1
        Rectangle:
            size: self.size
            pos: self.pos
            source: 'img.jpg'

    MDNavigationLayout:
        MDScreenManager:
            id: screen_manager

            MDScreen:
                # Main background image
                name: 'main'
                FloatLayout:
                    # Menu icon
                    MDIconButton:
                        icon: "menu"
                        pos_hint: {"center_x": 0.05, "center_y": 0.95}
                        on_release: nav_drawer.set_state("toggle")

                    MDLabel:
                        text: 'BeastDex'
                        theme_text_color: "Custom"
                        text_color: 1, 1, 1, 1
                        font_style: "H1"
                        pos_hint: {'top': 0.95, 'center_x': 0.5}
                        halign: "center"
                        font_name: 'Amores Free Font.ttf'
                        size_hint_y: None
                        height: dp(60)

                    # Main content layout
                    BoxLayout:
                        orientation: 'vertical'
                        padding: [20, 200, 20, 20]
                        spacing: dp(20)
                        size_hint_y: 1

                        # Search bar layout
                        BoxLayout:
                            orientation: 'horizontal'
                            padding: dp(10)
                            spacing: dp(10)
                            size_hint_y: None
                            height: dp(50)

                            MDTextField:
                                id: search_input
                                hint_text: "Buscar bestia"
                                size_hint_x: 0.8
                                height: dp(50)
                                multiline: False

                            MDRaisedButton:
                                text: "Search"
                                size_hint_x: 0.2
                                height: dp(50)
                                on_release: root.on_search_button()

                        # Data layout for result display
                        ScrollView:
                            do_scroll_x: False

                            GridLayout:
                                cols: 1
                                spacing: dp(30)
                                padding: dp(20)
                                size_hint_y: None
                                height: self.minimum_height

                                # Display name label
                                MDLabel:
                                    id: name_label
                                    text: ""
                                    font_style: "H5"
                                    halign: "center"
                                    theme_text_color: "Custom"
                                    text_color: 0, 0, 0, 1 

                                # Beast image display
                                Image:
                                    id: beast_img
                                    source: ''
                                    size_hint_y: None
                                    height: 0
                                    allow_stretch: True
                                    keep_ratio: True

                                # Description label
                                MDLabel:
                                    id: descrip_label
                                    text: ""
                                    font_style: "Body1"
                                    halign: "center"
                                    theme_text_color: "Custom"
                                    text_color: 0, 0, 0, 1

            MDScreen:
                name: 'db'
                BoxLayout:
                    orientation: 'vertical'
                    MDIconButton:
                        icon: "menu"
                        pos_hint: {"center_x": 0.05, "center_y": 0.95}
                        on_release: nav_drawer.set_state("toggle")
                    
                    MDLabel:
                        text: 'BeastDex'
                        theme_text_color: "Custom"
                        text_color: 1, 1, 1, 1
                        font_style: "H1"
                        pos_hint: {'top': 0.95, 'center_x': 0.5}
                        halign: "center"
                        font_name: 'Amores Free Font.ttf'
                        size_hint_y: None
                        height: dp(60)

                    ScrollView:
                        do_scroll_x: False  # Disable horizontal scrolling if not needed

                        GridLayout:
                            id: beast_list
                            cols: 2
                            spacing: dp(10)
                            padding: [dp(40), ]
                            size_hint_y: None
                            height: self.minimum_height

            MDScreen:
                name: 'add'
                BoxLayout:
                    orientation: 'vertical'
                    MDIconButton:
                        icon: "menu"
                        pos_hint: {"center_x": 0.05, "center_y": 0.95}
                        on_release: nav_drawer.set_state("toggle")
                    
                    MDLabel:
                        text: 'BeastDex'
                        theme_text_color: "Custom"
                        text_color: 1, 1, 1, 1
                        font_style: "H1"
                        pos_hint: {'top': 0.95, 'center_x': 0.5}
                        halign: "center"
                        font_name: 'Amores Free Font.ttf'
                        size_hint_y: None
                        height: dp(60)

                    ScrollView:
                        size_hint: 1, 0.85  # Make it take up the remaining space
                        pos_hint: {'top': 0.85}
                        do_scroll_x: False  # Disable horizontal scrolling

                        BoxLayout:
                            orientation: 'vertical'
                            size_hint_y: None
                            height: self.minimum_height
                            padding: [20, 20, 20, 20]  # Add some padding for spacing
                            spacing: 20  # Add some space between elements

                            Label:
                                text: 'Nombre'
                                color: 0, 0, 0, 1
                                halign: "center"

                            TextInput:
                                id: beast_name
                                hint_text: ""
                                multiline: False
                                size_hint_y: None
                                height: 60
                            
                            Label:
                                text: 'Imagen'
                                color: 0, 0, 0, 1
                                halign: "center"
                            
                            MDRaisedButton:
                                text: "Seleccionar Imagen"
                                pos_hint: {"center_x": 0.5}
                                on_release: app.root.get_screen('add').open_file_chooser()
                            
                            Image:
                                id: image_display
                                source: ""
                                size_hint_y: None
                                height: 50
                                allow_stretch: True
                                keep_ratio: True
                            
                            Label:
                                text: 'Descripción'
                                color: 0, 0, 0, 1
                                halign: "center"

                            TextInput:
                                id: beast_description
                                hint_text: ""
                                multiline: False
                                size_hint_y: None
                                height: 60

                            Button:
                                text: "Save Beast"
                                size_hint_y: None
                                height: 70
                                on_release: app.root.get_screen('add').save_beast()


        MDNavigationDrawer:
            id: nav_drawer
            BoxLayout:
                orientation: 'vertical'
                size_hint_y: None
                spacing: 10
                height: self.minimum_height  # Asegura que el tamaño se ajuste al contenido
                pos_hint: {'top': 1}

                MDLabel:
                    text: "Enciclopedia:\n Las Bestias del Multiverso"
                    theme_text_color: 'Custom'
                    text_color: 0, 0, 0, 1
                    halign: 'center'
                    font_style: 'H6'
                    size_hint_y: None
                    height: self.texture_size[1]

                # Línea separadora
                Widget:
                    size_hint_y: None
                    height: dp(3)  # Altura de la línea separadora
                    canvas:
                        Color:
                            rgba: 0.7, 0.7, 0.7, 1  # Color de la línea
                        Rectangle:
                            size: self.size
                            pos: self.pos

                MDList:
                    OneLineIconListItem:
                        text: "Buscador"
                        theme_text_color: 'Custom'
                        text_color: 0, 0, 0, 1.0
                        on_release: 
                            nav_drawer.set_state("close")
                            screen_manager.current = 'main'

                    OneLineIconListItem:
                        text: "Todas las Bestias"
                        theme_text_color: 'Custom'
                        text_color: 0, 0, 0, 1.0
                        on_release:    
                            nav_drawer.set_state("close")
                            screen_manager.current = 'db'

                    OneLineIconListItem:
                        text: "Añadir Bestia"
                        theme_text_color: 'Custom'
                        text_color: 0, 0, 0, 1.0
                        on_release:    
                            nav_drawer.set_state("close")
                            screen_manager.current = 'add'

<FileChooserScreen>:
    BoxLayout:
        orientation: 'vertical'
        canvas.before:
            Color:
                rgba: 0.5, 0.5, 0.5, 1  # Color de fondo (modifícalo a tu gusto)
            Rectangle:
                pos: self.pos
                size: self.size
        FileChooserIconView:
            rootpath: '/storage/emulated/0/' if platform == 'android' else '/'
            id: filechooser
            on_selection: root.select_image(filechooser.selection)
android file permissions kivy kivymd
1个回答
0
投票

根据 python-for-android 文档。

如果你想存储和检索数据,你不应该只保存到 当前目录,而不是硬编码 /sdcard/ 或其他路径

  • 每个设备可能会有所不同。

相反,您可以将 Android 模块添加到您的需求中 允许您查询最常用的路径:

从 android.storage 导入 app_storage_path 设置_path = 应用程序_存储_路径()

从 android.storage 导入primary_external_storage_path Primary_ext_storage = Primary_external_storage_path()

从 android.storage 导入 secondary_external_storage_path secondary_ext_storage = secondary_external_storage_path()

代替:

FileChooserIconView:
    rootpath: '/storage/emulated/0/' if platform == 'android' else '/'
    id: filechooser
    on_selection: root.select_image(filechooser.selection)

尝试:

FileChooserIconView:
    rootpath: app.path() # App class
    id: filechooser
    on_selection: root.select_image(filechooser.selection)

由于您从 Android 文件获取图像:

def path(self):
    from android.storage import primary_external_storage_path
    ext_storage = primary_external_storage_path()
    pictures_path = os.path.join(ext_storage, 'DCIM', 'Images') # Put your image path here

此外,使用 Permission.WRITE_EXTERNAL_STORAGE 时没有显示权限请求的原因是因为根据 android 文档:

如果您的应用程序位于运行 API 级别 19 或更高级别的设备上,则无需声明此权限即可在 Context.getExternalFilesDir(String) 和 Context.getExternalCacheDir() 返回的应用程序特定目录中读取和写入文件.

© www.soinside.com 2019 - 2024. All rights reserved.