痛点

病害导出时每条病害要生成一张雷达图截图。原来的实现是直接对界面上的 JavaFX 节点调 snapshot() 截图。问题是 snapshot() 必须在 FX Application Thread 执行,导出几十上百张图时,主线程被截图任务占满,整个 UI 完全卡死,进度条都不动。

思路

卡死的根因是渲染绑死在 UI 线程上。要彻底解决,就得让渲染脱离 JavaFX 节点,变成可以在任意线程跑的纯计算。于是把截图渲染从“拍 UI”改成“用 Java2D 纯数据画”。

改造

按 MVVM 三层切:

  • View 层:只负责从 UI 组件里提取纯数据(当前视口的里程范围、采样值数组、增益、颜色映射等),不参与画图。
  • ViewModel 层:负责渲染调度和参数构建,把 View 提取的数据组装成绘图参数。
  • Service 层:纯绘图,用 BufferedImage + Graphics2D 画雷达图、层位线、病害框、信息面板,完全不接触 JavaFX。
// Service 层,任意线程可跑
fun render(params: RenderParams): BufferedImage {
val img = BufferedImage(params.width, params.height, TYPE_INT_RGB)
val g = img.createGraphics()
drawRadar(g, params) // 画灰度图
drawOverlay(g, params) // 画层位线/病害框
drawInfoPanel(g, params) // 画底部信息面板
return img
}

收益

  • 渲染脱离 UI 线程,可以用协程并发跑,导出时 UI 完全不卡。
  • 不依赖界面状态,即使雷达图窗口没打开、界面尺寸不对也能正确出图(极端情况还能动态放大窗口或降级渲染)。
  • 速度比 snapshot() 还快,省去了 JavaFX 渲染管线的中转。

小结

UI 卡死这类问题,治本之道是把耗时的渲染 / 计算从 UI 线程上搬走,而不是想办法优化 UI 线程内的执行。Java2D 纯数据绘制是 JavaFX 场景下做“后台批量出图”的利器。