# paint thread

> CrossFly 的线程模型 —— 在 paint thread 上单线程、onUI 以及按需渲染。

Source: https://crossfly.dev/zh/docs/concepts/the-paint-thread — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# paint thread

这大概是框架中最重要的一条规则，用来避免你的应用出错。

## CrossFly 在 paint thread 上是单线程的
**所有** UI 与渲染操作都发生在**单一一条线程**上 —— 即 *paint thread*。`Main`
始终在其上调用 `create()`、`render()` 和 `resize()`；每一个 `onShow`、`onPaint`、`onLayout`、触摸
回调以及组件变更都在其中运行。

**为什么做这个决定？** 简单与正确。只有一条执行线操作 UI，绘制路径上就**没有
锁，也没有被争用的共享状态**。代价是你必须遵守的一条规则：**绝不要从其他线程碰 UI**。

## 标准范式：重活在外，UI 用 `onUI` 回来
网络、数据库、文件、长时间计算 → **后台线程**。要用结果去**碰 UI**，就用
`Component.onUI(...)` 回到 paint thread：

```java
new Thread(() -> {
   List<Cliente> dados = repositorio.buscarTudo();   // 繁重工作，后台 thread
   Component.onUI(() -> {                             // 回到 paint thread
      lista.setItems(dados);                          // 现在可以安全操作 UI
      Component.repaint();                             // 请求新的一帧
   });
}).start();
```
此外还有 `Main.runOnPaintThread(Runnable)`（以及变体 `runOnPaintThreadAndWait(...)`）用于
在 `Component` 上下文之外达到同样目的。

> 违反该规则的典型症状：当你从网络/定时器回调内部"刷新屏幕"时，应用崩溃或
> 表现异常。修正办法始终是**用 `onUI(...)` 包裹对 UI 的操作**。

## 按需渲染
paint thread **不会持续重绘**。只有当有人调用
**`repaint()`**（内部为 `Gdx.graphics.requestRendering()`）时才会生成新帧。这能省电 —— 静止的
屏幕不消耗 GPU。

大多数组件的 setter 已经替你调用了 `repaint()`。需要记住的后果：
- **改了框架不知道的东西？** 你自己调用 `repaint()`。
- **动画？** 它需要在每一帧**保持渲染请求存活**（这正是动画器
  在 `onUpdate` 中所做的；参见 [值动画](/docs/ui/animations/value-animations)）。

