บทที่ 22: Performance Optimization & Best Practices
สารบัญเนื้อหา
บทที่ 22: Performance Optimization & Best Practices
🎯 เป้าหมายการเรียนรู้
หลังจากเรียนบทนี้แล้ว คุณจะสามารถ:
- วิเคราะห์ประสิทธิภาพแอปด้วย Flutter DevTools (Profile Mode)
- เข้าใจและแก้ไขปัญหา "Jank" (แอปกระตุก)
- ใช้
constconstructor เพื่อลดการ Build Widget ซ้ำซ้อน - จัดการ Memory Leak จาก Controller และ Stream
- เพิ่มประสิทธิภาพ ListView ให้รองรับข้อมูลหลักหมื่นรายการ
📚 ทฤษฎีและแนวคิด
1. Build Phases
Flutter วาดหน้าจอเร็วมาก (60/120 fps) เพราะมันฉลาด
- Build: สร้าง Widget Tree (เร็วมาก, ทำบ่อย)
- Layout: คำนวณขนาดและตำแหน่ง (ช้ากว่า, ทำเมื่อจำเป็น)
- Paint: วาด pixel ลงจอ (ช้าสุด, ควรทำให้น้อยที่สุด)
2. Jank คืออะไร?
ถ้าเฟรมฮวบต่ำกว่า 60fps ผู้ใช้จะรู้สึก "กระตุก" (Jank) สาเหตุหลัก:
- ทำคำนวณหนักๆ ใน Build method
- Rebuild บ่อยเกินความจำเป็น
- โหลดรูปภาพขนาดใหญ่เกินไป
💻 เทคนิคและวิธีการ (Best Practices)
1. ใช้ const ให้มากที่สุด
ทำไม?
Widget ที่เป็น const จะถูกสร้างแค่ครั้งเดียวตอนเริ่มแอป และจะไม่ถูกสร้างใหม่เลยแม้ Parent จะ Rebuild
แย่ ❌:
@override
Widget build(BuildContext context) {
return Column(
children: [
Text("Hello"), // สร้างใหม่ทุกครั้งที่ build
SizedBox(height: 10), // สร้างใหม่ทุกครั้ง
MyCustomWidget(), // สร้างใหม่ทุกครั้ง
],
);
}
ดี ✅:
@override
Widget build(BuildContext context) {
return const Column( // const คลุมทั้งก้อน
children: [
Text("Hello"), // ตัวเดิม
SizedBox(height: 10), // ตัวเดิม
MyCustomWidget(), // ตัวเดิม
],
);
}
2. ListView.builder (Lazy Loading)
ถ้ามีของเป็นร้อยชิ้น ห้ามใช้ ListView(children: [...]) หรือ Column เด็ดขาด เพราะมันจะสร้าง Widget เตรียมไว้ทั้งหมดทันที (Memory บวม)
ดี ✅:
ListView.builder(
itemCount: 10000,
itemBuilder: (context, index) {
// สร้างเฉพาะตัวที่ "กำลังเห็น" บนหน้าจอ
return ListTile(title: Text("Item $index"));
},
)
3. Image Optimization (รูปภาพ)
รูปภาพ 4K กินแรมมหาศาล ถ้าเราจะโชว์แค่ Thumbnail 100x100px ให้ใช้ cacheWidth / cacheHeight
Image.network(
"https://example.com/big_image.jpg",
cacheWidth: 200, // resize ลงหน่วยความจำแค่ 200px
)
4. Controller & Stream ต้อง Dispose เสมอ
ถ้าไม่ dispose, ตัวแปรจะลอยค้างอยู่ใน RAM (Memory Leak) ยิ่งเปิดปิดหน้านั้นบ่อยๆ RAM ก็จะเต็มจนแอปเด้ง
class MyWidget extends StatefulWidget { ... }
class _MyWidgetState extends State<MyWidget> {
late TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController(); // สร้าง
}
@override
void dispose() {
_controller.dispose(); // ทำลาย
super.dispose();
}
}
5. RepaintBoundary
เหมาะสำหรับ Widget ที่มีการเปลี่ยนแปลงบ่อยๆ (เช่น Animation หมุนๆ, Video Player) แต่ Widget รอบข้างมักนิ่ง
การหุ้มด้วย RepaintBoundary บอก Flutter ว่า "ตัวนี้วาดแยก Layer นะ อย่าไปกวนเพื่อน"
Column(
children: [
StaticHeader(), // ไม่ต้องวาดใหม่
RepaintBoundary(
child: RotatingSpinner(), // วาดใหม่เฉพาะในกรอบนี้
),
StaticFooter(), // ไม่ต้องวาดใหม่
],
)
🛠 เครื่องมือตรวจสอบ (Tools)
1. Flutter DevTools
กดปุ่ม ⚡️ (Open DevTools) ใน VS Code หรือ Android Studio
- Flutter Inspector: ดูโครงสร้าง Widget
- Performance: ดูกราฟ FPS
- Memory: ดูการใช้ RAM
2. Profile Mode
ห้ามวัดประสิทธิภาพใน Debug Mode เด็ดขาด! เพราะมันช้ากว่าความเป็นจริง 3-4 เท่า ให้รันด้วย:
flutter run --profile
📝 แบบฝึกหัด
โจทย์ที่ 1: Hunt for Const
สิ่งที่ต้องทำ:
- ติดตั้ง linter rule:
prefer_const_constructorsในanalysis_options.yaml - ไล่แก้ Warning สีฟ้าทั้งโปรเจกต์ให้เป็น
const
โจทย์ที่ 2: Fix Memory Leak
สิ่งที่ต้องทำ:
- สร้างหน้าจอ Timer ที่นับเลขทุกวินาที (
Timer.periodic) - ลองกด Back ออก แล้วเข้ามาใหม่ 10 รอบ
- เปิด Log ดูว่า Timer เก่าหยุดทำงานไหม? (ถ้าไม่หยุด = Leak) -> แก้ไขโดย
timer.cancel()ในdispose()
📌 สรุปจบคอร์ส QUOM Learning
เดินทางมาถึงบทสุดท้าย ยินดีด้วยครับ! 🎉 คุณได้เรียนรู้ตั้งแต่พื้นฐาน จนถึงการทำ App จริงระดับ Production:
- Frontend: Flutter UI, State Management, Animation
- Backend: Node.js, Express, MongoDB
- Integration: API, Auth, Upload
- Advanced: i18n, Admin, Performance
Next Step: ไม่มีอะไรดีไปกว่าการ "ลงมือทำโปรเจกต์จริง" ขอให้สนุกกับการสร้างสรรค์แอปพลิเคชันครับ! 🚀