这是一个带有按钮和进度条的 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()
Run Code Online (Sandbox Code Playgroud)
首先,正如@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
Run Code Online (Sandbox Code Playgroud)
其次,正如@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)
Run Code Online (Sandbox Code Playgroud)
这是完整的新版本:
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()
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1207 次 |
| 最近记录: |