The Render Pipeline
สี่ phase ต่อ frame
หัวข้อที่มีชื่อว่า “สี่ phase ต่อ frame”เวลาวาด frame หนึ่ง Flutter จะรัน pipeline ที่ตายตัว รู้จัก phase เหล่านี้แล้วคุณจะรู้ว่าเวลาไปหมดที่ไหนและควร optimize ตรงไหน
flowchart LR build["1. Build (run build methods)"] --> layout["2. Layout (constraints down, sizes up)"] layout --> paint["3. Paint (record draw commands)"] paint --> composite["4. Composite (assemble layers, to GPU)"]
- Build — widget ที่ dirty rebuild ออกมาเป็นคำอธิบาย widget ที่อัปเดต
- Layout — pass แบบ constraints-ลงล่าง/size-ขึ้นบน คำนวณ size และตำแหน่งของ render object ทุกตัว
- Paint — render object แต่ละตัวบันทึกคำสั่งวาด (ยังไม่ใช่ pixel) ลงใน layer
- Composite — layer ถูกประกอบและส่งให้ GPU rasterize
budget แคบมาก: จะได้ 60fps คุณมีเวลา ~16ms ต่อ frame (~8ms ที่ 120Hz) jank คือ frame ที่พลาด budget นั้น และเกือบทุกครั้งมาจากงานเยอะเกินไปใน build หรือ layout
RenderObject ทำงานจริง
หัวข้อที่มีชื่อว่า “RenderObject ทำงานจริง”widget เป็น คำอธิบาย ที่เบาและ immutable object ที่ layout และ paint จริง ๆ คือ RenderObject และอยู่ใน tree ที่สามจากสาม tree ของ Flutter:
- Widget tree — configuration ที่ immutable ของคุณ (rebuild บ่อย, ถูก)
- Element tree — กาว mutable ที่เก็บ state และเชื่อม widget กับ render object
- Render tree —
RenderObjectที่คำนวณ layout และ paint (แพง; ถูกเก็บและ mutate ไม่ใช่ rebuild)
การแยกนี้คือ เหตุผล ที่ rebuild widget ถูก: widget ใหม่ถูกเทียบกับตัวเก่า แล้ว Flutter mutate render object เดิมในที่เดิมแทนที่จะสร้างส่วนที่แพงขึ้นใหม่
repaint boundary
หัวข้อที่มีชื่อว่า “repaint boundary”โดย default เมื่อ render object repaint ก็ลากให้เพื่อนบ้าน repaint ตามไปด้วย — เพราะแชร์ layer กัน RepaintBoundary เอา subtree ไปไว้บน layer ของตัวเอง เพื่อให้ repaint ของตัวเองไม่กระเพื่อมออกไปข้างนอก (และกลับกัน) นี่คือเครื่องมือหลักในการแยกพื้นที่ที่ animate หรือ repaint บ่อย
RepaintBoundary( child: SpinningLogo(), // repaint บน layer ของตัวเอง ไม่ใช่ทั้งหน้าจอ)ใช้ครอบสิ่งที่ repaint บ่อย (animation, progress spinner) เพื่อไม่ให้ทั้ง frame ต้องบันทึกใหม่ ใช้มากไปเปลือง memory (แต่ละ boundary คือ layer) ดังนั้นใช้ตรงที่ profiling บอกว่ามี repaint churn
relayout boundary และวิธี profile
หัวข้อที่มีชื่อว่า “relayout boundary และวิธี profile”Flutter ยังสร้าง relayout boundary อัตโนมัติตรงที่ size ของ subtree เปลี่ยน parent ไม่ได้ (เช่น ภายใต้ tight constraints) — เพื่อให้การเปลี่ยนแปลงข้างในไม่บังคับให้ทั้ง tree ต้อง layout ใหม่ คุณแทบไม่ต้องจัดการเองด้วยมือ แต่เป็นเหตุผลที่การเปลี่ยนแปลงเฉพาะจุดยังคงอยู่เฉพาะจุด
จะเห็นทั้งหมดนี้ ใช้ Flutter DevTools: performance view โชว์เวลา build/layout/paint ต่อ frame และเครื่องมือ “Highlight repaints” กับ “Track widget rebuilds” โชว์ว่าอะไรกำลัง churn optimize สิ่งที่คุณวัด ไม่ใช่สิ่งที่คุณเดา