Skip to Content
Flutter⚡ Nâng cao🔄 Rendering Pipeline (Chi tiết)

Flutter Rendering Pipeline - Phân tích chuyên sâu

Bài viết này đi sâu vào từng giai đoạn của Rendering Pipeline trong Flutter - quy trình chuyển đổi widgets thành pixels hiển thị trên màn hình.


1. Tổng quan Pipeline

Flutter rendering pipeline bao gồm 7 giai đoạn chính, thực hiện tuần tự trong mỗi frame:

┌─────────────────────────────────────────────────────────────────────────────────┐ │ FLUTTER RENDERING PIPELINE │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌───────────┐ ┌───────┐ ┌────────┐ ┌───────┐ │ │ │ User │ │Animation │ │ Build │ │ Layout │ │ Paint │ │ │ │ Input │──▶│ Tick │──▶│ Phase │──▶│ Phase │──▶│ Phase │──▶ │ │ └──────────┘ └───────────┘ └───────┘ └────────┘ └───────┘ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ ──▶ │ Compositing │──▶│ Rasterize │──▶ GPU ──▶ Display │ │ │ Phase │ │ Phase │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ ◀────────────────── Dart Framework ──────────────────▶│◀── Engine/GPU ──▶ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

Thời gian mục tiêu:

  • 60 FPS: 16.67ms per frame
  • 120 FPS: 8.33ms per frame

Nếu pipeline mất quá lâu → frame dropjank.


2. User Input Phase

2.1 Luồng xử lý

Khi người dùng tương tác (chạm, vuốt, gõ), hệ điều hành gửi raw events đến Flutter Engine thông qua Platform Embedder.

┌─────────────────────────────────────────────────────────────────────────────────┐ │ USER INPUT FLOW │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────┐ │ │ │ Hardware │ │ │ │ (Touchscreen) │ │ │ └───────┬───────┘ │ │ │ Raw touch data (x, y, pressure, timestamp) │ │ ▼ │ │ ┌───────────────┐ │ │ │ OS Layer │ │ │ │ (Android/iOS) │ │ │ └───────┬───────┘ │ │ │ MotionEvent / UITouch │ │ ▼ │ │ ┌───────────────┐ │ │ │ Platform │ │ │ │ Embedder │ │ │ └───────┬───────┘ │ │ │ flutter::PointerDataPacket │ │ ▼ │ │ ┌───────────────┐ │ │ │ Flutter Engine│ │ │ │ (dart:ui) │ │ │ └───────┬───────┘ │ │ │ PointerEvent (Dart object) │ │ ▼ │ │ ┌───────────────┐ │ │ │ Gesture │ │ │ │ Recognition │ ← GestureDetector, Listener, etc. │ │ └───────┬───────┘ │ │ │ onTap, onPan, onDrag, etc. │ │ ▼ │ │ ┌───────────────┐ │ │ │ setState() │ ← Trigger rebuild │ │ └───────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

2.2 Hit Testing

Flutter thực hiện hit testing để xác định widget nào nhận event:

┌─────────────────────────────────────────────────────────────────────────────────┐ │ HIT TESTING │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Tap at (150, 200) │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────┐ │ │ │ RenderView │ ← Bắt đầu từ root │ │ │ hitTest(result, position) │ │ │ └───────────────┬─────────────────┘ │ │ │ │ │ ▼ (150, 200) có trong bounds? │ │ ┌─────────────────────────────────┐ │ │ │ RenderPositionedBox │ │ │ │ hitTestChildren → hitTestSelf │ │ │ └───────────────┬─────────────────┘ │ │ │ │ │ ▼ Tiếp tục đệ quy │ │ ┌─────────────────────────────────┐ │ │ │ RenderFlex │ │ │ └───────────────┬─────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────┐ │ │ │ RenderDecoratedBox (Button) │ ← HIT! Thêm vào HitTestResult │ │ └─────────────────────────────────┘ │ │ │ │ Kết quả: HitTestResult chứa danh sách RenderObjects │ │ theo thứ tự từ innermost → outermost │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

