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

Android端Kivy开发音乐下载App安装后启动崩溃求助

YouTube音乐下载App Android打包崩溃问题

我用Python的Kivy库开发了一款通过API从YouTube下载音乐的App,桌面运行main.py时功能完全正常,但用buildozer打包成APK安装到Android手机后,App显示图片后立即崩溃。

main.py代码

from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from googleapiclient.discovery import build
from pytube import YouTube
from kivymd.app import MDApp
from kivymd.uix.button import MDFillRoundFlatButton
from kivymd.uix.textfield import MDTextField
from kivymd.uix.list import OneLineListItem
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivymd.uix.boxlayout import MDBoxLayout
from kivy.uix.widget import Widget
from kivymd.theming import ThemeManager
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.uix.dialog import MDDialog
from kivy.app import App
import logging
import os


Builder.load_string('''
<RecycleViewRow@OneLineListItem>:
    text: ''
    video_id: ''
    on_release: app.on_video_selected(root.video_id)

<RV>:
    viewclass: 'RecycleViewRow'
    RecycleBoxLayout:
        default_size: None, dp(56)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        orientation: 'vertical'
''')

class RV(RecycleView):
    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        self.data = []

class MusicDownloaderApp(MDApp):
    def build(self):
        logging.info('Building the app')
        self.theme_cls.primary_palette = 'Teal'  
        self.theme_cls.primary_hue = '700'
        self.theme_cls.theme_style = 'Light'
        

        layout = MDBoxLayout(orientation='vertical', padding='32dp', spacing='20dp')

        # Add a BoxLayout at the top for the title/logo
        title_layout = MDBoxLayout(orientation='horizontal', size_hint=(1, None), height="50dp", padding='2dp', spacing='7dp' )
        layout.add_widget(title_layout)

        # Add a label as a title
        title = Label(text="Music Downloader", font_size='20sp')
        title_layout.add_widget(title)

        title_layout.add_widget(Widget())

        # Or add an image as a logo
        logo = Image(source="download.png", size_hint=(None, None), size=("80dp", "80dp"))
        title_layout.add_widget(logo)

        title_layout.add_widget(Widget())

        title_layout.add_widget(Widget())

        self.search_bar = MDTextField(
            hint_text="Enter song title or artist name",
            size_hint=(2, None),
            height="40dp"
        )
        layout.add_widget(self.search_bar)

        self.search_button = MDFillRoundFlatButton(
            text="Search",
            on_release=self.perform_search
        )
        layout.add_widget(self.search_button)

        self.results_list = RV()
        layout.add_widget(self.results_list)

        return layout

    def perform_search(self, instance):
        query = self.search_bar.text
        logging.info(f'Performing search with query {query}')
        if not query.strip():
            return

        api_key = '<my-api-key'
        youtube = build('youtube', 'v3', developerKey=api_key)

        request = youtube.search().list(
            part="snippet",
            maxResults=50,
            q=query
        )
        response = request.execute()

        results = []
        for item in response['items']:
            if item['id']['kind'] == "youtube#video":
                video_title = item['snippet']['title']
                video_id = item['id']['videoId']
                results.append({'text': video_title, 'video_id': video_id})

        self.results_list.data = results

    def on_video_selected(self, video_id):
        logging.info(f'Selected video with id {video_id}')
        youtube_url = f"[https://www.youtube.com/watch?v={video_id}]"
        youtube = YouTube(youtube_url)
         # Get the path to the app's internal storage directory
        app_path = App.get_running_app().user_data_dir

        # Define a subdirectory to save the downloads
        download_dir = os.path.join(app_path, 'downloads')

        # Create the download directory if it doesn't exist
        if not os.path.exists(download_dir):
            os.makedirs(download_dir)

        # Download the video to the download directory
        youtube.streams.filter(only_audio=True).first().download(download_dir)
        
        dialog = MDDialog(title='Finished Downloading', text='Enjoy your music!')
        dialog.open()


if __name__ == "__main__":
    MusicDownloaderApp().run()

buildozer.spec配置

[app]

# (str) Title of your application
title = Music Downloader

# (str) Package name
package.name = musicdownloader

# (str) Package domain (needed for android/ios packaging)
package.domain = org.test

# (str) Source code where the main.py live
source.dir = .

# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas

# (str) Application versioning (method 1)
version = 0.1

# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy,kivymd,pytube,google-api-python-client


# (str) Presplash of the application
presplash.filename = /content/download.png

# (str) Icon of the application
icon.filename = /content/download.png

#
# OSX Specific
#

#
# author = © Copyright Info

# change the major version of python used by the app
osx.python_version = 3

# Kivy version to use
osx.kivy_version = 1.9.1

#
# Android specific
#

# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0


# (list) Permissions
# (See https://python-for-android.readthedocs.io/en/latest/buildoptions/#build-options-1 for all the supported syntaxes and properties)
android.permissions = INTERNET,WRITE_EXTERNAL_STORAGE


#
# iOS specific
#

# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master

# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.10.0

# (bool) Whether or not to sign the code
ios.codesign.allowed = false

已尝试操作

  • 查看logcat日志,未找到崩溃相关具体错误
  • 确认buildozer.spec中已声明INTERNET和WRITE_EXTERNAL_STORAGE权限
  • 验证requirements包含kivy、kivymd、pytube、google-api-python-client等依赖
  • 确认APK安装过程无报错或警告

解决建议

1. 修复URL格式错误

on_video_selected方法里的YouTube URL被错误添加了方括号,导致pytube无法解析:

# 错误写法
youtube_url = f"[https://www.youtube.com/watch?v={video_id}]"
# 修正为
youtube_url = f"https://www.youtube.com/watch?v={video_id}"

2. 主线程避免网络操作

Android禁止在主线程执行网络请求,否则会触发崩溃或ANR。需要将搜索、下载逻辑放到子线程,更新UI时切回主线程:

import threading
from kivy.clock import Clock

# 重构搜索方法
def perform_search(self, instance):
    query = self.search_bar.text
    if not query.strip():
        return
    threading.Thread(target=self._search_thread, args=(query,), daemon=True).start()

def _search_thread(self, query):
    api_key = '<my-api-key>'  # 补全缺失的闭合>
    youtube = build('youtube', 'v3', developerKey=api_key)
    request = youtube.search().list(part="snippet", maxResults=50, q=query)
    response = request.execute()
    results = []
    for item in response['items']:
        if item['id']['kind'] == "youtube#video":
            results.append({
                'text': item['snippet']['title'],
                'video_id': item['id']['videoId']
            })
    # 切回主线程更新列表
    Clock.schedule_once(lambda dt: setattr(self.results_list, 'data', results))

# 重构下载方法
def on_video_selected(self, video_id):
    threading.Thread(target=self._download_thread, args=(video_id,), daemon=True).start()

def _download_thread(self, video_id):
    youtube_url = f"https://www.youtube.com/watch?v={video_id}"
    youtube = YouTube(youtube_url)
    download_dir = os.path.join(App.get_running_app().user_data_dir, 'downloads')
    os.makedirs(download_dir, exist_ok=True)
    youtube.streams.filter(only_audio=True).first().download(download_dir)
    # 切回主线程显示对话框
    Clock.schedule_once(lambda dt: self._show_dialog())

def _show_dialog(self):
    dialog = MDDialog(title='Finished Downloading', text='Enjoy your music!')
    dialog.open()

3. 修正API_KEY语法错误

代码中api_key = '<my-api-key'缺失了闭合的>,会导致API调用失败,补全为api_key = '<my-api-key>'(替换为你的实际API密钥)。

4. 优化buildozer.spec配置

  • 指定依赖版本:避免兼容性问题,比如:
    requirements = python3,kivy>=2.1.0,kivymd>=1.1.1,pytube>=15.0.0,google-api-python-client>=2.100.0,pycryptodome
    
  • 补充权限:添加必要的存储和网络状态权限:
    android.permissions = INTERNET,WRITE_EXTERNAL_STORAGE,READ_EXTERNAL_STORAGE,ACCESS_NETWORK_STATE
    
  • 修正资源路径:如果download.png和main.py在同一目录,将启动图和图标路径改为相对路径:
    presplash.filename = download.png
    icon.filename = download.png
    
  • 启用Python日志:便于排查问题:
    android.logcat_filters = *:S python:D
    

内容的提问来源于stack exchange,提问作者Wioletta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 18:54:57