0Pricing
Android Academy · درس

السيطرة على إعادة التركيب

استخدم معاملات مستقرة وقلّل عمليات إعادة التركيب

السيطرة على إعادة التركيب درس مجاني في Android Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Android Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Android Academy 4 دروس في المجموع.

إعادة التركيب: مفتاح أداء Compose

في Jetpack Compose، تُوصف واجهة المستخدم باستخدام الدوال. عندما تتغير الحالة، ينفذ Compose إعادة التركيب: أي يعيد تشغيل العناصر القابلة للتركيب التي قرأت تلك الحالة لتحديث الشاشة.

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

عرض عدد مرات إعادة التركيب

قبل الإصلاح، عليك القياس. يعرض Layout Inspector في Android Studio عدد مرات إعادة التركيب لكل عنصر قابل للتركيب في الوقت الفعلي. ويمكنك أيضًا استخدام مقاييس مترجم Compose أو عداد تصحيح سريع.

تتمثل إحدى الحيل البسيطة في أن يزيد SideEffect مرجعًا بمقدار واحد في كل مرة يُعاد فيها تركيب عنصر قابل للتركيب، كي تتمكن من تسجيل الحالات المفاجئة أثناء التطوير.

@Composable
fun RecompositionCounter(tag: String) {
    val count = remember { mutableStateOf(0) }
    SideEffect { count.value++ }
    Log.d("Recompose", "$tag recomposed ${count.value} times")
}

// Drop RecompositionCounter("PriceLabel") inside a composable
// to watch how often it re-runs while you interact.

اقرأ الحالة في أقرب وقت ممكن

لا يعيد Compose تركيب العناصر القابلة للتركيب التي تقرأ قيمة حالة إلا تلك العناصر. فإذا قرأ العنصر الأب الحالة، أُعيد تركيب العنصر الأب بأكمله؛ أما إذا قرأها عنصر فرعي صغير فقط، فأُعيد تركيب ذلك العنصر وحده.

لذلك، ادفع قراءات الحالة إلى أسفل الشجرة. يعيد الإصدار السيئ هنا تركيب Column بأكمله عند كل نبضة، بينما يعزلها الإصدار الجيد في التسمية.

// BAD: Column reads `seconds`, so everything recomposes each second
@Composable
fun TimerBad(seconds: Int) {
    Column {
        ExpensiveHeader()
        Text("Elapsed: $seconds")
    }
}

// GOOD: only the Text reads the value via a lambda
@Composable
fun TimerGood(seconds: () -> Int) {
    Column {
        ExpensiveHeader()
        Text("Elapsed: ${seconds()}")
    }
}

أجّل القراءات باستخدام Lambdas

هناك نمط فعّال يتمثل في تمرير دالة تعيد القيمة بدلًا من تمرير قيمة تتغير كثيرًا. والعنصر القابل للتركيب الذي يستدعي الدالة أخيرًا هو الوحيد الذي يُعاد تركيبه.

لهذا السبب يأخذ كل من Modifier.offset { ... } وgraphicsLayer { ... } دوالًا: فقيم التمرير والتحريك تتغير مع كل إطار، وتحافظ الدالة على إخراج إعادة التركيب من مرحلة التخطيط بالكامل.

// Passing the value: parent recomposes every frame of scroll
Box(Modifier.offset(y = scrollOffset.dp))

// Passing a lambda: skips recomposition, updates in the layout phase
Box(Modifier.offset { IntOffset(x = 0, y = scrollOffset.roundToInt()) })

// Same idea for alpha/scale during animation:
Image(
    painter = painter,
    contentDescription = null,
    modifier = Modifier.graphicsLayer { alpha = animatedAlpha() }
)

الثبات: لماذا يتخطى Compose إعادة التركيب

