痛点

病害导出的时候,每条病害要生成一张雷达图截图。原来的做法是直接对着界面上那个 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 线程里那点执行。在 JavaFX 下要做”后台批量出图”,Java2D 这种纯数据绘制挺好使。