Kivy无法切换至extracting屏幕,manager.current设置失效问题排查
问题描述
我开发的视频抽帧App中,调用get_frames函数时,本应切换至extracting屏幕显示抽帧状态,完成后再跳转至completed屏幕。但实际运行时,extracting屏幕从未显示,无任何报错,抽帧完成后直接跳转到completed屏幕。
原因分析
Kivy的UI渲染和事件处理都在主线程中执行。当前get_frames函数里,切换到extracting屏幕的指令之后,紧接着是耗时的视频抽帧循环(cv2读取帧、保存图片的操作),这部分代码会完全阻塞主线程。主线程被阻塞时,无法及时渲染extracting屏幕的UI内容,直到整个抽帧循环执行完毕,才会处理后续的屏幕切换指令,因此用户看不到extracting屏幕的过渡状态。
修复方案
将耗时的抽帧操作放到子线程中执行,让主线程专注于UI更新和事件响应。同时,更新UI元素(比如进度条)时,必须通过Kivy的Clock.schedule_once回到主线程操作(因为Kivy不允许在子线程中直接修改UI)。
修改后的核心代码
1. 补充导入模块
在代码顶部添加线程和Clock的导入:
from threading import Thread from kivy.clock import Clock
2. 重构get_frames方法
将抽帧逻辑拆分到子线程函数中,主线程只负责切换屏幕和触发子线程:
class GetFramesScreen(MDScreen): base_dir_for_frames = StringProperty(f"{os.path.join(os.path.expanduser('~'),'Desktop')}") def get_frames(self): # 立即切换到extracting屏幕,主线程优先渲染UI self.manager.transition.direction = 'right' self.manager.current = 'extracting' # 预准备抽帧参数 app = MDApp.get_running_app() app.file_name = str(os.path.basename(app.app_filepath)) saved_frame_dir = os.path.join(self.base_dir_for_frames, os.path.basename(app.app_filepath)).replace('.', '_').replace('\01', '01') save_dir = direxist(saved_frame_dir) print(f'保存路径 -> {save_dir}') vidcap = cv2.VideoCapture(app.app_filepath) fps = vidcap.get(cv2.CAP_PROP_FPS) app.fps = str(int(fps)) frame_count = vidcap.get(cv2.CAP_PROP_FRAME_COUNT) capduration = int(frame_count / fps) video_time = str(datetime.timedelta(seconds=capduration)) app.video_duration = str(video_time) # 启动子线程执行抽帧,避免阻塞主线程 Thread(target=self._extract_frames, args=(vidcap, save_dir, fps, frame_count), daemon=True).start() def _extract_frames(self, vidcap, save_dir, fps, total_frames): start_time = time.time() success, frame = vidcap.read() cont = 1 n_extracted_frames = 0 app = MDApp.get_running_app() while success: if cont == 1 or cont % int(fps) == 0: seconds1 = (vidcap.get(cv2.CAP_PROP_POS_MSEC)) / 1000 video_time2 = str(datetime.timedelta(seconds=seconds1)) x = video_time2.replace(':', '.') formatted = f"frame{cont}_{x}.jpg" dd = os.path.join(str(save_dir), formatted) cv2.imwrite(dd, frame) n_extracted_frames += 1 # 通过Clock回到主线程更新进度条 progress = (cont / total_frames) * 100 Clock.schedule_once(lambda dt: setattr(app, 'pb', progress)) success, frame = vidcap.read() cont += 1 vidcap.release() # 释放视频资源 end_time = time.time() - start_time # 抽帧完成后,回到主线程切换屏幕并更新统计数据 Clock.schedule_once(lambda dt: self._on_extract_complete(n_extracted_frames, end_time, save_dir)) def _on_extract_complete(self, n_extracted_frames, time_taken, save_dir): app = MDApp.get_running_app() app.extracted_total = str(n_extracted_frames) app.time_taken = str(datetime.timedelta(seconds=time_taken)) app.saved_path = str(save_dir) self.manager.transition.direction = 'right' self.manager.current = 'completed'
3. 进度条功能激活
原代码中pb变量已定义但未使用,修改后进度条会根据抽帧进度实时更新,让用户直观看到抽帧状态。
效果验证
修改后,点击"Get frames"按钮会立即切换到extracting屏幕,显示视频信息和实时更新的进度条,抽帧完成后自动跳转到completed屏幕,整个流程的UI状态过渡正常。
内容的提问来源于stack exchange,提问作者Abraham Márquez
相关产品推荐
相关产品推荐

