这是一个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()
答案 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()