فهم خط أنابيب التصيير في Flutter
فهم خط أنابيب التصيير في Flutter
قبل أن تتمكن من تحسين أداء تطبيق Flutter، تحتاج إلى نموذج ذهني واضح حول كيفية تحويل Flutter لكود Dart إلى بكسلات على الشاشة. يستخدم Flutter معمارية ثلاثية الأشجار — شجرة الودجات، وشجرة العناصر، وشجرة التصيير — ويمر كل إطار بخط أنابيب دقيق من المراحل. فهم هذا الخط هو الأساس لجميع أعمال الأداء التي تليه.
معمارية الأشجار الثلاث
يحافظ Flutter على ثلاثة أشجار متوازية ومتزامنة في وقت التشغيل. كل منها يخدم غرضاً مختلفاً:
- شجرة الودجات — مخططات غير قابلة للتغيير تنشأ من خلال دوال
build(). الودجات هي كائنات تهيئة خفيفة الوزن؛ يتم إنشاؤها والتخلص منها في كل إطار بدون عقوبة أداء. - شجرة العناصر — عقد قابلة للتغيير وطويلة العمر تعمل كجسر بين الودجات وكائنات التصيير. يحتفظ العنصر بمرجع للودجت الذي هيأه وكائن التصيير الذي يرسمه. يقارن Flutter شجرة الودجات بشجرة العناصر لتحديد ما الذي تغير فعلياً.
- شجرة التصيير — محرك التخطيط والرسم الفعلي. كل
RenderObjectيعرف حجمه وموضعه وكيفية رسم نفسه. هذه الشجرة مكلفة الإنشاء، لذلك يعيد Flutter استخدام كائنات التصيير كلما أمكن.
مراحل خط أنابيب التصيير
يمر كل إطار يصيّره Flutter بهذه المراحل بالترتيب:
- ١. البناء — يتم استدعاء
build()على الودجات المتسخة، منتجةً شجرة فرعية جديدة من كائنات تهيئة الودجات. - ٢. التوفيق بين العناصر (المقارنة) — يتجول Flutter في كلتا الشجرتين القديمة والجديدة في آن واحد. إذا لم يتغير نوع الودجت في موضع معين، يتم تحديث العنصر الموجود (وكائن التصيير الخاص به) في مكانه. إذا تغير النوع، يتم إلغاء تركيب العنصر القديم وإنشاء عنصر جديد.
- ٣. التخطيط — يمرر Flutter القيود لأسفل شجرة التصيير (من الأب إلى الابن) ويستلم الأحجام للأعلى (من الابن إلى الأب). يحسب كل
RenderObjectحجمه الخاص ويحدد مواضع أبنائه. - ٤. التركيب — يقرر Flutter أي كائنات تصيير تحتاج إلى طبقة تركيب خاصة بها (مثل الشفافية أو القص أو التحويلات). تسمح الطبقات لوحدة معالجة الرسوميات بتخزين البكسلات مؤقتاً وإعادة استخدامها.
- ٥. الرسم — تسجل كائنات التصيير أوامر الرسم في لوحة الطبقة. فقط الكائنات التي أُبطل رسمها يتم إعادة تسجيلها.
- ٦. التحويل النقطي — يرسل المحرك الطبقات إلى وحدة معالجة الرسوميات (Skia أو Impeller) التي تحولها إلى بكسلات فعلية.
كيف يُشغّل setState() خط الأنابيب
عند استدعاء setState()، يضع Flutter علامة على العنصر المقابل باعتباره متسخاً. في نبضة الإطار التالية يستدعي المجدول build() على ذلك العنصر، ويتجول المُوفِّق لأسفل من هناك. والأهم من ذلك، هذا لا يعيد بناء الشجرة بأكملها تلقائياً — فقط الشجرة الفرعية التي جذرها العنصر المتسخ يتم إعادة بنائها. ومع ذلك، إذا كان العنصر المتسخ في مستوى عالٍ من الشجرة (مثل MaterialApp أو Scaffold على مستوى أعلى)، فقد تكون إعادة البناء المتتالية ضخمة للغاية.
تصور أي عنصر يصبح متسخاً
// يتم إعادة بناء CounterDisplay فقط عند تغيّر _count،
// لأن setState() يُستدعى داخل _CounterPageState،
// الذي يملك CounterDisplay في ناتج build().
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0;
@override
Widget build(BuildContext context) {
// يتم إعادة وصف هذه الشجرة الفرعية بالكامل في كل استدعاء setState.
// يُوفِّق Flutter بينها وبين شجرة العناصر — فقط
// العناصر التي تغيّرت تهيئة ودجاتها يتم تحديثها.
return Column(
children: [
// StatelessWidget؛ يُعاد بناؤه فقط إذا تغيرت معاملاته.
const HeaderBanner(),
// يُعاد بناؤه في كل مرة لأن _count يُمرَّر كمعامل.
CounterDisplay(count: _count),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('زيادة'),
),
],
);
}
}
هوية العنصر والمُوفِّق
يستخدم مُوفِّق Flutter النوع + المفتاح لتحديد ما إذا كان سيحدّث عنصراً موجوداً أم يستبدله. إذا كان الودجت في الموضع i من الشجرة الجديدة يحمل نفس النوع وقت التشغيل (ونفس المفتاح إن وُجد) كالعنصر في الموضع i، يستدعي Flutter element.update(newWidget) — محتفظاً بكائن التصيير. إذا كان النوع مختلفاً، يفكّك Flutter العنصر القديم ويتخلص من كائن التصيير وينشئ كل شيء من جديد. لهذا السبب تتسبب التغييرات في نوع الودجت (حتى بالخطأ) في عمليات إعادة إنشاء شجرة فرعية مكلفة.
عدم تطابق النوع يُجبر على إعادة إنشاء الشجرة الفرعية بالكامل
// سيئ: تغيير النوع الشرطي يُجبر على إعادة إنشاء كائن التصيير في كل تبديل.
Widget build(BuildContext context) {
return _showPadded
? Padding(
padding: const EdgeInsets.all(8),
child: MyCard(),
)
: MyCard(); // موضع مختلف في الشجرة — فتحة عنصر مختلفة
}
// جيد: أبقِ هيكل الشجرة مستقراً؛ انقل الشرط إلى الداخل.
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(_showPadded ? 8.0 : 0.0),
child: MyCard(),
);
}
التخطيط: القيود تنزل، الأحجام تصعد
بروتوكول التخطيط في Flutter أنيق ومحدد. يمرر الأب كائن BoxConstraints لكل ابن (الحد الأدنى والأقصى للعرض والارتفاع). يختار الابن حجماً ضمن تلك القيود ويعيده. ثم يحدد الأب موضع الابن. هذا التخطيط بمرور واحد (في الحالة الشائعة) هو أحد أسباب سرعة Flutter — لا توجد دورات إعادة تدفق متعددة المرور كما في المتصفح.
ListView داخل Column بدون ارتفاع ثابت، أو الودجات IntrinsicHeight/IntrinsicWidth المتداخلة) تفرض تخطيطاً متعدد المرور وقد تضر بالأداء بشكل كبير. ستتعلم كيفية اكتشاف هذه المشكلات وإصلاحها في درس تحسين التخطيط.الخلاصة
خط أنابيب تصيير Flutter هو تسلسل محدد جيداً: البناء ← التوفيق ← التخطيط ← التركيب ← الرسم ← التحويل النقطي. تضمن معمارية الأشجار الثلاث أن يتم القدر الأدنى من العمل في كل إطار — لكن فقط إذا كان هيكل شجرة الودجات مستقراً. التغييرات غير الضرورية في نوع الودجت، ونطاقات إعادة البناء المفرطة، وتركيبات الودجات المكثفة في التخطيط، هي الأسباب الجذرية لمعظم مشكلات أداء Flutter. مع هذا النموذج الذهني في مكانه، ستكون لكل تقنية تحسين تتعلمها تفسير آلي واضح.