# การเพิ่มประสิทธิภาพ Flutter ในปี 2026: Impeller, การรีบิลด์ และแนวปฏิบัติที่ดี > วิธีคงแอป Flutter ไว้ที่ 60 หรือ 120fps อย่างคงที่ในปี 2026 ด้วย Impeller, การรีบิลด์วิดเจ็ตอย่างมีวินัย, RepaintBoundary และการ profiling ด้วย DevTools - Published: 2026-07-04 - Updated: 2026-07-07 - Author: SharpSkill - Tags: flutter, performance, impeller, best-practices, dart, mobile - Reading time: 9 min --- การเพิ่มประสิทธิภาพของ Flutter ในปี 2026 ตั้งอยู่บนการเปลี่ยนแปลงสองอย่างที่พลิกวิธีสร้างแอปให้ลื่นไหล นั่นคือเอนจินเรนเดอร์ Impeller ที่ตอนนี้เปิดใช้งานเป็นค่าเริ่มต้นบน iOS และ Android และโมเดลการรีบิลด์ของเฟรมเวิร์กที่ให้ผลตอบแทนแก่นักพัฒนาที่คงต้นไม้วิดเจ็ตให้เล็กและไม่เปลี่ยนแปลง ทีมที่ใช้ทั้งสองคานงัดนี้จะรักษาระดับ 60 หรือ 120fps ได้อย่างคงที่โดยไม่ต้องปรับแต่งทุกเฟรมด้วยมือ > **การเปลี่ยนแปลงที่ส่งผลมากที่สุด** > > ผลได้ที่ใหญ่ที่สุดในแอป Flutter ส่วนใหญ่คือการเพิ่มคอนสตรัคเตอร์ `const` และการแยกเมธอด `build` ที่ใหญ่ออกเป็นส่วนย่อย เพื่อให้การเรียก `setState` วาดปุ่มเดียวใหม่แทนที่จะเป็นทั้งหน้าจอ ## เหตุใด Impeller จึงตั้งเส้นฐานประสิทธิภาพของ Flutter ขึ้นใหม่ Impeller คือเอนจินเรนเดอร์ของ Flutter และได้เข้ามาแทนที่ Skia ในฐานะค่าเริ่มต้นบนแพลตฟอร์มมือถือ ความแตกต่างสำคัญที่สุดในไม่กี่วินาทีแรกของแอนิเมชัน Skia คอมไพล์เชเดอร์แบบทันเวลา ในครั้งแรกที่เอฟเฟกต์หนึ่งปรากฏบนหน้าจอ ซึ่งก่อให้เกิดอาการกระตุกในการรันครั้งแรกอันโด่งดังที่แอป Flutter เคยถูกวิจารณ์ Impeller คอมไพล์เชเดอร์เหล่านั้นล่วงหน้าในระหว่างการบิลด์ ดังนั้นเฟรมแรกสุดของแอนิเมชันจึงลื่นไหลเท่ากับเฟรมที่หนึ่งร้อย Impeller ยังเล็งไปที่ API กราฟิกสมัยใหม่โดยตรง ได้แก่ Metal บน iOS และ Vulkan บน Android โดยมี OpenGL เป็นทางเลือกสำรองสำหรับฮาร์ดแวร์ Android รุ่นเก่า [เอกสารของเอนจิน Impeller บน GitHub](https://github.com/flutter/flutter/blob/master/docs/engine/impeller/README.md) อธิบายสถาปัตยกรรมการเรนเดอร์และเมทริกซ์การรองรับแพลตฟอร์มโดยละเอียด | แง่มุม | Skia (แบบเดิม) | Impeller (ค่าเริ่มต้น 2026) | |--------|---------------|-------------------------| | การคอมไพล์เชเดอร์ | ขณะรัน ทำให้กระตุกในการรันครั้งแรก | ล่วงหน้า ในระหว่างบิลด์ | | แบ็กเอนด์ iOS | OpenGL / Metal | Metal | | แบ็กเอนด์ Android | OpenGL | Vulkan สำรองด้วย OpenGL | | ความลื่นไหลของแอนิเมชันแรก | กระตุกเป็นครั้งคราว | คงที่ | สำหรับโปรเจกต์ส่วนใหญ่ การย้ายระบบนั้นฟรี เพราะ Flutter รุ่นล่าสุดเปิดใช้ Impeller โดยอัตโนมัติ และวิธีแก้เฉพาะหน้าแบบเก่าสำหรับการอุ่นเชเดอร์ (`--purge-persistent-cache`, ไฟล์ `.sksl` ที่ฝังมา) ไม่จำเป็นอีกต่อไปและสามารถลบทิ้งได้ เชเดอร์ `FragmentProgram` แบบกำหนดเองที่เขียนตามชุดย่อยของ GLSL ที่ Flutter รองรับก็ยังใช้งานได้ต่อ เพราะ Impeller คอมไพล์มันผ่านไปป์ไลน์แบบออฟไลน์เดียวกับเอฟเฟกต์ในตัวของเฟรมเวิร์ก การตรวจสอบว่าแอปทำงานบนเอนจินใดใช้เพียงบรรทัดเดียว บิลด์แบบดีบักจะพิมพ์แบ็กเอนด์ที่ทำงานอยู่เมื่อเริ่มต้น และตัวตรวจสอบใน DevTools จะรายงานไว้ในรายละเอียดการเรนเดอร์ของแอป เมื่อยืนยัน Impeller แล้ว งานประสิทธิภาพที่เหลือจะขยับขึ้นไปยังชั้นวิดเจ็ต ที่ซึ่งเฟรมเวิร์กรีบิลด์บ่อยกว่าที่หน้าจอเปลี่ยนแปลงจริงมาก นั่นคือจุดที่ส่วนที่เหลือของคู่มือนี้มุ่งเน้น เพราะเอนจินที่เร็วไม่อาจกอบกู้ต้นไม้วิดเจ็ตที่วาดตัวเองใหม่หลายร้อยครั้งต่อวินาทีได้ ## ลดการรีบิลด์ของ Flutter ด้วย const และการแยกวิดเจ็ต ทุก `setState` จะทำเครื่องหมายวิดเจ็ตของมันว่าล้าสมัยและรัน `build` ใหม่สำหรับเอลิเมนต์นั้นและต้นไม้ย่อยของมัน เคล็ดลับคือคงต้นไม้ย่อยนั้นให้แคบที่สุดเท่าที่จะทำได้ นิสัยสองอย่างทำงานส่วนใหญ่นี้ ได้แก่ ทำเครื่องหมายวิดเจ็ตแบบสถิตเป็น `const` และผลักสเตตลงล่างเพื่อให้ส่วนที่มีสเตตยังเล็กอยู่ วิดเจ็ต `const` ถูกทำให้เป็นรูปแบบมาตรฐานเดียว ดังนั้นเมื่อพาเรนต์รีบิลด์ Flutter จะเปรียบเทียบการอ้างอิงที่เหมือนกัน ไม่พบการเปลี่ยนแปลง และข้ามการวาดกิ่งนั้นใหม่ไปทั้งหมด ```dart // product_screen.dart // A cart update repaints the badge, not the header. class ProductScreen extends StatefulWidget { const ProductScreen({super.key}); @override State createState() => _ProductScreenState(); } class _ProductScreenState extends State { int _cartCount = 0; @override Widget build(BuildContext context) { return Column( children: [ const ProductHeader(), // const: never rebuilds on setState CartBadge(count: _cartCount), // only this subtree rebuilds FilledButton( onPressed: () => setState(() => _cartCount++), child: const Text('Add to cart'), ), ], ); } } ``` การเปิดใช้ lint `prefer_const_constructors` ใน `analysis_options.yaml` เปลี่ยนวินัยนี้ให้กลายเป็นการตรวจสอบของคอมไพเลอร์ ดังนั้นโอกาสในการใช้ `const` จะไม่ถอยหลังอย่างเงียบ ๆ กฎที่เกี่ยวข้อง `prefer_const_literals_to_create_immutables` ขยายการรับประกันเดียวกันไปยังลิสต์และแมปที่ส่งเข้าไปในวิดเจ็ต คีย์ของวิดเจ็ตก็ควรถูกกล่าวถึงตรงนี้เช่นกัน เมื่อลิสต์เรียงลำดับใหม่หรือวิดเจ็ตคงชนิดเดิมแต่เปลี่ยนอัตลักษณ์ `ValueKey` หรือ `ObjectKey` ช่วยให้ Flutter จับคู่เอลิเมนต์เดิมกับอินสแตนซ์วิดเจ็ตใหม่ที่ถูกต้องแทนที่จะรื้อและสร้างสเตตขึ้นใหม่ การขาดคีย์เป็นสาเหตุที่พบบ่อยของการสูญเสียตำแหน่งเลื่อนและการรีเซ็ตแอนิเมชันในลิสต์แบบไดนามิก และงานรีบิลด์ส่วนเกินที่มันก่อขึ้นก็มองข้ามได้ง่าย ## จำกัดการรีบิลด์ให้อยู่กับข้อมูลที่เปลี่ยนไป เมื่อสเตตอยู่สูงในต้นไม้ แม้เลย์เอาต์ที่แยกไว้ดีก็ยังรีบิลด์มากอยู่ดี วิธีแก้คือกระจายค่าออกไปและปล่อยให้วิดเจ็ตเดียวคอยฟังมัน Flutter มี `ValueNotifier` และ `ValueListenableBuilder` มาให้เพื่อการนี้โดยเฉพาะ โดยไม่ต้องใช้แพ็กเกจภายนอกใด ๆ ```dart // article_list.dart // A scroll-to-top button that rebuilds alone, not the whole page. class ArticleList extends StatefulWidget { const ArticleList({super.key}); @override State createState() => _ArticleListState(); } class _ArticleListState extends State { final _showFab = ValueNotifier(false); final _controller = ScrollController(); @override void initState() { super.initState(); // Update the notifier, not setState: the list never rebuilds _controller.addListener(() => _showFab.value = _controller.offset > 400); } @override void dispose() { _showFab.dispose(); _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: ListView.builder( controller: _controller, itemCount: 200, itemBuilder: (_, i) => ListTile(title: Text('Item $i')), ), floatingActionButton: ValueListenableBuilder( valueListenable: _showFab, builder: (_, visible, __) => visible ? FloatingActionButton( onPressed: () => _controller.jumpTo(0), child: const Icon(Icons.arrow_upward), ) : const SizedBox.shrink(), ), ); } } ``` ไลบรารีจัดการสเตตทำให้แนวคิดเดียวกันนี้เป็นภาพรวม `ref.watch(provider.select(...))` ของ Riverpod และวิดเจ็ต `Selector` ของ Provider ต่างรีบิลด์เฉพาะเมื่อชิ้นส่วนของสเตตที่เลือกไว้เปลี่ยนไป ซึ่งเป็นปัจจัยชี้ขาดระหว่างความลื่นไหลกับความกระตุกบนหน้าจอที่ข้อมูลหนาแน่น การแลกเปลี่ยนระหว่างไลบรารีเหล่านั้นถูกกล่าวถึงในคู่มือเรื่อง [การจัดการสเตตใน Flutter ปี 2026](/blog/flutter/flutter-state-management-2026-riverpod-bloc-getx) > **ระวังขอบเขตของการรีบิลด์** > > การเรียก `setState` ใกล้รากของหน้าจอ หรือการห่อทั้งหน้าไว้ใน `Consumer` ระดับบนสุด จะวาดวิดเจ็ตหลายร้อยตัวใหม่อย่างเงียบ ๆ ในทุกการเปลี่ยนแปลง จง profiling ด้วยตัวนับการรีบิลด์ใน DevTools ก่อนจะสรุปว่าหน้าจอหนึ่งมีต้นทุนต่ำ ## แยกการวาดใหม่ที่แพงออกด้วย RepaintBoundary การรีบิลด์และการวาดใหม่เป็นคนละขั้นตอนกัน วิดเจ็ตหนึ่งอาจข้ามการรีบิลด์แต่ยังวาดใหม่ได้ เพราะเพื่อนบ้านที่จุกจิกใช้เลเยอร์ร่วมกับมัน `RepaintBoundary` มอบเลเยอร์ของตัวเองให้ต้นไม้ย่อย ดังนั้นกราฟที่เคลื่อนไหวจึงไม่อาจบังคับให้การ์ดสถิตวาดใหม่ทุกเฟรม ```dart // dashboard.dart // The live chart repaints on its own layer, cards stay untouched. class Dashboard extends StatelessWidget { const Dashboard({required this.ticker, super.key}); final Stream ticker; @override Widget build(BuildContext context) { return Column( children: [ // SummaryCards is static, so isolate it from the animation below const RepaintBoundary(child: SummaryCards()), RepaintBoundary( child: StreamBuilder( stream: ticker, builder: (_, snap) => LivePriceChart(value: snap.data ?? 0), ), ), ], ); } } ``` RepaintBoundary ไม่ได้ฟรี แต่ละตัวจัดสรรเลเยอร์หนึ่งชั้น ดังนั้นการโปรยมันไว้ทุกที่จึงเป็นโทษมากกว่าเป็นคุณ ควรใช้มันรอบบริเวณที่เคลื่อนไหวจริง ๆ และลิสต์เลื่อนยาว ๆ แล้วยืนยันผลได้ในไทม์ไลน์ [เอกสารอ้างอิง API RepaintBoundary](https://api.flutter.dev/flutter/widgets/RepaintBoundary-class.html) อธิบายวิธีที่โอเวอร์เลย์การวาดใหม่แบบดีบักเน้นให้เห็นว่าเลเยอร์ใดถูกวาดใหม่จริง ## คงเมธอด build ให้ต้นทุนต่ำและปราศจากผลข้างเคียง เมธอด `build` ทำงานในทุกการรีบิลด์ ดังนั้นทุกอย่างที่แพงภายในมันจะถูกทวีคูณ กฎคือ `build` ควรอ่านสเตตและคืนวิดเจ็ต ไม่มากไปกว่านั้น การจัดสรร `ScrollController`, `AnimationController` หรือ `Future` ภายใน `build` จะสร้างมันขึ้นใหม่ทุกเฟรมและปล่อยอินสแตนซ์ก่อนหน้ารั่วไหล วัตถุเหล่านั้นควรอยู่ใน `initState` หรือในฟิลด์ `late final` ที่ปลดปล่อยใน `dispose` งานแบบซิงโครนัสที่หนักคือกับดักที่สอง การแยกวิเคราะห์ JSON การกรองลิสต์ใหญ่ หรือการจัดรูปแบบวันที่ภายใน `build` จะรันงานนั้นซ้ำในทุกการวาดใหม่ แม้ข้อมูลเบื้องหลังจะไม่เปลี่ยน จงคำนวณผลลัพธ์หนึ่งครั้งเมื่ออินพุตเปลี่ยนแล้วแคชไว้ หรือย้ายมันไปยัง `FutureBuilder` เพื่อให้เฟรมเวิร์กไม่บล็อกเฟรมหนึ่งเพื่อรอมัน สำหรับค่าที่อนุมานจากค่าอื่น จงจดจำมันไว้หลังเกตเตอร์ที่คำนวณใหม่เฉพาะเมื่อดีเพนเดนซีของมันเปลี่ยน วิดเจ็ต `Opacity` เป็นตัวการเฉพาะที่ควรเอ่ยชื่อ การห่อต้นไม้ย่อยไว้ใน `Opacity` บังคับให้เกิด `saveLayer` นอกจอ ซึ่งเป็นหนึ่งในปฏิบัติการที่แพงที่สุดในไปป์ไลน์ สำหรับแอนิเมชันจางหาย `AnimatedOpacity` หรือ `FadeTransition` ถูกกว่า และสำหรับสีทับที่คงที่ การใส่สีที่ระดับ paint ผ่าน `foregroundDecoration` หรือเชเดอร์จะเลี่ยงเลเยอร์นั้นได้ทั้งหมด ความระมัดระวังเดียวกันใช้กับ `ClipPath` และ `ClipRRect` ที่ลบรอยหยักบนพื้นผิวใหญ่ ซึ่งก็กระตุ้น `saveLayer` เบื้องหลังเช่นกัน สุดท้าย จงเลือกใช้คอลเลกชัน `const` และคอลแบ็กที่ไม่จับโคลเชอร์ใหม่ในทุกการบิลด์ `const []` ที่ส่งเป็นค่าเริ่มต้นและการอ้างอิงเมธอดแทนแลมบ์ดาอินไลน์ต่างคงการตรวจสอบความเท่าเทียมของวิดเจ็ตให้ต้นทุนต่ำ ซึ่งทำให้การลัดวงจร `const` ที่อธิบายไว้ก่อนหน้านี้ทำงานได้จริง ## สร้างลิสต์ยาวและรูปภาพอย่างมีประสิทธิภาพ ฟีดยาว ๆ และกริดรูปภาพคือจุดที่โค้ด Flutter แบบไร้เดียงสาทำเฟรมตก กฎสองข้อครอบคลุมกรณีส่วนใหญ่ ได้แก่ สร้างแถวแบบขี้เกียจ และถอดรหัสรูปภาพที่ขนาดซึ่งมันแสดงจริงแทนที่จะเป็นความละเอียดต้นทาง ```dart // feed.dart // Lazy rows plus right-sized image decoding keep scrolling at 60fps. class Feed extends StatelessWidget { const Feed({required this.posts, super.key}); final List posts; @override Widget build(BuildContext context) { return ListView.builder( itemCount: posts.length, // build on demand, not all at once cacheExtent: 600, // pre-build past the viewport edge itemBuilder: (context, index) { final post = posts[index]; return ListTile( leading: Image.network( post.thumbnailUrl, width: 48, height: 48, cacheWidth: 96, // decode at display size, save memory ), title: Text(post.title), ); }, ); } } ``` รูปภาพความละเอียดเต็ม 3000 พิกเซลที่ถูกยัดลงในอวาตาร์ 48 พิกเซลสิ้นเปลืองหน่วยความจำและเวลาในการถอดรหัสในทุกภาพ การตั้ง `cacheWidth` บอกเอนจินให้ถอดรหัสที่ขนาดเป้าหมาย ซึ่งลำพังอย่างเดียวก็สามารถลดรอยเท้าหน่วยความจำของลิสต์ลงได้ในระดับหนึ่งอันดับความใหญ่ [แนวปฏิบัติที่ดีด้านประสิทธิภาพ](https://docs.flutter.dev/perf/best-practices) ของ Flutter รวบรวมคำแนะนำเพิ่มเติมเรื่อง `Opacity`, `saveLayer` และการใช้ clip ที่เดินตามหลักการเดียวกัน ## วัดผลการเพิ่มประสิทธิภาพ Flutter ในปี 2026 ด้วย DevTools ทุกกฎข้างต้นควรถูกตรวจสอบ ไม่ใช่สันนิษฐาน Flutter DevTools คือแหล่งความจริง โอเวอร์เลย์ประสิทธิภาพ (`P` ในคอนโซลดีบัก หรือ `flutter run --profile`) วาดกราฟสองเส้น คือ UI และ raster และแท่งใดที่ข้ามเส้นเขียวคือเฟรมที่ตก มุมมองไทม์ไลน์จะระบุเฟรมที่ช้าแต่ละเฟรมให้กับการเรียก `build`, layout หรือ paint ที่เจาะจง จง profiling ในโหมด `--profile` บนอุปกรณ์จริงเสมอ บิลด์ดีบักรัน Dart ที่ไม่ได้ปรับให้เหมาะสมและทำให้ทุกการวัดพองตัว ดังนั้นหน้าจอที่กระตุกในโหมดดีบักมักทำงานได้อย่างสมบูรณ์แบบเมื่อคอมไพล์แล้ว [คู่มือประสิทธิภาพของ DevTools](https://docs.flutter.dev/tools/devtools/performance) พาเดินผ่านการอ่านแผนภูมิเฟรมและการหาต้นตอของการกระตุก และกล้ามเนื้อ profiling เดียวกันนี้ก็ปรากฏในการสัมภาษณ์เรื่อง [แอนิเมชันและการเรนเดอร์ใน Flutter](/technologies/flutter/interview-questions/animations) > **วงจร profiling อย่างรวดเร็ว** > > รันในโหมด profile ทำซ้ำการโต้ตอบที่กระตุก เปิดไทม์ไลน์ แล้วจัดเรียงเฟรมตามระยะเวลา เฟรมที่แย่ที่สุดเกือบจะชี้ไปที่การรีบิลด์ที่ใหญ่เกินไปเพียงจุดเดียวหรือรูปภาพที่ไม่ได้แคชเสมอ ซึ่งเร็วกว่าการเดามาก [ศูนย์รวมเทคโนโลยี Flutter](/technologies/flutter) เชื่อมโยงหัวข้อการจัดการสเตต การทดสอบ และการเรนเดอร์ที่เติมเต็มกลยุทธ์ประสิทธิภาพระดับโปรดักชัน ## บทสรุป ประสิทธิภาพของ Flutter ในปี 2026 ไม่ได้เกี่ยวกับกลเม็ดเล็ก ๆ มากนัก แต่เกี่ยวกับรายการนิสัยสั้น ๆ ที่นำไปใช้อย่างสม่ำเสมอ - ปล่อยแอปบน Impeller และลบโค้ดอุ่นเชเดอร์แบบเก่าออก ปัญหาการกระตุกที่เฟรมแรกถูกแก้ที่ระดับเอนจินแล้ว - ทำเครื่องหมายวิดเจ็ตสถิตเป็น `const` และบังคับใช้ด้วย lint `prefer_const_constructors` - แยกวิดเจ็ตที่มีสเตตให้เล็กลง เพื่อให้ทุก `setState` วาดต้นไม้ย่อยที่แคบที่สุดเท่าที่ทำได้ใหม่ - กระจายค่าที่เปลี่ยนแปลงผ่าน `ValueListenableBuilder` หรือ API สไตล์ `select` แทนการรีบิลด์ทั้งหน้าจอ - ห่อบริเวณที่เคลื่อนไหวจริง ๆ ไว้ใน `RepaintBoundary` แต่จงวัดผลก่อนจะเพิ่มมากขึ้น - สร้างลิสต์ด้วย `ListView.builder` และถอดรหัสรูปภาพที่ขนาดการแสดงผลด้วย `cacheWidth` - profiling ในโหมด `--profile` บนฮาร์ดแวร์จริง และให้ DevTools ไม่ใช่สัญชาตญาณ เป็นผู้ตัดสินว่าจะเพิ่มประสิทธิภาพสิ่งใดต่อไป --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/flutter/flutter-performance-optimization-2026-impeller-rebuilds