3. Animation Phase

3.1 Vsync và SchedulerBinding

Animations được driven bởi vsync signal từ display:

┌─────────────────────────────────────────────────────────────────────────────────┐ │ ANIMATION TICK │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Display Hardware │ │ │ │ │ │ VSync signal (every 16.67ms @ 60Hz) │ │ ▼ │ │ ┌─────────────────────────────────┐ │ │ │ SchedulerBinding │ │ │ │ handleBeginFrame(timestamp) │ │ │ └───────────────┬─────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────┐ │ │ │ Transient Callbacks │ ← Ticker.tick() │ │ │ (Animation frame callbacks) │ │ │ └───────────────┬─────────────────┘ │ │ │ │ │ Cho mỗi AnimationController đang active: │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────┐ │ │ │ Calculate new animation value │ │ │ │ based on: │ │ │ │ • elapsed time │ │ │ │ • duration │ │ │ │ • curve (easing function) │ │ │ └───────────────┬─────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────┐ │ │ │ Notify listeners │ │ │ │ → AnimatedBuilder.build() │ │ │ │ → setState() in some cases │ │ │ └─────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

3.2 Curves và Interpolation

┌─────────────────────────────────────────────────────────────────────────────────┐ │ ANIMATION VALUE CALCULATION │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ t = elapsedTime / totalDuration (0.0 → 1.0) │ │ │ │ Linear: value = t │ │ │ │ EaseIn: value = t² │ │ │ │ EaseOut: value = 1 - (1-t)² │ │ │ │ EaseInOut: value = t < 0.5 ? 2t² : 1 - (-2t+2)²/2 │ │ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ 1.0 ─────────────────────────────────★ │ │ │ │ │ ★ │ ← EaseOut │ │ │ │ ★ │ │ │ │ │ ★ │ │ │ │ 0.5 │ ★ │ │ │ │ │ ★ │ │ │ │ │ ★ │ │ │ │ │ ★ │ │ │ │ 0.0 ★───────────────────────────────── │ │ │ │ 0.0 1.0 │ │ │ │ t │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ Tween interpolation: │ │ finalValue = begin + (end - begin) * curvedValue │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

4. Build Phase - Chi tiết

4.1 Khi nào Build được trigger?

┌─────────────────────────────────────────────────────────────────────────────────┐ │ BUILD TRIGGERS │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ StatefulWidget: │ │ ┌───────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ 1. setState() called │ │ │ │ └── Element được đánh dấu dirty │ │ │ │ └── Scheduled trong next frame │ │ │ │ │ │ │ │ 2. didUpdateWidget() - parent rebuild với config mới │ │ │ │ │ │ │ │ 3. didChangeDependencies() - InheritedWidget thay đổi │ │ │ │ │ │ │ │ 4. Widget được reinsert sau khi deactivate │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ │ │ StatelessWidget: │ │ ┌───────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ 1. Lần đầu insert vào tree │ │ │ │ │ │ │ │ 2. Parent rebuild và tạo widget mới với config khác │ │ │ │ (canUpdate() returns false → tạo Element mới) │ │ │ │ │ │ │ │ 3. InheritedWidget dependency thay đổi │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

4.2 Dirty Elements và BuildOwner

┌─────────────────────────────────────────────────────────────────────────────────┐ │ BUILD OWNER MECHANISM │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Khi setState() được gọi: │ │ │ │ 1. Element.markNeedsBuild() │ │ │ │ │ ▼ │ │ 2. BuildOwner.scheduleBuildFor(element) │ │ │ │ │ │ ┌─────────────────────────────────────────┐ │ │ │ │ _dirtyElements List │ │ │ │ ├─────────────────────────────────────────┤ │ │ │ │ [Element A, Element C, Element G, ...] │ │ │ │ └─────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ 3. SchedulerBinding.ensureVisualUpdate() │ │ └── Request new frame nếu chưa scheduled │ │ │ │ Trong frame tiếp theo: │ │ │ │ 4. BuildOwner.buildScope() │ │ │ │ │ │ Sort _dirtyElements theo DEPTH (shallow → deep) │ │ │ │ │ │ Tại sao sort theo depth? │ │ │ → Parent build trước có thể thay đổi children │ │ │ → Tránh build child 2 lần │ │ │ │ │ ▼ │ │ 5. Lặp qua từng dirty element: │ │ │ │ │ │ for element in _dirtyElements: │ │ │ if element.dirty: │ │ │ element.rebuild() │ │ │ │ │ ▼ │ │ 6. Clear _dirtyElements │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

