使用Kivy app下载文件而不锁定事件循环

时间:2016-05-28 17:46:05

标签: python kivy

这是一个Kivy应用程序,它有一个按钮和一个进度条。按下按钮时,将从Web下载ZIP文件并解压缩。进度条前进以标记下载的进度。

问题是,下载会锁定Kivy事件循环,在下载过程中冻结应用程序。如何在后台下载和解压缩文件?

from __future__ import division
import os
import requests
import zipfile
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout

ZIP_URL = 'https://www.python.org/ftp/python/3.5.1/python-3.5.1-embed-win32.zip'
ZIP_FILENAME = 'Python351.zip'

kv_string = """
<RootWidget>
    BoxLayout:
        orientation: "vertical"
        Button:
            id: download_button
            text: "Download content"
            on_press: self.parent.parent.download_content()
        ProgressBar:
            id: download_progress_bar
            max: 1
            value: 0.1
"""

Builder.load_string(kv_string)


class RootWidget(BoxLayout):
    def __init__(self, **kwargs):
        super(RootWidget, self).__init__(**kwargs)

    def download_content(self):
        self.ids["download_button"].disabled = True

        total_size_KiB = 6023182 / 1024 # DEBUG: hardcoded
        print("Downloading file: %s" % ZIP_URL)
        with open(ZIP_FILENAME, 'wb') as handle:
            response = requests.get(ZIP_URL, stream=True)

            if not response.ok:
                print("Something went wrong.")
                return

            current_size_KiB = 0
            for block in response.iter_content(1024):
                percent_downloaded = current_size_KiB / total_size_KiB
                if not current_size_KiB % 100:
                    print("%.2f%% downloaded so far" % (percent_downloaded * 100))
                self.ids['download_progress_bar'].value = percent_downloaded

                handle.write(block)

                current_size_KiB += 1

        self.unzip_content()

    def unzip_content(self):
        print("Unzipping file")
        fh = open(ZIP_FILENAME, 'rb')
        z = zipfile.ZipFile(fh)
        ZIP_EXTRACT_FOLDER = ZIP_FILENAME + '_extracted'
        if not os.path.exists(ZIP_EXTRACT_FOLDER):
            os.makedirs(ZIP_EXTRACT_FOLDER)
        z.extractall(ZIP_EXTRACT_FOLDER)
        fh.close()
        os.remove(ZIP_FILENAME)

        print("Done")


class MyApp(App):

    def build(self):
        return RootWidget()


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

1 个答案:

答案 0 :(得分:4)

首先,正如@Nykakin建议的那样,要使下载异步,请使用kivy.network.urlrequest.UrlRequest

def download_content(self):
    self.ids["download_button"].disabled = True
    req = UrlRequest(ZIP_URL, on_progress=self.update_progress,
                     chunk_size=1024, on_success=self.unzip_content,
                     file_path=ZIP_FILENAME)

def update_progress(self, request, current_size, total_size):
    self.ids['download_progress_bar'].value = current_size / total_size

其次,正如@KeyWeeUsr建议的那样,更改解压缩方法,以便它不会阻止事件循环,使用threading模块:

def unzip_content(self, req, result):
    threading.Thread(target=self.unzip_thread).start()

def unzip_thread(self):
    # ... (same as unzip_content method in question)

这是全新版本:

from __future__ import division
import os
import zipfile
import threading
import time
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.network.urlrequest import UrlRequest

ZIP_URL = 'https://www.python.org/ftp/python/3.5.1/python-3.5.1-embed-win32.zip'
ZIP_FILENAME = 'Python351.zip'

kv_string = """
<RootWidget>
    BoxLayout:
        orientation: "vertical"
        Button:
            id: download_button
            text: "Download content"
            on_press: self.parent.parent.download_content()
        ProgressBar:
            id: download_progress_bar
            max: 1
            value: 0
"""

Builder.load_string(kv_string)


class RootWidget(BoxLayout):

    stop = threading.Event()    

    def __init__(self, **kwargs):
        super(RootWidget, self).__init__(**kwargs)

    def download_content(self):
        self.ids["download_button"].disabled = True
        req = UrlRequest(ZIP_URL, on_progress=self.update_progress,
                         chunk_size=1024, on_success=self.unzip_content,
                         file_path=ZIP_FILENAME)

    def update_progress(self, request, current_size, total_size):
        self.ids['download_progress_bar'].value = current_size / total_size

    def unzip_content(self, req, result):
        threading.Thread(target=self.unzip_thread).start()

    def unzip_thread(self):
        print("Unzipping file")
        fh = open(ZIP_FILENAME, 'rb')
        z = zipfile.ZipFile(fh)
        ZIP_EXTRACT_FOLDER = ZIP_FILENAME + '_extracted'
        if not os.path.exists(ZIP_EXTRACT_FOLDER):
            os.makedirs(ZIP_EXTRACT_FOLDER)
        z.extractall(ZIP_EXTRACT_FOLDER)
        fh.close()
        os.remove(ZIP_FILENAME)
        time.sleep(4) # DEBUG: stretch out the unzip method to test threading

        print("Done")


class MyApp(App):

    def on_stop(self):
        # The Kivy event loop is about to stop, set a stop signal;
        # otherwise the app window will close, but the Python process will
        # keep running until all secondary threads exit.
        self.root.stop.set()        

    def build(self):
        return RootWidget()


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