تحسين الأداء

تقليل حجم التطبيق: إزالة الكود غير المستخدم والتحميل المؤجل وتحسين الأصول

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

تقليل حجم التطبيق في Flutter

يُحمَّل التطبيق الأصغر بشكل أسرع، ويستهلك مساحة تخزين أقل، ويقلل من معدل الانسحاب على الشبكات ذات النطاق الترددي المحدود. يمنحك Flutter عدة أدوات متكاملة لتقليص حجم APK أو AAB أو IPA: إزالة الكود غير المستخدم (Tree Shaking)، والتحميل المؤجل للمكونات (تقسيم التطبيق إلى أجزاء تُحمَّل عند الطلب)، وتحسين الأصول (ضغط الصور والخطوط)، وتقرير تحليل الحجم المدمج من خلال الأمر flutter build --analyze-size.

ملاحظة: قِس الحجم دائماً على بنية الإصدار النهائي (release). تحتوي بنيات التصحيح (debug) على Dart VM والمرصد والرموز غير المضغوطة — وهي عادةً أكبر بـ 4 إلى 10 مرات من بنية الإصدار ولا تعكس ما يُنزّله المستخدمون.

1. إزالة الكود غير المستخدم (Tree Shaking)

يُجري مترجم Dart عملية إزالة الكود غير المستخدم تلقائياً عند التحويل في وضع الإصدار النهائي (flutter build apk --release). تحلل هذه العملية شجرة الاستيراد وتزيل كل فئة ودالة وثابت لا يمكن الوصول إليه من main(). لهذا السبب، استيراد حزمة ثقيلة واستخدام أداة واحدة منها فقط لا يضخّم حجم الحزمة بالضرورة — تُحذف الرموز غير المستخدمة.

  • تعمل عملية الإزالة بشكل أفضل عندما تُصدّر الحزم ملفات فردية بدلاً من ملف مجمّع واحد.
  • التفكير العكسي (Reflection) والمكتبة dart:mirrors تُعطّل الإزالة للكود المتأثر — تجنّبهما في بيئة الإنتاج.
  • الكود المولَّد بواسطة build_runner قابل للإزالة الكاملة طالما أن المراجع ثابتة.

التحقق من إزالة الكود غير المستخدم

// math_utils.dart — مكتبة بدالتين على مستوى المشروع
double square(double x) => x * x;
double cube(double x) => x * x * x;  // لا يُستدعى في أي مكان

// main.dart
import 'math_utils.dart';

void main() {
  print(square(4));  // square() فقط يمكن الوصول إليه
  // cube() غير مُستخدَم، فيُزيله مترجم Dart من الحزمة
}

2. التحميل المؤجل (Lazy Imports)

بالنسبة للميزات التي نادراً ما تُستخدم — تدفقات الإعداد الأولي وشاشات المساعدة ولوحات المشرف — يمكنك تقسيمها إلى مكتبة مؤجلة. يُصدر مترجم Dart لقطة .js أو لقطة أصلية منفصلة لكل مكتبة مؤجلة، ويقوم وقت التشغيل بتنزيلها أو تحميلها فقط عندما يحتاجها التطبيق فعلياً. في Flutter Web تُعيَّن مباشرةً إلى تقسيم كود JavaScript؛ وعلى الأجهزة المحمولة تُدعم عبر المكونات المؤجلة على Android (Android App Bundles + Play Feature Delivery).

صيغة الاستيراد المؤجل

import 'package:myapp/features/onboarding/onboarding_screen.dart'
    deferred as onboarding;

class HomeScreen extends StatelessWidget {
  Future<void> _launchOnboarding(BuildContext context) async {
    // تُحمَّل المكتبة من القرص أو الشبكة هنا، فقط عند الاستدعاء الأول
    await onboarding.loadLibrary();
    if (!context.mounted) return;
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (_) => onboarding.OnboardingScreen(),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () => _launchOnboarding(context),
          child: const Text('بدء الإعداد'),
        ),
      ),
    );
  }
}
نصيحة: استدعِ loadLibrary() في أقرب وقت ممكن — على سبيل المثال في initState() — حتى تكون المكتبة مخزّنة مؤقتاً عندما يضغط المستخدم على الزر. الدالة loadLibrary() تعمل بصورة متكررة آمنة: استدعاؤها عدة مرات لا يسبب مشاكل.