يمكن لـ Compose تخطي إعادة تركيب عنصر قابل للتركيب إذا كانت جميع معلماته مستقرة ولم تتغير. ويكون النوع مستقرًا عندما يستطيع Compose الوثوق بأن equals يعكس التغير الفعلي، وأن حقوله العامة لا تتغير دون علمه.

  • مستقرة: الأنواع البدائية، وString، وفئات البيانات غير القابلة للتغيير، وState.
  • غير مستقرة: واجهات List/Map، والفئات التي تحتوي على حقول var، والأنواع القادمة من وحدات لا تستخدم المترجم.

تفرض المعلمة غير المستقرة إعادة التركيب حتى عندما لا يتغير شيء.

// UNSTABLE: List is an interface; Compose cannot assume immutability,
// so UserList recomposes even if the contents are identical.
@Composable
fun UserList(users: List<User>) { /* ... */ }

data class User(val id: Long, val name: String) // stable: all vals

اجعل المعلمات مستقرة

هناك إصلاحان شائعان للمعلمات غير المستقرة:

  • استخدم مجموعات غير قابلة للتغيير من kotlinx.collections.immutable (مثل ImmutableList)، إذ يتعامل معها Compose على أنها مستقرة.
  • أضف التعليق التوضيحي @Immutable أو @Stable إلى فئة تتحكم فيها، لتتعهد لـ Compose بأنها لن تتغير.

عندها يستطيع Compose تخطي إعادة التركيب بأمان عند تمرير المثيل نفسه مرة أخرى.

import kotlinx.collections.immutable.ImmutableList
import androidx.compose.runtime.Immutable

@Immutable
data class UiState(
    val title: String,
    val users: ImmutableList<User>
)

// Stable parameter -> Compose can skip this when state is unchanged
@Composable
fun UserList(users: ImmutableList<User>) { /* ... */ }

remember: لا تعِد الحساب في كل إطار

يمكن أن تعمل العناصر القابلة للتركيب مرات عديدة. وأي عملية حسابية غير بسيطة تُجرى مباشرةً في جسم العنصر ستعمل في كل إعادة تركيب. لفّها داخل remember كي لا يُعاد حسابها إلا عند تغير مفاتيحها.

بالنسبة إلى القيم المشتقة من حالة أخرى، يُفضّل استخدام derivedStateOf، الذي لا يعيد الإصدار إلا عندما تتغير النتيجة المحسوبة فعليًا.

// Recomputes the sorted list only when `items` changes
val sorted = remember(items) { items.sortedBy { it.name } }

// derivedStateOf: only triggers readers when the BOOLEAN flips,
// not on every scroll pixel
val showButton by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 5 }
}
if (showButton) ScrollToTopButton()

مفاتيح مستقرة في القوائم الكسولة

في LazyColumn/LazyRow، امنح كل عنصر مفتاحًا مستقرًا. فمن دون المفاتيح، يؤدي إدراج العناصر أو إعادة ترتيبها إلى إجبار Compose على إعادة تركيب عناصر لم تتغير فعليًا وإعادة قياسها، لأنه يتتبعها حسب موضعها.

باستخدام مفتاح مستقر، يستطيع Compose مطابقة العناصر عبر التحديثات وتخطي العناصر التي لم تتغير.

LazyColumn {
    items(
        items = users,
        key = { user -> user.id }   // stable identity
    ) { user ->
        UserRow(user)
    }
}
// Now adding a user at the top reuses existing rows
// instead of recomposing the whole list.

ارفع الحالة ومرّر الأحداث إلى الأعلى

يمكن لمعلمات lambda غير المستقرة أن تعطل التخطي أيضًا إذا أُنشئ مثيل lambda جديد عند كل إعادة تركيب. ثبّت دوال الاستدعاء بتذكرها أو بالإشارة إلى دوال مستقرة.

وبدمج ذلك مع رفع الحالة (تعيش الحالة لدى المستدعي وتتدفق الأحداث إلى الأعلى)، تحافظ على انخفاض تكلفة العناصر القابلة للتركيب الطرفية وقابليتها للتخطي.

