انتقل للمحتوى
علي هيثم·TECH·يردّ خلال أقل من ساعة
  • الرئيسيةابدأ من هنا
  • الأعمالدراسات حالة، مشاريع
  • الخدماتما الذي سأبنيه لك
  • المدونةمقالات، سلاسل
  • التدريبدورات، مختبرات، دورات
  • عنّيالمهندس وراء هذا الموقع
تسجيل الدخولابدأ مشروع
علي هيثم · TECH
  • الرئيسية↗
  • الأعمال↗
  • الخدمات↗
  • المدونة↗
  • التدريب↗
  • عنّي↗
تسجيل الدخولابدأ مشروع
متاح
علي هيثم·TECH

استوديو هندسي. دمشق، GMT+3.

aliyosef.online

الاستوديو

  • الأعمال
  • المدونة
  • التدريب
  • عنّي
  • تواصل معي

البوابة

  • تسجيل الدخول
  • فتح تذكرة دعم
  • متابعة مشروع
  • حسابي

مصادر

  • الوثائق
  • حالة الأنظمة
  • سجلّ التغييرات
  • حزمة العلامة
  • الخصوصية
  • الشروط

النشرة

ملاحظات تقنية وأخبار، أسبوعياً. بدون حشو.

مجاني. إلغاء الاشتراك متى ما حبيت.

تابعني على
© 2026 علي هيثم يوسف. جميع الحقوق محفوظة.مبنيّ يدويًا بـ React 19.آخر نشر · 2026-05-08كل الأنظمة تعمل
  1. الرئيسيّة/
  2. تدريب/
  3. مختبرات/
  4. حلّل قائمة Flutter بطيئة
مختبر٠٣

حلّل قائمة Flutter بطيئة

قائمة تمرير تنزل إلى ٣٥fps على Android اقتصاديّ ليست "المنصّة بطيئة"، هي كومة من الأخطاء الصغيرة تستطيع إيجادها بالأدوات المشحونة في Flutter SDK. هذا المختبر يعطيك قائمة بطيئة عمداً، يمشي بك عبر ثلاث أدوات تشخيص (DevTools timeline، الـ performance overlay، ومسجّل frames صغير مخصّص)، ويطبّق أربعة إصلاحات تجلبها إلى ٥٨fps ثابتة على جهاز Snapdragon من السلسلة الرابعة.

ابدأ المختبر↓افتح المستودع↖
دقيقة
75
الـ STACK
Flutter
المستوى
مبتدئ
/02 — التهيئة

ما ستبنيه

قائمة بـ ٣٥fps، ثلاث أدوات تشخيص، أربعة إصلاحات. من ٣٥ إلى ٥٨fps على Android اقتصاديّ.

المتطلّبات

  1. 01Flutter 3.16+ مثبّت وجهاز أو محاكي عامل.
  2. 02بنيت شاشة Flutter واحدة على الأقلّ تستخدم ListView.builder.
  3. 03هدف بناء profile-mode. وضع debug مضلّل لعمل الأداء.
/03 — الشرح
/04 — موارد

خذها معك.

  • ⌥
    المستودعالكود الابتدائيّ على GitHub.
  • ↓
    تحميل zipنفس الكود، بدون git.
/يُستخدم في

دورات تتزاوج مع هذا المختبر.

  • Flutter offline-first→
/05 — التالي
جرّب هذا تالياً٠٤اضبط حزمة eval لـ LLMمختبر آخر قصير ومجّانيّ.→
أو اذهب إلى العمق

Flutter offline-first

تحليل القوائم فصل واحد. الدورة الكاملة تغطّي التخزين offline، محرّكات المزامنة، وقرارات العمارة التي تجعل تلك الـ Androids الاقتصاديّة تشعر بأنّها أصليّة.

شاهد الدورة←

ما الذي نقيس مقابله#

افتح المشروع الابتدائيّ وشغّله في وضع profile على Android اقتصاديّ (Snapdragon من السلسلة الرابعة، 4GB RAM، طبقة OEM من المستوى المتوسّط). الشاشة الرئيسيّة تعرض قائمة من ٥٠٠٠ عنصر، كلّ منها بصورة، صفّان من النصّ، و chevron خلفيّ. القائمة تمرّر بـ ٣٥fps تقريباً مع إسقاط دوريّ للـ frames إلى ٢٠. هدفنا ٥٨fps ثابتة.

إن كان لديك محاكٍ فقط أو هاتف من الفئة العليا، ستبدو الأرقام مختلفة لكن شكل الإصلاحات متطابق. وضع profile غير قابل للتفاوض: إضافة وضع debug تكذب حول كلّ شيء.

flutter run --profile -d <device_id>

الخطوة ١: شغّل performance overlay#

داخل التطبيق، أرخص إشارة هي الأولى. أضف الـ overlay إلى MaterialApp:

return MaterialApp(
  showPerformanceOverlay: true,
  // ...
);

سترى رسمَين بيانيَّين أعلى الشاشة. العلويّ هو GPU (raster) timeline، السفليّ هو UI (build + layout + paint) timeline. الشريط الأفقيّ على كلّ منهما ١٦.٦ms. أيّ شيء يعبر الشريط frame مُسقَط.

في مشروعنا الابتدائيّ، شريط UI ينسكب باستمرار. هذا يخبرنا أنّ العمل في build()، layout()، أو paint(). ليس الـ GPU.