3. تحسين الأصول

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

  • استخدم WebP أو AVIF بدلاً من PNG/JPEG. يوفر WebP ملفات أصغر بنسبة 25 إلى 35 بالمئة بجودة بصرية مماثلة. قم بالتحويل باستخدام cwebp أو Squoosh.
  • أعد الحجم لدقة العرض. تضمين صورة بدقة 4K تُعرض بحجم 200 × 200 بكسل منطقي يُهدر نحو 400 ضعف البيانات الضرورية. استخدم المتغيرات الأقرب بحجم 1x و2x و3x.
  • قطّع الخطوط (Font Subsetting). إذا شحنت خطاً مخصصاً، استخدم أداة مثل pyftsubset للاحتفاظ بنطاقات Unicode التي يستخدمها تطبيقك فعلاً فقط. يمكن تقليص خط لاتيني عربي كامل من 500 كيلوبايت إلى أقل من 50 كيلوبايت.
  • احذف الأصول غير المستخدمة. الأصول المُعلَنة في pubspec.yaml تحت flutter: assets: تُضمَّن دائماً بغض النظر عن تحميلها وقت التشغيل.

4. قراءة تقرير تحليل الحجم

يُنتج مفتاح --analyze-size تفصيلاً دقيقاً لكل حزمة مُجمَّعة وأصل في حزمة الإصدار. شغّله على النحو التالي:

توليد تقرير تحليل الحجم

# Android App Bundle (موصى به لمتجر Play)
flutter build appbundle --release --analyze-size

# iOS (يتطلب سلسلة أدوات Xcode)
flutter build ios --release --analyze-size

# موقع الإخراج مُطبَع في نهاية البناء، مثلاً:
# A summary of your APK analysis can be found at:
# /Users/you/.flutter-devtools/apk-code-size-analysis_01.json

# افتحه في Dart DevTools للحصول على خريطة شجرية تفاعلية:
dart devtools
# ثم افتح ملف .json المحفوظ في تبويب "App Size"

يُجمّع التقرير الرموز حسب الحزمة ← المكتبة ← الفئة ← العضو. أكبر العقد هي أهدافك للتحسين. من أبرز النتائج الشائعة:

  • الحزم التي تجلب تبعيات متعدية ضخمة (مثل Firebase أو Google Maps).
  • الكود المولَّد (تسلسل JSON والمسارات) الذي يكون أكبر من المتوقع.
  • خطوط الأيقونات غير المقطوعة المضمَّنة بالكامل (مثل MaterialIcons — يُزيل Flutter بيانات الأيقونات تلقائياً منذ Flutter 2.3).
تحذير: يُجري مفتاح --analyze-size التجميع مع أدوات قياس إضافية تزيد وقت البناء قليلاً. لا تستخدمه في خط أنابيب CI العادي — احتفظ به للمراجعات الدورية للحجم.

5. تقنيات إضافية لتقليل الحجم

  • بناء APKs مقسّمة حسب ABI: يُولّد الأمر flutter build apk --split-per-abi ملفات APK منفصلة لـ arm64-v8a وarmeabi-v7a وx86_64، تحتوي كل منها على المكتبة الأصلية لتلك البنية فقط.
  • تشفير كود Dart: يُجرّد المفتاح --obfuscate --split-debug-info=./debug-info أسماء الرموز القابلة للقراءة، مما يقلل حجم اللقطة بنسبة 5 إلى 15 بالمئة ويُقوّي الحماية من الهندسة العكسية.
  • تجنّب تضمين أصول الاختبار: احتفظ ببيانات الاختبار في مجلد test/ لا في assets/ — فقط المدخلات تحت assets: في pubspec.yaml تُضمَّن في الحزمة.

الخلاصة

تقليل حجم تطبيق Flutter هو انضباط متعدد الطبقات. إزالة الكود غير المستخدم تعمل تلقائياً لكنها تعتمد على كود ثابت وغير انعكاسي. التحميل المؤجل يتيح لك تقسيم الميزات النادرة إلى أجزاء تُحمَّل عند الطلب. تحسين الأصول — الصيغ الصحيحة للصور ومتغيرات الدقة وتقطيع الخطوط — يحقق في الغالب أكبر وفورات مطلقة. وأخيراً، يمنحك flutter build --analyze-size مع Dart DevTools خريطة شجرية مرئية لإيجاد أكبر مساهمي الانتفاخ في الحزمة والتخلص منهم.

الخلاصة الرئيسية: قِس قبل أن تُحسّن. استخدم --analyze-size لتحديد الاختناقات الحقيقية، وطبّق إصلاحات مستهدفة (استيرادات مؤجلة للميزات الكبيرة، WebP للصور، تقسيم APK للمكتبات الأصلية)، وأعد القياس للتحقق من التحسن.