@Composable
fun SearchBar(query: String, onQueryChange: (String) -> Unit) {
    TextField(value = query, onValueChange = onQueryChange)
}

// In the caller, a remembered lambda keeps the reference stable:
val onChange = remember { { newValue: String -> viewModel.setQuery(newValue) } }
SearchBar(query = query, onQueryChange = onChange)

تجنب قراءة حالة التمرير أو التحريك في مستوى عالٍ جدًا

من الأخطاء الشائعة قراءة حالة سريعة التغير (إزاحة التمرير أو تقدم التحريك) في عنصر قابل للتركيب عالي المستوى. فهذا يجبر الشجرة الفرعية بأكملها على إعادة التركيب مع كل إطار.

أبقِ هذه القراءات داخل دوال Modifier (offset {} وgraphicsLayer {} وdrawBehind {}) كي يحدث العمل في مرحلة التخطيط أو الرسم، لا في إعادة التركيب. وهذا هو الفرق بين تمرير سلس بسرعة 60 إطارًا في الثانية وتمرير متقطع.

// Animated color used only for drawing -> stay in the draw phase
Box(
    Modifier.drawBehind {
        drawRect(color = animatedColor())  // lambda read, no recomposition
    }
)

قائمة تحقق لإعادة التركيب

عندما تبدو الشاشة متقطعة أثناء التفاعل، راجع هذه القائمة:

  • هل المعلمات مستقرة (بيانات غير قابلة للتغيير، ImmutableList)؟
  • هل تقرأ الحالة سريعة التغير في أدنى موضع ممكن، ويفضل داخل دوال المعدّلات؟
  • هل تملك القوائم الكسولة مفاتيح مستقرة؟
  • هل وضعت العمليات الحسابية المكلفة خلف remember أو derivedStateOf؟
  • هل أكد Layout Inspector أن العدد انخفض فعلًا؟

كل مربع تحدده يزيل عملًا مهدورًا من كل إطار.

تحقق سريع

تمرر List<User> إلى عنصر قابل للتركيب وتلاحظ أنه يُعاد تركيبه حتى عندما لا تتغير البيانات. ما الإصلاح المباشر الأفضل؟

مراجعة: إعادة تركيب أصغر وأندر

تعلمت التحكم في إعادة التركيب، وهي أكبر مشكلة أداء في Compose:

  • قِس الأعداد باستخدام Layout Inspector أو عداد تصحيح.
  • اقرأ الحالة في أدنى موضع ممكن؛ وأجّل القراءات باستخدام الدوال.
  • اجعل المعلمات مستقرة باستخدام البيانات غير القابلة للتغيير و@Immutable/ImmutableList.
  • استخدم remember وderivedStateOf لتجنب إعادة الحساب في كل إطار.
  • امنح العناصر الكسولة مفاتيح مستقرة؛ وأبقِ قراءات الحالة السريعة داخل دوال المعدّلات.

بعد ذلك ننتقل من وحدة المعالجة المركزية وإعادة التركيب إلى الذاكرة: العثور على التسريبات وإصلاحها.

الأسئلة الشائعة

هل درس «السيطرة على إعادة التركيب» مجاني؟

نعم — نص درس «السيطرة على إعادة التركيب» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Android Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Android Academy 4 دروس في المجموع.

ماذا ستتعلم في «السيطرة على إعادة التركيب»؟

استخدم معاملات مستقرة وقلّل عمليات إعادة التركيب تتمرن على Android Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Android Academy؟

لا تُشترط خبرة سابقة. Android Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «السيطرة على إعادة التركيب»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Android Academy هذا؟

نعم. كل درس في Android Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. قياس الأداء
  2. السيطرة على إعادة التركيب
  3. تسرّبات الذاكرة وإصلاحها
  4. ملفات تعريف بدء التشغيل والأساس
← العودة إلى Android Academy