4.3 Widget → Element Reconciliation

┌─────────────────────────────────────────────────────────────────────────────────┐ │ ELEMENT RECONCILIATION │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Khi element.rebuild() được gọi: │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────┐ │ │ │ 1. Gọi widget.build(context) → newWidget │ │ │ │ │ │ │ │ 2. So sánh oldWidget vs newWidget │ │ │ │ │ │ │ │ Widget.canUpdate(oldWidget, newWidget): │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ return oldWidget.runtimeType == newWidget.runtimeType │ │ │ │ │ │ && oldWidget.key == newWidget.key; │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ 3. Nếu canUpdate == true: │ │ │ │ └── Reuse existing Element │ │ │ │ └── element.update(newWidget) │ │ │ │ └── Đệ quy xuống children │ │ │ │ │ │ │ │ 4. Nếu canUpdate == false: │ │ │ │ └── Deactivate old Element │ │ │ │ └── Create new Element từ newWidget │ │ │ │ └── Mount new Element │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ │ │ Ví dụ: │ │ │ │ Old: Container(color: red) New: Container(color: blue) │ │ │ │ │ │ └── Same type, same key (null) │ │ │ └── canUpdate = TRUE ──────────┘ │ │ └── Element REUSED, chỉ update color property │ │ │ │ Old: Container(...) New: Text('...') │ │ │ │ │ │ └── Different type ────────────┘ │ │ └── canUpdate = FALSE │ │ └── Old Element DESTROYED, new Element CREATED │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

4.4 Widget Explosion

Một điều quan trọng: code bạn viết không phải widget tree thật!

┌─────────────────────────────────────────────────────────────────────────────────┐ │ WIDGET EXPANSION │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Code bạn viết: │ │ ┌───────────────────────────────────────────────────────────────────────────┐ │ │ │ Container( │ │ │ │ color: Colors.blue, │ │ │ │ child: Text('Hello'), │ │ │ │ ) │ │ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ │ │ Widget tree thực tế sau khi build(): │ │ ┌───────────────────────────────────────────────────────────────────────────┐ │ │ │ Container │ │ │ │ └── Align (nếu có alignment) │ │ │ │ └── Padding (nếu có padding) │ │ │ │ └── DecoratedBox (nếu có decoration) │ │ │ │ └── ColoredBox (color != null) │ │ │ │ └── ConstrainedBox (nếu có constraints) │ │ │ │ └── Text │ │ │ │ └── RichText │ │ │ │ └── ... (more) │ │ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ │ │ → Dev Tools sẽ hiển thị tree sâu hơn code bạn viết │ │ → Đây là normal behavior, không phải bug │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

5. Layout Phase - Chi tiết

5.1 Constraints Protocol

┌─────────────────────────────────────────────────────────────────────────────────┐ │ LAYOUT PROTOCOL │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ "Constraints go down. Sizes go up. Parent sets position." │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────┐ │ │ │ PARENT │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ 1. Gọi child.layout(constraints, parentUsesSize: true) │ │ │ │ │ │ Constraints: minW=0, maxW=300, minH=0, maxH=∞ │ │ │ │ │ └────────────────────────────┬────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ constraints đi XUỐNG │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ CHILD │ │ │ │ │ │ 2. Nhận constraints │ │ │ │ │ │ 3. Tính toán size của mình (phải trong constraints) │ │ │ │ │ │ 4. Layout children của mình (đệ quy) │ │ │ │ │ │ 5. Set size = Size(200, 100) │ │ │ │ │ └────────────────────────────┬────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ▼ size đi LÊN │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ 6. Parent nhận size của child │ │ │ │ │ │ 7. Parent set position cho child (offset) │ │ │ │ │ │ child.parentData.offset = Offset(50, 50) │ │ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

