Back Press Handling هي آلية اعتراض ومعالجة زر الرجوع النظامي في Android، والتي تحدد الإجراء الذي يتم تنفيذه عند الضغط عليه. وفقًا لـ Android Developers (2024)، بدءًا من Android 11، تم استبدال الطريقة التقليدية onBackPressed() بـ OnBackPressedDispatcher. تسمح واجهة API الجديدة للمكونات على أي مستوى في التسلسل الهرمي باعتراض الضغط، وليس فقط Activity. الفرق الرئيسي — دعم استدعاءات متعددة في سلسلة ذات أولويات.
الملامح الرئيسية
Back Press Handling هي آلية نظام في Android تحدد ما يحدث عندما يضغط المستخدم على زر الرجوع المادي أو البرمجي. اعتمادًا على السياق، يمكن أن يؤدي الضغط إلى: إغلاق الشاشة الحالية والعودة إلى الشاشة السابقة، إخفاء لوحة المفاتيح، إغلاق Drawer أو Bottom Sheet، الخروج من التطبيق إذا كان المستخدم على الشاشة الرئيسية.
تطور سلوك زر الرجوع تطور مع كل إصدار من Android. Android 10 قدم التنقل بالإيماءات، Android 11 قدم OnBackPressedDispatcher كواجهة API قياسية، و Android 13 قدم دعمًا محسنًا للإيماءة التنبؤية للرجوع، حيث يعرض النظام رسمًا متحركًا للانتقال قبل تنفيذ الإجراء فعليًا. تتجه Google باستمرار نحو سلوك Back Press قابل للتوقع ومتسق عبر جميع الأجهزة.
المعالجة الصحيحة لزر الرجوع هي عنصر حاسم في تجربة المستخدم في تطبيق Android. يتوقع المستخدم أن الضغط على رجوع يعيده إلى الشاشة السابقة في مكدس التنقل، وليس إغلاق التطبيق بشكل غير متوقع. انتهاك هذا التوقع هو أحد الأسباب الرئيسية للمراجعات السلبية والتقييمات المنخفضة في Google Play.
يعكس تاريخ Back Press API في Android التطور العام للمنصة: من طريقة بسيطة في Activity إلى نظام استدعاءات مرن مع دورة حياة ودعم Compose. دعونا نلقي نظرة على ثلاث مراحل من التطور.
منذ أول Android API، كان زر الرجوع يُعالج في طريقة onBackPressed لفئة Activity. كان المطور يتجاوز هذه الطريقة ويكتب منطقه الخاص. كانت المشكلة أن Fragment و View لم يكونا قادرين على اعتراض الضغط — كل التحكم كان يمر عبر Activity. أدى هذا إلى Activities منتفخة وسلاسل if-else معقدة لتحديد من يجب أن يعالج الضغط.
مع Activity 1.0.0 (AndroidX)، قدمت Google OnBackPressedDispatcher. هذا موزع مركزي يقبل الاستدعاءات من أي مكون — Activity و Fragment و Dialog و View مخصصة. تُسجل الاستدعاءات بترتيب (عبر priority) ويمكن إضافتها أو إزالتها ديناميكيًا. يتم استدعاء OnBackPressedDispatcher قبل onBackPressed القديم، مما يسمح باعتراض الضغط قبل أن تعالجه Activity.
قدم Android 13 الإيماءة التنبؤية للرجوع — رسمًا متحركًا للنظام يوضح أين سيؤدي الضغط على رجوع قبل أن يكمل المستخدم الإيماءة. لدعم هذا الرسم المتحرك، يجب على المطورين استخدام OnBackPressedDispatcher وتحديد ما إذا كان الاستدعاء يدعم الرسم المتحرك للنظام عبر خاصية isEnabled. إذا كان الاستدعاء لا يدعم الرسم المتحرك التنبؤي، يعرض النظام رسمًا متحركًا افتراضيًا قد لا يتوافق مع سياق التطبيق.
| API | SDK الأدنى | دعم Fragment | الرجوع التنبؤي |
|---|---|---|---|
| onBackPressed | API Level 1 | عبر Activity | لا |
| OnBackPressedDispatcher | Activity 1.0.0 | مباشر | جزئي |
| OnBackPressedDispatcher + lifecycle | Activity 1.3.0 | واعٍ بدورة الحياة | كامل |
OnBackPressedDispatcher هو جوهر واجهة Back Press API الجديدة. يدير سلسلة من الاستدعاءات، مستدعيًا إياها بالترتيب حتى يعالج أولها الحدث. إذا لم يعالج أي استدعاء الضغط، ينفذ الموزع الإجراء الافتراضي — استدعاء finish() لـ Activity أو popBackStack() لمكون Navigation.
يُسجل الاستدعاء عبر addCallback مع LifecycleOwner وكائن OnBackPressedCallback. يحتوي الاستدعاء على خاصية isEnabled — إذا تم تعيينها على false، يتم تخطي الاستدعاء. للأولوية، يمكنك تمرير قيمة من 0 (الأدنى) إلى Integer.MAX_VALUE. تستخدم Fragment Activity Result API هذه الآلية للتسجيل التلقائي للاستدعاءات المرتبطة بدورة الحياة.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback = object : OnBackPressedCallback(enabled = true) {
override fun handleOnBackPressed() {
if (isDrawerOpen) { closeDrawer() }
else { isEnabled = false; onBackPressed() }
}
}
onBackPressedDispatcher.addCallback(this, callback)
}
يتم إزالة الاستدعاءات تلقائيًا عندما ينتقل LifecycleOwner إلى حالة DESTROYED. هذا يحل المشكلة القديمة لتسرب الاستدعاءات أثناء تدوير الشاشة. إذا تمت إضافة استدعاء في Fragment، فمن المضمون إزالته عند تدمير Fragment. لتعطيل استدعاء مؤقتًا، استخدم خاصية isEnabled — يمكنك تبديلها دون إزالة وإعادة تسجيل.
ترتيب الاستدعاء هو عكس ترتيب الإضافة: آخر استدعاء مضاف يحصل على التحكم أولاً. هذا منطقي لأن عنصر واجهة المستخدم الأكثر تداخلاً (على سبيل المثال، Bottom Sheet داخل Fragment) يجب أن يعالج الضغط قبل Fragment الأصلي. إذا كان الاستدعاء الأعمق لا يعالج الضغط (isEnabled = false)، ينتقل التحكم إلى التالي في السلسلة.
توفر Fragment API تكاملها الخاص مع OnBackPressedDispatcher عبر طريقة requireActivity().onBackPressedDispatcher. بدءًا من Fragment 1.2.0، يمكن لكل Fragment تسجيل استدعائه الخاص، الذي يرتبط تلقائيًا بدورة حياة Fragment ويُزال عند تدميره.
يتم تسجيل استدعاء في Fragment في onCreate أو onViewCreated أو حتى في View نفسها — المهم هو أن LifecycleOwner (Fragment) نشط. عندما ينتقل Fragment إلى حالة STARTED، يتم تمكين الاستدعاء؛ وعند STOPPED، يتم تعطيله. هذا يضمن أن Fragment المخفي (في ViewPager) لن يعالج ضغط الرجوع.
class EditorFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
showDiscardDialog()
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}
}
تعترض الحوارات و BottomSheets ضغط الرجوع تلقائيًا للإغلاق. إذا كنت بحاجة إلى تنفيذ إجراء إضافي قبل الإغلاق — سجل استدعاء بأولوية أعلى. مهم: إذا قمت بتعيين setCancelable(false) على Dialog، لن يتم تفعيل الاستدعاء — هذا سلوك نظامي.
يوفر Jetpack Compose واجهة API تصريحية لمعالجة زر الرجوع عبر دالة BackHandler composable. تقبل BackHandler enabled (حالة منطقية) و onBack — استدعاء يُستدعى عند الضغط. إذا كان enabled = false، يتم تمرير الضغط إلى أسفل السلسلة.
يسجل BackHandler تلقائيًا OnBackPressedCallback في OnBackPressedDispatcher الخاص بـ Activity الأم. يراعي دورة حياة composable: عند الخروج من التركيب، يُحذف الاستدعاء. يمكن ربط enabled بالحالة — على سبيل المثال، إظهار حوار تأكيد فقط إذا كان النموذج يحتوي على تغييرات غير محفوظة.
@Composable
fun EditScreen(hasUnsavedChanges: Boolean) {
BackHandler(enabled = hasUnsavedChanges) {
// Show confirmation dialog
}
Column {
TextField(value = ..., onValueChange = ...)
}
}
الإيماءة التنبؤية للرجوع Predictive back gesture في Compose مدعومة بدءًا من Compose 1.5.0. يتعامل BackHandler تلقائيًا مع الرسم المتحرك لانتقال النظام إذا كان ممكّنًا على الجهاز. للرسوم المتحركة التنبؤية المخصصة، استخدم معدِّل predictiveBackHandler، الذي يُرجع تقدم الإيماءة من 0 إلى 1.
يبدو Back Press Handling بسيطًا، ولكن في الممارسة العملية يرتكب المطورون عددًا من الأخطاء المنهجية. دعونا نلقي نظرة على المشكلات الأكثر شيوعًا وحلولها بناءً على توصيات Google وخبرة المجتمع.
الاستدعاء المباشر لـ finish() في handleOnBackPressed قد يؤدي إلى خروج غير متوقع من التطبيق إذا كانت هناك شاشات خلفية في مكدس التنقل. تحقق دائمًا من NavController.backStack عبر Navigation Component أو Coordinator قبل إغلاق Activity.
إذا قمت بتسجيل استدعاء بدون LifecycleOwner (باستخدام addCallback القديم بدون المعامل)، سيعيش الاستدعاء إلى الأبد وقد يسبب NullPointerException إذا كانت Activity قد دُمّرت بالفعل. استخدم دائمًا addCallback(this, callback) مع LifecycleOwner.
للنوافذ المشروطة (Bottom Sheet، Dialog)، اضبط دائمًا isEnabled = true فقط عندما تكون النافذة مرئية. استخدم addCallback مع لامدا تتحقق من حالة النافذة. يدير Navigation Component هذا تلقائيًا لـ NavHost.
الضغط المزدوج السريع على رجوع قد يؤدي إلى استدعاء مزدوج لـ finish(). استخدم علمًا أو throttleLast للحماية من الاستدعاءات المتكررة خلال 500 مللي ثانية. يعالج Navigation Component هذه الحالة بشكل مضمن، ولكن في السيناريوهات المخصصة تحتاج إلى تطبيق الحماية يدويًا.
private var lastBackPressTime = 0L
override fun handleOnBackPressed() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastBackPressTime > 500) {
lastBackPressTime = currentTime
navigateBack()
}
}
الأسئلة الشائعة
onBackPressed أُهمل في Android 11 لأنه يعمل فقط على مستوى Activity. يسمح OnBackPressedDispatcher لأي مكون (Fragment، Dialog، View) باعتراض الضغط من خلال آلية واحدة مع دعم دورة الحياة.
يقوم نظام Android بتحويل إيماءة السحب من الحافة إلى ضغطة رجوع نظامية قبل أن يستقبلها التطبيق. على مستوى OnBackPressedDispatcher، لا يمكنك التمييز بين هذين الحدثين — كلاهما يصل كـ handleOnBackPressed.
نعم، معالجة OnBackPressedDispatcher هي نفسها للأجهزة ذات الأزرار الثلاثة والتنقل بالإيماءات. لا يعتمد رمز الاعتراض على نوع التنقل — النظام نفسه يحول الإيماءة إلى استدعاء للموزع.
فعّل الرجوع التنبؤي في Developer Options لمحاكي Android 13+. استخدم ADB: `adb shell settings put global enable_back_animation 1`. بعد التفعيل، سيعرض الرسم المتحرك للنظام معاينة للانتقال عند الضغط على رجوع.
تحقق من شرطين: يجب أن يكون LifecycleOwner في حالة STARTED أو RESUMED، ويجب أن يكون isEnabled الخاص بالاستدعاء true. إذا تحقق كلا الشرطين، تأكد من إضافة الاستدعاء إلى OnBackPressedDispatcher الصحيح — استخدم requireActivity().onBackPressedDispatcher في Fragment.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.