تحسين الأداء

التحليل باستخدام Flutter DevTools: المعالج والذاكرة والشبكة

16 دقيقة الدرس 10 من 12

التحليل باستخدام Flutter DevTools: المعالج والذاكرة والشبكة

Flutter DevTools هي مجموعة الأداء الرسمية المعتمدة على المتصفح لتطبيقات Flutter وDart. تأتي مع مجموعة أدوات Flutter SDK وتتصل بالتطبيق قيد التشغيل عبر بروتوكول VM Service. في هذا الدرس ستتعلم كيفية فتح واستخدام ثلاث من أقوى علاماتها التبويبية — Performance (مخططات لهب المعالج)، وMemory (تخصيصات الكومة)، وNetwork (حركة HTTP) — حتى تتمكن من تشخيص مشاكل الأداء الحقيقية بدلاً من التخمين.

ملاحظة: قم دائماً بالتحليل في وضع profile (flutter run --profile)، وليس في وضع debug. وضع debug يعطّل تحسينات المترجم وأعباء JIT تُحرّف كل قياس. وضع release يقطع اتصال DevTools، لذا وضع profile هو الوسط الصحيح.

فتح Flutter DevTools

هناك طريقتان سريعتان لفتح DevTools أثناء تشغيل تطبيقك:

  • من الطرفية — بعد flutter run --profile، اضغط V (حرف كبير) في الطرفية لفتح DevTools تلقائياً في متصفحك الافتراضي.
  • من VS Code / Android Studio — انقر على أيقونة DevTools في شريط أدوات التصحيح، أو شغّل أمر Open DevTools من لوحة الأوامر.
  • مستقل — شغّل dart devtools والصق عنوان VM Service URL الظاهر في الطرفية.

بمجرد الفتح، ستجد شريط تبويب في الأعلى: App Size وPerformance وCPU Profiler وMemory وNetwork وLogging وغيرها. ركّز على Performance وMemory وNetwork لهذا الدرس.

تبويب Performance — مخططات لهب المعالج

يتيح لك تبويب Performance تسجيل تتبع الجدول الزمني لتفاعل حقيقي وتصويره كمخطط لهب. كل شريط أفقي يمثل استدعاء دالة؛ عرضه يتناسب مع الوقت الذي استهلكه. الأشرطة مكدّسة رأسياً لإظهار سلسلة الاستدعاء.

الأقسام الرئيسية في مخطط اللهب:

  • خيط UI — كود Dart: استدعاءات build() وردود الفعل على اللمسات وتحديثات الحالة. أي شيء يتجاوز ~8 مللي ثانية يهدد 60 إطار في الثانية.
  • خيط Raster — تحويل شجرة الطبقات إلى بكسل عبر GPU. استدعاءات الشيدر المكلفة والتركيب وsaveLayer تظهر هنا.
  • مؤشرات الإطار — مؤشرات الإطار الملونة في الأعلى؛ الإطارات الحمراء تجاوزت ميزانية 16.6 مللي ثانية.

تشغيل تفاعل محلَّل

// شغّل التطبيق في وضع profile
// flutter run --profile

// ثم في الطرفية اضغط 'V' لفتح DevTools،
// انتقل إلى تبويب Performance،
// انقر "Record" (أو اضغط زر التسجيل)،
// نفّذ التفاعل في تطبيقك (مثلاً تمرير قائمة)،
// انقر "Stop" — يُعرض مخطط اللهب تلقائياً.

// إطار بناء بطيء نموذجي يبدو هكذا في Dart:
class HeavyListItem extends StatelessWidget {
  final int index;
  const HeavyListItem({super.key, required this.index});

  @override
  Widget build(BuildContext context) {
    // سيء: عمل مكلف داخل build() — يظهر كشريط عريض في مخطط اللهب
    final items = List<int>.generate(1000, (i) => i * index);
    return ListTile(title: Text('Item $index: ${items.last}'));
  }
}

في مخطط اللهب ستجد HeavyListItem.build كشريط عريض بشكل غير طبيعي. انقل العمل المكلف خارج build() — إلى متغير مخبأ أو FutureBuilder أو isolate منفصل — وأعد التسجيل للتحقق من انخفاض وقت الإطار.

نصيحة: استخدم تراكب Widget Rebuild Stats (للتفعيل عبر Enhance Tracing > Track Widget Builds) لترى بالضبط أي الودجات أعادت البناء أثناء التسجيل. الودجت الذي يعيد البناء مئات المرات أثناء التمرير هو هدف تحسين واضح.

تبويب Memory — تخصيصات الكومة

