ليش تطبيق Flutter بطيء؟ عبدالله بن سنكر يشخّص الأسباب والعلاج
ليش تطبيق Flutter بطيء؟ عبدالله بن سنكر يشخّص الأعراض واحداً واحداً: تقطيع التمرير، وتجمّد الشاشة، وإعادة البناء الزائدة، والتأثيرات الثقيلة، مع علاج محدد لكل عرض.

تطبيق فلاتر بطيء
عبدالله بن سنكر
المستخدم ما يكتب لك «فيه تأخير في الإطارات»، يكتب «التطبيق ثقيل» ويحذفه. والمطوّر يرد: «عندي يشتغل تمام». الاثنين صادقين غالباً، لأن البطء له أشكال، وكل شكل له سبب مختلف وعلاج مختلف. عبدالله بن سنكر يطوّر بـ Flutter، ويتعامل مع البطء مثل الطبيب: ما يكتب علاجاً قبل ما يحدد العرض. هذي طريقته في التشخيص، عرضاً عرضاً.
قبل أي علاج: قِس في المكان الصح
أشهر خطأ إنك تحكم على الأداء من وضع التطوير. نسخة debug بطيئة بطبيعتها، لأنها مبنية عشان تعطيك Hot Reload وأدوات فحص، مو عشان السرعة. القياس الحقيقي يكون بنسخة profile على جهاز حقيقي، ويفضّل يكون جهازاً متوسطاً أو ضعيفاً، مو أحدث جوال عندك. افتح Flutter DevTools، وراقب شريط الإطارات: على شاشة ٦٠ هرتز كل إطار عنده تقريباً ١٦ ملّي ثانية. وانتبه إن فيه خيطين: خيط الواجهة اللي يبني الويدجتات ويحسب التخطيط، وخيط الرسم اللي يحوّل النتيجة لبكسلات. معرفة أي واحد فيهم متأخر تقطع نص الطريق للحل.
العرض الأول: التمرير يتقطّع في القوائم
هذا أكثر عرض تشوفه، وأسبابه محصورة تقريباً:
- قائمة تُبنى كاملة دفعة وحدة: ListView بقائمة children جاهزة يبني كل العناصر حتى اللي ما ظهرت. العلاج ListView.builder أو ما يشابهه، عشان يُبنى بس اللي على الشاشة وقريب منها.
- صور أكبر من مكانها: صورة ٤٠٠٠ بكسل تُعرض في مربع ١٠٠ بكسل، والجهاز يفك الصورة كاملة في الذاكرة. حدّد cacheWidth أو cacheHeight عشان تنفك بالحجم المطلوب، واطلب من الخادم نسخة مصغّرة أصلاً.
- عنصر القائمة نفسه معقّد: طبقات متداخلة من الظلال والقص والشفافية في كل بطاقة. بسّط البطاقة، وخلّ الثقيل منها في شاشة التفاصيل.
- ارتفاعات تنحسب في كل مرة: لو كل العناصر بنفس الارتفاع، itemExtent أو prototypeItem يوفّر على الإطار حسابات كثيرة.
العرض الثاني: الشاشة تتجمّد لحظة ثم ترجع
تضغط زراً فتوقف الحركة ثانية كاملة، حتى مؤشر التحميل يتجمد. هذا غالباً شغل ثقيل على الخيط الرئيسي: تحويل ملف JSON كبير، أو ضغط صورة، أو تشفير، أو قراءة ملف بشكل متزامن. Dart يشغّل كودك في معزل واحد افتراضياً، فأي حساب طويل يوقف رسم الواجهة معه. العلاج نقل الحساب لمعزل منفصل عبر Isolate.run أو compute، فتبقى الواجهة حية والحساب شغّال في الخلفية. وقبل هذا اسأل: هل لازم التطبيق يحوّل خمسة آلاف عنصر أصلاً؟ أحياناً العلاج الأصح إن الخادم يرسل صفحة من خمسين.
العرض الثالث: ضغطة صغيرة تعيد بناء الشاشة كلها
في DevTools فيه خيار يعرض عدد مرات إعادة بناء كل ويدجت. شغّله واضغط زر «إعجاب» في بطاقة، ولو شفت الشاشة كلها تنبني من جديد، لقيت السبب. الحالة محفوظة في أعلى الشجرة، و setState هناك يعيد بناء كل اللي تحتها. العلاج إنك تنزّل الحالة لأقرب مكان يحتاجها، وتقسّم الويدجت الكبير لويدجتات أصغر، وتستخدم const لكل ويدجت ما يتغير، وفي أدوات إدارة الحالة تستمع لجزء محدد من الحالة بدل الحالة كلها.
وفيه فخ يتكرر كثير: إنشاء Future داخل دالة build وتمريره لـ FutureBuilder. كل إعادة بناء تصنع طلباً جديداً للخادم، فتشوف مؤشر التحميل يرجع بدون سبب، والخادم يستقبل طلبات مكررة. أنشئ الطلب مرة وحدة خارج build واحفظه، وخلّ build يعرض النتيجة بس.
العرض الرابع: الحركات والتأثيرات تتقطّع
إذا كان خيط الرسم هو المتأخر، فالمشكلة في طريقة الرسم مو في منطقك. بعض الويدجتات تطلب من المحرّك يرسم طبقة كاملة منفصلة ثم يدمجها، وهذا مكلف لو تكرر في كل إطار:
- ويدجت Opacity فوق شجرة كبيرة أثناء حركة: استبدله بـ FadeTransition أو AnimatedOpacity.
- تأثير التمويه BackdropFilter على مساحة كبيرة أو في عناصر قائمة متكررة.
- القص بحواف ناعمة مع saveLayer في عناصر كثيرة، و ShaderMask بدون داعي.
- حركة مستمرة صغيرة، مثل مؤشر أو أيقونة تنبض، تجبر الشاشة كلها تنرسم: لفّها بـ RepaintBoundary عشان ينرسم جزؤها بس.
العرض الخامس: فتح بطيء، أو ثقل يزيد مع الوقت
افتح main وشوف وش يصير قبل runApp: تهيئة خدمات تحليلات، وتحميل إعدادات، وفتح قاعدة بيانات، واستدعاء خادم. كل سطر هنا يأخّر أول شاشة. الأسلم إنك تهيئ الضروري فقط، وتأجل الباقي لما بعد ظهور الواجهة. وأحياناً البطء مو في التطبيق أصلاً: خادم يرد في ثلاث ثوانٍ يخلّي أسرع تطبيق في العالم يبان بطيئاً، فقيس زمن الطلبات قبل ما تلوم الإطار.
والوجه الثاني لهذا العرض: أول ربع ساعة كل شي ناعم، وبعد ساعة استخدام يبدأ التطبيق يتثاقل، وأحياناً يطفي فجأة على الأجهزة الضعيفة. هذا في الغالب تسريب ذاكرة: شاشات تنقفل لكن أجزاء منها تبقى حية في الخلفية. والأسباب المعتادة معروفة:
- متحكمات ما انتخلص منها: AnimationController و TextEditingController و ScrollController لازم لها dispose لما تنقفل الشاشة.
- اشتراكات ما انلغت: StreamSubscription أو مستمع على قاعدة بيانات لحظية يبقى يشتغل لشاشة ما عادت موجودة.
- مؤقتات Timer دورية تشتغل للأبد بعد ما طلع المستخدم من الشاشة.
- كاش صور بلا سقف في شاشة تعرض مئات الصور عالية الدقة.
تبويب الذاكرة في DevTools يكشف هذا: افتح الشاشة واقفلها عشر مرات، ولو استهلاك الذاكرة يطلع وما ينزل، فعندك شي ما ينظَّف.
الخلاصة اللي يكررها عبدالله عبدالرحيم بن سنكر الكندي: Flutter نادراً يكون هو سبب البطء، السبب غالباً كود ما انتبه لطريقة عمل الإطار. وفي مشاريع منصة أزهل الرقمية، فحص الأداء بنسخة profile على جهاز متوسط خطوة ثابتة قبل كل إصدار، مو ردة فعل بعد أول تقييم سلبي في المتجر.
عندك فكرة؟ الاستشارة الأولى علينا
كلّم عبدالله مباشرة على واتساب — رسالتك توصل له هو، مو لرد آلي. نسمع فكرتك ونقول لك وش تحتاج وكم تكلّف، ببلاش.
مقالات ذات صلة
كل المقالات
تحويل الصوت إلى نص
عبدالله بن سنكر
تحويل الصوت إلى نص بالعربي: عبدالله بن سنكر يختبر اللهجات قبل ما يختار
عبدالله بن سنكر يشرح كيف تختبر أداة تحويل الصوت إلى نص على تسجيلاتك العربية الحقيقية: اللهجات، ومعنى نسبة الخطأ، وفصل المتحدثين، والخصوصية، وأين يفيدك التفريغ فعلاً.
اقرأ المقال
دروس بناء المشاريع الرقمية
عبدالله بن سنكر
عبدالله بن سنكر: أنماط تتكرر في معرض أعمال من ٢٧ مشروعاً
عبدالله بن سنكر يستخلص أنماطاً عامة من بناء ٢٧ مشروعاً في معرض أعمال أزهل: وين يروح الوقت فعلاً، وليش تكبر لوحة الإدارة، ومتى ينقذك الوصف المكتوب، وش اللي يتأخر دائماً.
اقرأ المقال
إعلانات قوقل
عبدالله بن سنكر
عبدالله بن سنكر: إعلانات قوقل للبحث، نيّة الباحث قبل الميزانية
ليش حملة قوقل تصرف وما تبيع؟ عبدالله بن سنكر يشرح كيف تقرأ نيّة الباحث، وتفرز الكلمات بين معلوماتية وتجارية وشرائية، وتستخدم أنواع المطابقة والكلمات السلبية صح.
اقرأ المقال