الخطوة ٢: اعثر على المسار الحارّ بـ DevTools timeline#

افتح Flutter DevTools وانتقل إلى تبويبة Performance. اضغط Record، مرّر القائمة لثانيتَين، اضغط Stop. الـ flame graph يُظهر لك أيّ الطرق تأكل ميزانيّة الـ frame.

لقائمتنا الابتدائيّة، أكبر شريط هو _HomeListItem.build بـ ~٩ms لكلّ frame. ١٤ عنصراً مرئيّ، إذاً ١٤ × ٩ms = ١٢٦ms من البناء لكلّ frame، فوق الميزانيّة بكثير. الكلفة في ثلاثة أماكن:

  1. ودجة Text تقوم بتنسيق التاريخ بنفسها على كلّ بناء.
  2. MemoryImage يعيد فكّ تشفير نفس الـ bytes على كلّ تمرير.
  3. ودجة Padding تلفّ ودجة Padding تلفّ ودجة Padding. (نعم، حقّاً. كود إنتاج.)

الخطوة ٣: الإصلاحات الأربعة#

الإصلاح ١: احسب نصّ التاريخ مسبقاً خارج build().

// قبل
class _HomeListItem extends StatelessWidget {
  final Order order;
  @override
  Widget build(BuildContext context) {
    final date = DateFormat('d MMM, h:mm a').format(order.createdAt);
    return Row(children: [Text(date), /* ... */]);
  }
}
// بعد
class _HomeListItem extends StatelessWidget {
  _HomeListItem({required this.order})
      : _date = DateFormat('d MMM, h:mm a').format(order.createdAt);
  final Order order;
  final String _date;
  @override
  Widget build(BuildContext context) {
    return Row(children: [Text(_date), /* ... */]);
  }
}

هذا يُسقط كلفة التنسيق لكلّ بناء من ~٠.٤ms إلى صفر على التمريرات الحارّة. عبر العرض المرئيّ، هذا ~٥ms لكلّ frame مُستردّ.

الإصلاح ٢: خزّن الصور المفكوكة.

MemoryImage لا يخزّن افتراضيّاً؛ يخصّص ImageStream جديداً على كلّ بناء لنفس حمولة الـ bytes. أرخص إصلاح هو رفع ودجة Image إلى const أو التحوّل إلى Image.memory(... cacheWidth: 96) كي يخزّن إطار العمل بـ (bytes, cacheWidth). للصور الشبكيّة، استخدم cached_network_image.

Image.memory(
  order.thumbnailBytes,
  cacheWidth: 96,
  fit: BoxFit.cover,
)

هذا أكبر فوز فرديّ في المختبر: ~٣ms لكلّ عنصر مرئيّ مُستردّ.

الإصلاح ٣: اطوِ الـ padding المتداخل.

ثلاث ودجات Padding متداخلة تنشئ ثلاثة تمريرات layout. اطوِها في EdgeInsets.fromLTRB(...) واحد على الحاوية الخارجيّة. هذا يبدو تافهاً؛ عمق شجرة layout في قائمتنا الابتدائيّة عميق لدرجة أنّ تسطيحها يقطع ~١.٥ms لكلّ عنصر.

الإصلاح ٤: استخرج الأجزاء الثابتة إلى const.

الـ chevron الخلفيّ Icon(Icons.chevron_right) ثابت. تعليمه const يستبعده من شجرة إعادة البناء كلّيّاً. نفس الشيء لأيّ divider، spacer، أو نصّ ملصق قيمته لا تتغيّر أبداً.

الخطوة ٤: قِس مرّة أخرى#

أعد تسجيل الـ timeline بعد الإصلاحات. الـ flame graph لـ _HomeListItem.build يجب أن يجلس الآن حول ١.٥ms. عبر ١٤ عنصراً مرئيّاً، هذا ٢١ms المجموع، ضمن ميزانيّة ١٦.٦ms لكلّ frame بشكل مريح لأنّ الـ ١٤ عنصراً ليست جميعها يُعاد بناؤها كلّ frame؛ فقط تلك التي تمرّر إلى الرؤية.

الـ performance overlay يجب أن يُظهر الشريطَين كليهما ثابتَين تحت الخطّ. شعور تمريرك ينتقل من "خشن" إلى "هل هذا نفس التطبيق؟" عند البكسل الصحيح بالضبط.

الخطوة ٥: فخّ الانضباط#

أيّ من الإصلاحات الأربعة وحده يُعطيك تحسيناً ٢-٤fps. الأربعة معاً تُعطيك الـ ٢٣fps التي احتجتها. فخّ الانضباط هو إيجاد جانٍ واحد واضح، شحن الإصلاح، إعلان النصر، وعدم قياس الباقي أبداً. عمل الأداء ضربيّ: الإصلاحات الصغيرة تتراكم. الإصلاحات الفرديّة نادراً ما تحرّك الإبرة وحدها.

نفس الشيء صحيح لعمل محرّك المزامنة، لـ latency الـ backend، لميزانيّات البدء البارد. دورة Flutter offline-first الكاملة تُعلّم النسخة المنهجيّة من هذا: كيف تضع تنبيهات ميزانيّة الـ frame في CI، كيف تحتفظ بسجلّ perf للفريق، وكيف تُحاجج ضدّ ميزات تدفعك للعودة فوق الخطّ. هذا المختبر العضلة الأولى. الدورة خطّة التمرين.