5.2 BoxConstraints Chi tiết

┌─────────────────────────────────────────────────────────────────────────────────┐ │ BOX CONSTRAINTS │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ BoxConstraints { │ │ double minWidth; │ │ double maxWidth; │ │ double minHeight; │ │ double maxHeight; │ │ } │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ CONSTRAINT TYPES │ │ │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ TIGHT (min == max) │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ BoxConstraints.tight(Size(100, 100)) │ │ │ │ │ │ → minW=100, maxW=100, minH=100, maxH=100 │ │ │ │ │ │ → Child PHẢI có size 100x100 │ │ │ │ │ │ → Ví dụ: SizedBox(width: 100, height: 100, child: ...) │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ LOOSE (min == 0) │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ BoxConstraints.loose(Size(100, 100)) │ │ │ │ │ │ → minW=0, maxW=100, minH=0, maxH=100 │ │ │ │ │ │ → Child có thể từ 0 đến 100 │ │ │ │ │ │ → Ví dụ: Center loosens constraints cho child │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ UNBOUNDED (max == infinity) │ │ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ │ │ BoxConstraints(minW: 0, maxW: ∞, minH: 0, maxH: ∞) │ │ │ │ │ │ → Không giới hạn │ │ │ │ │ │ → Ví dụ: Trong ListView, height là unbounded │ │ │ │ │ │ → ⚠️ Gây lỗi nếu child muốn expand! │ │ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

5.3 Layout Complexity: O(n)

┌─────────────────────────────────────────────────────────────────────────────────┐ │ LINEAR TIME LAYOUT │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Flutter layout đạt O(n) time complexity nhờ: │ │ │ │ 1. SINGLE PASS - Mỗi node được visit tối đa 2 lần: │ │ • Lần 1: Nhận constraints, tính size │ │ • (Không có lần quay lại để "thương lượng") │ │ │ │ 2. CONSTRAINTS LÀ FINAL: │ │ • Parent pass constraints → Child PHẢI tuân thủ │ │ • Child không thể yêu cầu parent thay đổi constraints │ │ │ │ 3. SUBTREE ISOLATION: │ │ • Child chỉ biết constraints của mình │ │ • Không thể nhìn siblings hoặc parent's size │ │ │ │ Trade-off: │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ [-] Không thể: "Làm tôi to bằng sibling lớn nhất" │ │ │ │ [-] Cần IntrinsicHeight/Width cho một số cases (tốn performance) │ │ │ │ │ │ │ │ [+] Layout cực nhanh │ │ │ │ [+] Predictable │ │ │ │ [+] Không infinite loops │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

5.4 relayoutBoundary

┌─────────────────────────────────────────────────────────────────────────────────┐ │ RELAYOUT BOUNDARY │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Khi RenderObject thay đổi size, parent cần re-layout. │ │ Nhưng propagation có thể dừng lại tại RELAYOUT BOUNDARY. │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ RenderObject A │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ RenderObject B ← relayoutBoundary = true │ │ │ │ │ (constraints là TIGHT, size cố định) │ │ │ │ ▼ │ │ │ │ RenderObject C ← thay đổi size │ │ │ │ │ │ │ │ Khi C thay đổi: │ │ │ │ • C.markNeedsLayout() │ │ │ │ • Propagate lên... và DỪNG tại B (boundary) │ │ │ │ • A KHÔNG bị re-layout │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ │ relayoutBoundary = true khi: │ │ • constraints.isTight (min == max cho cả w và h) │ │ • sizedByParent = true │ │ • parentUsesSize = false │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

6. Paint Phase - Chi tiết