<div class="cf-diagram">
<svg role="img" id="my-svg" width="803" height="86" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flowchart" style="max-width: 802.969px; background-color: transparent;" viewBox="0 0 802.96875 86" role="graphics-document document" aria-roledescription="flowchart-v2"><style>#my-svg{font-family:Manrope,system-ui,sans-serif;font-size:14px;fill:var(--text);}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#my-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#my-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#my-svg .error-icon{fill:var(--bg-elev);}#my-svg .error-text{fill:var(--text-dim);stroke:var(--text-dim);}#my-svg .edge-thickness-normal{stroke-width:1px;}#my-svg .edge-thickness-thick{stroke-width:3.5px;}#my-svg .edge-pattern-solid{stroke-dasharray:0;}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#my-svg .edge-pattern-dashed{stroke-dasharray:3;}#my-svg .edge-pattern-dotted{stroke-dasharray:2;}#my-svg .marker{fill:var(--text-dim);stroke:var(--text-dim);}#my-svg .marker.cross{stroke:var(--text-dim);}#my-svg svg{font-family:Manrope,system-ui,sans-serif;font-size:14px;}#my-svg p{margin:0;}#my-svg .label{font-family:Manrope,system-ui,sans-serif;color:var(--text);}#my-svg .cluster-label text{fill:var(--text-dim);}#my-svg .cluster-label span{color:var(--text-dim);}#my-svg .cluster-label span p{background-color:transparent;}#my-svg .label text,#my-svg span{fill:var(--text);color:var(--text);}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:var(--bg-soft);stroke:var(--border-strong);stroke-width:1px;}#my-svg .rough-node .label text,#my-svg .node .label text,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-anchor:middle;}#my-svg .node .katex path{fill:var(--text-dim);stroke:var(--text-dim);stroke-width:1px;}#my-svg .rough-node .label,#my-svg .node .label,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-align:center;}#my-svg .node.clickable{cursor:pointer;}#my-svg .root .anchor path{fill:var(--text-dim)!important;stroke-width:0;stroke:var(--text-dim);}#my-svg .arrowheadPath{fill:rgba(255, 255, 255, 0);}#my-svg .edgePath .path{stroke:var(--text-dim);stroke-width:1px;}#my-svg .flowchart-link{stroke:var(--text-dim);fill:none;}#my-svg .edgeLabel{background-color:var(--bg-elev);text-align:center;}#my-svg .edgeLabel p{background-color:var(--bg-elev);}#my-svg .edgeLabel rect{opacity:0.5;background-color:var(--bg-elev);fill:var(--bg-elev);}#my-svg .labelBkg{background-color:rgba(255, 255, 255, 0.5);}#my-svg .cluster rect{fill:var(--bg-elev);stroke:hsl(0, 0%, 90%);stroke-width:1px;}#my-svg .cluster text{fill:var(--text-dim);}#my-svg .cluster span{color:var(--text-dim);}#my-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Manrope,system-ui,sans-serif;font-size:12px;background:var(--bg-elev);border:1px solid hsl(0, 0%, 90%);border-radius:2px;pointer-events:none;z-index:100;}#my-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:var(--text);}#my-svg rect.text{fill:none;stroke-width:0;}#my-svg .icon-shape,#my-svg .image-shape{background-color:var(--bg-elev);text-align:center;}#my-svg .icon-shape p,#my-svg .image-shape p{background-color:var(--bg-elev);padding:2px;}#my-svg .icon-shape .label rect,#my-svg .image-shape .label rect{opacity:0.5;background-color:var(--bg-elev);fill:var(--bg-elev);}#my-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#my-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#my-svg .node .neo-node{stroke:var(--border-strong);}#my-svg [data-look="neo"].node rect,#my-svg [data-look="neo"].cluster rect,#my-svg [data-look="neo"].node polygon{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].swimlane.cluster rect{filter:none;}#my-svg [data-look="neo"].node path{stroke:url(#my-svg-gradient);stroke-width:1px;}#my-svg [data-look="neo"].node .outer-path{filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node .neo-line path{stroke:var(--border-strong);filter:none;}#my-svg [data-look="neo"].node circle{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node circle .state-start{fill:var(--text-dim);}#my-svg [data-look="neo"].icon-shape .icon{fill:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].icon-shape .icon-neo path{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg :root{--mermaid-font-family:Manrope,system-ui,sans-serif;}</style><g><marker id="my-svg_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="11.5" refY="7" markerUnits="userSpaceOnUse" markerWidth="10.5" markerHeight="14" orient="auto"><path d="M 0 0 L 11.5 7 L 0 14 z" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-pointStart-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="1" refY="7" markerUnits="userSpaceOnUse" markerWidth="11.5" markerHeight="14" orient="auto"><polygon points="0,7 11.5,14 11.5,0" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refY="5" refX="12.25" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-circleStart-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-2" refY="5" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="my-svg_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="17.7" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5;"/></marker><marker id="my-svg_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="-3.5" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5; stroke-dasharray: 1, 0;"/></marker><g class="root"><g class="clusters"/><g class="edgePaths"><path d="M157.266,43L163.908,43C170.549,43,183.833,43,196.451,43C209.068,43,221.018,43,226.993,43L232.969,43" id="my-svg-L_BG_PT_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_BG_PT_0" data-points="W3sieCI6MTU3LjI2NTYyNSwieSI6NDN9LHsieCI6MTk3LjExNzE4NzUsInkiOjQzfSx7IngiOjIzNi45Njg3NSwieSI6NDN9XQ==" data-look="classic" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M370.859,43L378.781,43C386.703,43,402.547,43,417.724,43C432.901,43,447.411,43,454.667,43L461.922,43" id="my-svg-L_PT_RR_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_PT_RR_0" data-points="W3sieCI6MzcwLjg1OTM3NSwieSI6NDN9LHsieCI6NDE4LjM5MDYyNSwieSI6NDN9LHsieCI6NDY1LjkyMTg3NSwieSI6NDN9XQ==" data-look="classic" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/><path d="M636.891,43L641.057,43C645.224,43,653.557,43,661.224,43C668.891,43,675.891,43,679.391,43L682.891,43" id="my-svg-L_RR_Frame_0" class="edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_RR_Frame_0" data-points="W3sieCI6NjM2Ljg5MDYyNSwieSI6NDN9LHsieCI6NjYxLjg5MDYyNSwieSI6NDN9LHsieCI6Njg2Ljg5MDYyNSwieSI6NDN9XQ==" data-look="classic" marker-end="url(#my-svg_flowchart-v2-pointEnd)"/></g><g class="edgeLabels"><g class="edgeLabel" transform="translate(197.1171875, 43)"><g class="label" data-id="L_BG_PT_0" transform="translate(-14.8515625, -10.5)"><foreignObject width="29.703125" height="21"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>onUI</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(418.390625, 43)"><g class="label" data-id="L_PT_RR_0" transform="translate(-22.53125, -10.5)"><foreignObject width="45.0625" height="21"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"><p>repaint</p></span></div></foreignObject></g></g><g class="edgeLabel"><g class="label" data-id="L_RR_Frame_0" transform="translate(0, 0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel"></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default" id="my-svg-flowchart-BG-0" data-look="classic" transform="translate(82.6328125, 43)"><rect class="basic label-container" style="" x="-74.6328125" y="-35" width="149.265625" height="70"/><g class="label" style="" transform="translate(-46.6328125, -21)"><rect/><foreignObject width="93.265625" height="42"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>后台线程<br />(网络/DB/计算)</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-PT-1" data-look="classic" transform="translate(303.9140625, 43)"><rect class="basic label-container" style="" x="-66.9453125" y="-24.5" width="133.890625" height="49"/><g class="label" style="" transform="translate(-38.9453125, -10.5)"><rect/><foreignObject width="77.890625" height="21"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>paint thread</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-RR-3" data-look="classic" transform="translate(551.40625, 43)"><rect class="basic label-container" style="" x="-85.484375" y="-24.5" width="170.96875" height="49"/><g class="label" style="" transform="translate(-57.484375, -10.5)"><rect/><foreignObject width="114.96875" height="21"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>requestRendering</p></span></div></foreignObject></g></g><g class="node default" id="my-svg-flowchart-Frame-5" data-look="classic" transform="translate(740.9296875, 43)"><rect class="basic label-container" style="" x="-54.0390625" y="-35" width="108.078125" height="70"/><g class="label" style="" transform="translate(-26.0390625, -21)"><rect/><foreignObject width="52.078125" height="42"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel"><p>下一帧<br />(绘制树)</p></span></div></foreignObject></g></g></g></g></g><defs><filter id="my-svg-drop-shadow" height="130%" width="130%"><feDropShadow dx="4" dy="4" stdDeviation="0" flood-opacity="0.06" flood-color="var(--text-dim)"/></filter></defs><defs><filter id="my-svg-drop-shadow-small" height="150%" width="150%"><feDropShadow dx="2" dy="2" stdDeviation="0" flood-opacity="0.06" flood-color="var(--text-dim)"/></filter></defs><linearGradient id="my-svg-gradient" gradientUnits="objectBoundingBox" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="var(--border-strong)" stop-opacity="1"/><stop offset="100%" stop-color="hsl(0, 0%, 90%)" stop-opacity="1"/></linearGradient></svg>
</div>

## 参见
- 相关：[渲染管线](/docs/concepts/rendering-pipeline) · [状态与数据流](/docs/concepts/state-and-data-flow)
- 下一篇：[Layout 与 Relative 模型](/docs/concepts/the-layout-and-relative-model)