يعرض تبويب Memory جدولاً زمنياً حياً لاستخدام الكومة في تطبيقك ويتيح لك أخذ لقطات الكومة لفحص الكائنات الحية ومقدار الذاكرة التي تشغلها. المقاييس الرئيسية هي:

  • Dart Heap — الذاكرة المخصصة بكود Dart الخاص بك (الكائنات والإغلاقات والمجموعات).
  • External — ذاكرة native مرجع إليها من كائنات Dart (الصور المفكوكة وحزم قناة النظام).
  • أحداث GC — علامات رأسية تظهر متى تشغّل جمع القمامة؛ ارتفاعات GC المتكررة يمكن أن تسبب اهتزازاً.

تشخيص تسرب ذاكرة الصور

// سيء: إنشاء ImageProvider جديد في كل بناء — يمنع إعادة استخدام الكاش
class LeakyAvatar extends StatelessWidget {
  final String url;
  const LeakyAvatar({super.key, required this.url});

  @override
  Widget build(BuildContext context) {
    return CircleAvatar(
      // NetworkImage يُعاد إنشاؤه في كل بناء؛ القديم يبقى حتى جمع القمامة
      backgroundImage: NetworkImage(url),
      radius: 40,
    );
  }
}

// جيد: خبّئ المزود في الحالة بحيث يُنشأ مرة واحدة
class CachedAvatar extends StatefulWidget {
  final String url;
  const CachedAvatar({super.key, required this.url});

  @override
  State<CachedAvatar> createState() => _CachedAvatarState();
}

class _CachedAvatarState extends State<CachedAvatar> {
  late final ImageProvider _provider;

  @override
  void initState() {
    super.initState();
    _provider = NetworkImage(widget.url); // يُنشأ مرة واحدة
  }

  @override
  Widget build(BuildContext context) {
    return CircleAvatar(backgroundImage: _provider, radius: 40);
  }
}

لتأكيد الإصلاح: خذ لقطة قبل الإصلاح، تفاعل مع التطبيق لتشغيل التسرب، خذ لقطة ثانية، وقارن بين الاثنتين. تعرض طريقة عرض الفرق في تبويب Memory الكائنات التي نمت — الكلاس الذي يجب أن يكون وحيداً لكن لديه 50 نسخة هو علامة تحذير فورية.

تبويب Network — حركة HTTP

يسجّل تبويب Network كل طلب HTTP وHTTPS يُنفَّذ عبر HttpClient في dart:io (وبالتالي عبر حزمتَي http وdio). لكل طلب يمكنك فحص: الرابط، والطريقة، ورمز الحالة، وترويسات الطلب والاستجابة، وحجم الجسم، والجدول الزمني (بحث DNS ← اتصال ← TLS ← TTFB ← تنزيل).

الاكتشافات الشائعة من تبويب Network:

  • طلبات تُشغَّل في كل إطار داخل build() بدلاً من initState().
  • أجسام استجابة كبيرة غير مضغوطة يمكن ضغطها بـ gzip من جانب الخادم.
  • ترويسات Cache-Control مفقودة تتسبب في إعادة تنزيل الأصول الثابتة.
  • طلبات متسلسلة يمكن تشغيلها بالتوازي مع Future.wait().
تحذير: يلتقط تبويب Network فقط حركة البيانات من مكدس HTTP الخاص بـ Dart. الطلبات المُنفَّذة عبر قنوات النظام (مثل حزم SDK الأصلية) غير مرئية هنا. استخدم مُحلّل النظام الأصلي (Xcode Instruments / Android Studio Profiler) لتلك الطلبات.

الجمع بين الأدوات — سير عمل التحليل

جلسة تحليل منضبطة تتبع هذا التسلسل:

  • 1. حدد العرض — تفاعل محدد يبدو بطيئاً أو شاشة تستهلك ذاكرة كثيرة.
  • 2. سجّل في تبويب Performance — ابحث عن الإطارات الحمراء والأشرطة الأعرض في خيط UI.
  • 3. تحقق من تبويب Memory — خذ لقطات قبل/بعد التفاعل؛ ابحث عن الكائنات التي يجب إطلاقها لكنها لا تزال في الذاكرة.
  • 4. افحص تبويب Network — تأكد من عدم وجود طلبات زائدة تعمل أثناء التفاعل البطيء.
  • 5. أصلح شيئاً واحداً في كل مرة — غيّر متغيراً واحداً، أعد التحليل، وقِس الأثر قبل الانتقال للتالي.
النقطة الرئيسية: يكشف تبويب Performance في Flutter DevTools عن مخططات لهب المعالج مع تفاصيل خيطَي UI وRaster؛ ويعرض تبويب Memory نمو الكومة وتسربات الكائنات عبر اللقطات؛ ويتتبع تبويب Network جداول زمنية لحركة HTTP. مستخدمةً معاً في وضع profile، تمنحك هذه الأدوات الثلاثة صورة دقيقة مستندة إلى الأدلة حول أين يُنفق تطبيقك الوقت والذاكرة.