6.1 Paint Method

┌─────────────────────────────────────────────────────────────────────────────────┐ │ PAINT PHASE │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Mỗi RenderObject có method paint(): │ │ │ │ void paint(PaintingContext context, Offset offset) { │ │ // 1. Vẽ background │ │ context.canvas.drawRect( │ │ Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height), │ │ Paint()..color = Colors.blue, │ │ ); │ │ │ │ // 2. Vẽ children │ │ context.paintChild(child, offset + Offset(10, 10)); │ │ │ │ // 3. Vẽ foreground decorations │ │ context.canvas.drawLine(...); │ │ } │ │ │ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ PaintingContext cung cấp: │ │ │ │ • canvas: để vẽ trực tiếp │ │ │ │ • paintChild(): để vẽ child với compositing │ │ │ │ • pushLayer(): để tạo layer mới │ │ │ │ • pushClipRect(): clip region │ │ │ │ • pushOpacity(): apply opacity │ │ │ │ • pushTransform(): apply transform │ │ │ └─────────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

6.2 repaintBoundary

┌─────────────────────────────────────────────────────────────────────────────────┐ │ REPAINT BOUNDARY │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Khi RenderObject cần repaint, thông thường cả subtree của parent │ │ cũng phải repaint. repaintBoundary giới hạn điều này. │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ KHÔNG có RepaintBoundary: CÓ RepaintBoundary: │ │ │ │ │ │ │ │ Parent Parent │ │ │ │ │ │ │ │ │ │ ┌───┴───┐ ┌───┴───┐ │ │ │ │ │ │ │ │ │ │ │ │ Child A Child B ← repaint Child A RepaintBoundary │ │ │ │ │ │ │ │ │ → Cả Parent phải repaint! Child B ← repaint │ │ │ │ │ │ │ │ → Chỉ Child B repaint │ │ │ │ → Parent dùng cached layer │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ │ │ Dùng RepaintBoundary widget: │ │ • Animations thường xuyên │ │ • Scrolling content │ │ • Video playback │ │ • Spinning loaders │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

7. Compositing Phase

7.1 Layer Tree

┌─────────────────────────────────────────────────────────────────────────────────┐ │ LAYER TREE │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Paint phase tạo ra LAYER TREE song song với Render Tree: │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ TransformLayer (root) │ │ │ │ │ │ │ │ │ ├── OffsetLayer (main content) │ │ │ │ │ │ │ │ │ │ │ ├── PictureLayer (static background) │ │ │ │ │ │ │ │ │ │ │ └── PictureLayer (text, icons) │ │ │ │ │ │ │ │ │ ├── OpacityLayer (fading element) │ │ │ │ │ │ │ │ │ │ │ └── PictureLayer │ │ │ │ │ │ │ │ │ └── ClipRectLayer (clipped area) │ │ │ │ │ │ │ │ │ └── PictureLayer (scrollable content) │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ │ │ Layer types: │ │ • PictureLayer: Chứa drawing commands │ │ • ContainerLayer: Chứa child layers │ │ • TransformLayer: Apply matrix transform │ │ • OpacityLayer: Apply opacity │ │ • ClipRectLayer, ClipRRectLayer, ClipPathLayer: Clipping │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

7.2 Scene Building

┌─────────────────────────────────────────────────────────────────────────────────┐ │ SCENE BUILDING │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ RenderView.compositeFrame() được gọi khi platform yêu cầu frame mới: │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ 1. Tạo SceneBuilder │ │ │ │ final builder = SceneBuilder(); │ │ │ │ │ │ │ │ 2. Layer tree add vào builder │ │ │ │ layer.addToScene(builder); │ │ │ │ │ │ │ │ builder.pushTransform(...) │ │ │ │ builder.pushOffset(...) │ │ │ │ builder.addPicture(...) │ │ │ │ builder.pop() │ │ │ │ ... │ │ │ │ │ │ │ │ 3. Build Scene │ │ │ │ final scene = builder.build(); │ │ │ │ │ │ │ │ 4. Gửi đến Engine │ │ │ │ window.render(scene); │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ │ │ Scene là một opaque object chứa tất cả instructions để render UI │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

8. Rasterization Phase

8.1 GPU Rendering

┌─────────────────────────────────────────────────────────────────────────────────┐ │ RASTERIZATION │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Scene được chuyển từ Dart Framework → Flutter Engine → GPU: │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ Dart (UI Thread) │ │ │ │ │ │ │ │ │ │ window.render(scene) │ │ │ │ ▼ │ │ │ │ Flutter Engine │ │ │ │ │ │ │ │ │ │ Scene → Display List │ │ │ │ ▼ │ │ │ │ Raster Thread │ │ │ │ │ │ │ │ │ │ Impeller/Skia processes display list │ │ │ │ │ Creates GPU commands │ │ │ │ ▼ │ │ │ │ GPU │ │ │ │ │ │ │ │ │ │ Execute shaders │ │ │ │ │ Rasterize to framebuffer │ │ │ │ ▼ │ │ │ │ Display Hardware │ │ │ │ │ │ │ └───────────────────────────────────────────────────────────────────────────┘ │ │ │ │ UI Thread và Raster Thread chạy SONG SONG: │ │ • UI Thread chuẩn bị frame N+1 │ │ • Raster Thread render frame N │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

9. Performance Considerations

9.1 Pipeline Jank Points

┌─────────────────────────────────────────────────────────────────────────────────┐ │ COMMON JANK CAUSES │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ BUILD PHASE: │ │ • Quá nhiều widgets rebuild không cần thiết │ │ • build() method phức tạp, tốn thời gian │ │ • Không dùng const widgets │ │ │ │ LAYOUT PHASE: │ │ • IntrinsicHeight/Width (O(n²) thay vì O(n)) │ │ • Layout triggers từ deep in tree propagate lên root │ │ │ │ PAINT PHASE: │ │ • Không có RepaintBoundary cho animations │ │ • saveLayer() unnecessary (opacity, clip) │ │ │ │ RASTERIZATION: │ │ • Shader compilation (Skia) - lần đầu gặp effect mới │ │ • Too many layers │ │ • Large images │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

9.2 Optimization Strategies

┌─────────────────────────────────────────────────────────────────────────────────┐ │ OPTIMIZATION STRATEGIES │ ├─────────────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. MINIMIZE REBUILDS: │ │ • Sử dụng const constructors │ │ • Tách widgets nhỏ với state riêng │ │ • Dùng Provider/Riverpod select() để chỉ listen cần thiết │ │ │ │ 2. OPTIMIZE LAYOUT: │ │ • Tránh IntrinsicHeight/Width │ │ • Sử dụng ListView.builder thay vì ListView với children │ │ • Sử dụng const SizedBox thay vì Container trống │ │ │ │ 3. OPTIMIZE PAINT: │ │ • Wrap animations trong RepaintBoundary │ │ • Tránh Opacity widget cho complex trees (dùng AnimatedOpacity) │ │ • Cache images với precacheImage() │ │ │ │ 4. DEBUG TOOLS: │ │ • flutter run --profile │ │ • DevTools → Performance tab │ │ • debugPaintSizeEnabled = true │ │ • debugRepaintRainbowEnabled = true │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘

📝 Tóm tắt

PhaseInputOutputThread
User InputRaw eventsGesture callbacksUI
AnimationVSync signalUpdated valuesUI
BuildDirty elementsWidget/Element treesUI
LayoutConstraintsSizes, positionsUI
PaintRender treeLayer treeUI
CompositingLayer treeSceneUI
RasterizationScenePixelsRaster

Điểm quan trọng:

  • Pipeline chạy mỗi frame (16.67ms @ 60fps)
  • Build/Layout/Paint trên UI Thread - không block!
  • Rasterization trên Raster Thread - parallel
  • Tối ưu: const widgets, RepaintBoundary, relayout boundary
Last updated on