Back Press Handling — مکانیزم رهگیری و پردازش دکمه سیستمی «بازگشت» در Android است که تعیین میکند با فشار دادن آن چه عملی انجام شود. بر اساس Android Developers (2024)، از Android 11 به بعد متد سنتی onBackPressed() با OnBackPressedDispatcher جایگزین شده است. API جدید به مؤلفهها در هر سطحی از سلسلهمراتب امکان رهگیری فشار را میدهد، نه فقط Activity. تفاوت کلیدی — پشتیبانی از چندین callback در زنجیره با اولویتها.
نکات اصلی
Back Press Handling — مکانیزم سیستمی Android است که تعیین میکند وقتی کاربر دکمه سختافزاری یا نرمافزاری «بازگشت» را فشار میدهد چه اتفاقی میافتد. بسته به زمینه، فشار میتواند: صفحه فعلی را ببندد و به صفحه قبلی برگردد، صفحهکلید را مخفی کند، Drawer یا Bottom Sheet را ببندد، اگر کاربر در صفحه ریشه است از برنامه خارج شود.
رفتار دکمه «بازگشت» با هر نسخه Android تکامل یافته است. در Android 10 ژستهای ناوبری ظاهر شدند، در Android 11 — OnBackPressedDispatcher به عنوان API استاندارد، و در Android 13 — پشتیبانی بهبودیافته predictive back gesture، که در آن سیستم قبل از اجرای واقعی عمل، انیمیشن انتقال را نشان میدهد. Google به طور مداوم به سمت رفتار قابل پیشبینی و سازگار Back Press در همه دستگاهها حرکت میکند.
پردازش صحیح دکمه «بازگشت» — عنصر حیاتی UX در برنامه Android است. کاربر انتظار دارد که فشار «بازگشت» او را به صفحه قبلی در پشته ناوبری برگرداند، نه اینکه برنامه را به طور غیرمنتظره ببندد. نقض این انتظار یکی از دلایل اصلی نظرات منفی و رتبههای پایین در Google Play است.
تاریخچه Back Press API در Android تکامل کلی پلتفرم را منعکس میکند: از یک متد ساده در Activity تا یک سیستم callback منعطف با پشتیبانی از lifecycle و Compose. بیایید سه مرحله توسعه را بررسی کنیم.
از اولین Android API، دکمه «بازگشت» در متد onBackPressed کلاس Activity پردازش میشد. توسعهدهنده این متد را override میکرد و منطق خود را مینوشت. مشکل این بود که Fragment و View نمیتوانستند فشار را رهگیری کنند — تمام کنترل از طریق Activity انجام میشد. این منجر به Activityهای حجیم و زنجیره if-else پیچیده برای تعیین اینکه چه کسی باید فشار را پردازش کند، میشد.
با Activity 1.0.0 (AndroidX) Google OnBackPressedDispatcher را معرفی کرد. این یک توزیعکننده مرکزی است که callback را از هر مؤلفهای — Activity، Fragment، Dialog، View سفارشی — دریافت میکند. Callbackها با تعیین ترتیب (از طریق priority) ثبت میشوند و میتوانند به صورت پویا اضافه یا حذف شوند. OnBackPressedDispatcher زودتر از onBackPressed قدیمی فراخوانی میشود که امکان رهگیری فشار را قبل از پردازش آن توسط Activity فراهم میکند.
Android 13 predictive back gesture را معرفی کرد — انیمیشن سیستمی که نشان میدهد فشار «بازگشت» به کجا منتهی میشود، حتی قبل از اینکه کاربر ژست را کامل کند. برای پشتیبانی از این انیمیشن، توسعهدهندگان باید از OnBackPressedDispatcher استفاده کنند و مشخص کنند که آیا callback از انیمیشن سیستمی از طریق ویژگی isEnabled پشتیبانی میکند یا خیر. اگر callback از predictive animation پشتیبانی نکند، سیستم انیمیشن پیشفرضی را نشان میدهد که ممکن است با زمینه برنامه مطابقت نداشته باشد.
| API | حداقل SDK | پشتیبانی Fragment | Predictive Back |
|---|---|---|---|
| onBackPressed | API Level 1 | از طریق Activity | خیر |
| OnBackPressedDispatcher | Activity 1.0.0 | مستقیم | جزئی |
| OnBackPressedDispatcher + lifecycle | Activity 1.3.0 | Lifecycle-aware | کامل |
OnBackPressedDispatcher — هسته جدید Back Press API است. این زنجیره callback را مدیریت میکند و آنها را به ترتیب تا اولین callback که رویداد را پردازش میکند فراخوانی میکند. اگر هیچ callbackی فشار را پردازش نکند، توزیعکننده عمل پیشفرض را انجام میدهد — فراخوانی finish() برای Activity یا popBackStack() برای Navigation Component.
Callback از طریق addCallback با مشخص کردن LifecycleOwner و شی OnBackPressedCallback ثبت میشود. Callback شامل ویژگی isEnabled است — اگر false باشد، callback نادیده گرفته میشود. برای priority میتوان مقداری از 0 (پایینترین) تا Integer.MAX_VALUE ارسال کرد. Fragment Activity Result API از این مکانیزم برای ثبت خودکار callback متصل به چرخه حیات استفاده میکند.
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)
}
Callback به طور خودکار حذف میشود وقتی LifecycleOwner به حالت DESTROYED میرود. این مشکل قدیمی نشت callback در هنگام چرخش صفحه را حل میکند. اگر callback به Fragment اضافه شده باشد، تضمین میشود که هنگام نابودی Fragment حذف شود. برای غیرفعال کردن موقت callback از ویژگی isEnabled استفاده میشود — میتوان آن را بدون حذف و ثبت مجدد تغییر داد.
ترتیب فراخوانی معکوس ترتیب اضافه شدن است: آخرین callback اضافه شده ابتدا کنترل را دریافت میکند. این منطقی است زیرا تو در توترین عنصر UI (مثلاً Bottom Sheet داخل Fragment) باید فشار را زودتر از Fragment والد پردازش کند. اگر عمیقترین callback فشار را پردازش نکند (isEnabled = false)، کنترل به بعدی در زنجیره منتقل میشود.
Fragment API یکپارچگی خاص خود را با OnBackPressedDispatcher از طریق متد requireActivity().onBackPressedDispatcher فراهم میکند. از Fragment 1.2.0 به بعد، هر Fragment میتواند callback خود را ثبت کند که به طور خودکار به چرخه حیات Fragment متصل شده و هنگام نابودی آن حذف میشود.
ثبت callback در Fragment در onCreate، onViewCreated یا حتی در خود View انجام میشود — فقط مهم است که LifecycleOwner (Fragment) فعال باشد. وقتی Fragment به حالت STARTED میرود، callback فعال میشود، در 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)
}
}
دیالوگها و BottomSheet به طور خودکار فشار «بازگشت» را برای بسته شدن رهگیری میکنند. اگر نیاز به انجام اقدام اضافی قبل از بسته شدن دارید — callback را با اولویت بالاتر ثبت کنید. مهم: اگر setCancelable(false) را روی Dialog تنظیم کرده باشید، callback کار نخواهد کرد — این رفتار سیستمی است.
Jetpack Compose API اعلامی برای پردازش دکمه «بازگشت» از طریق تابع composable BackHandler فراهم میکند. BackHandler شامل enabled (مقدار بولی) و onBack — callback که هنگام فشار فراخوانی میشود. اگر enabled = false باشد، فشار در طول زنجیره به جلو منتقل میشود.
BackHandler به طور خودکار OnBackPressedCallback را در OnBackPressedDispatcher Activity والد ثبت میکند. این چرخه حیات composable را در نظر میگیرد: هنگام خروج از ترکیب، callback حذف میشود. enabled میتواند به حالت متصل شود — مثلاً فقط در صورت وجود تغییرات ذخیرهنشده در فرم، دیالوگ تأیید را نشان دهد.
@Composable
fun EditScreen(hasUnsavedChanges: Boolean) {
BackHandler(enabled = hasUnsavedChanges) {
// نمایش دیالوگ تأیید
}
Column {
TextField(value = ..., onValueChange = ...)
}
}
Predictive back gesture در Compose از Compose 1.5.0 پشتیبانی میشود. BackHandler اگر روی دستگاه فعال باشد، به طور خودکار انیمیشن انتقال سیستمی را مدیریت میکند. برای predictive-animation سفارشی از تغییردهنده predictiveBackHandler استفاده کنید که پیشرفت ژست را از 0 تا 1 برمیگرداند.
Back Press Handling ساده به نظر میرسد، اما در عمل توسعهدهندگان مرتکب اشتباهات سیستماتیکی میشوند. بیایید رایجترین مشکلات و راهحلهای آنها را بر اساس توصیههای Google و تجربه جامعه بررسی کنیم.
فراخوانی مستقیم finish() در handleOnBackPressed میتواند منجر به خروج غیرمنتظره از برنامه شود اگر در پشته ناوبری صفحات پسزمینه وجود داشته باشد. همیشه قبل از بستن Activity، NavController.backStack را از طریق Navigation Component یا Coordinator بررسی کنید.
اگر callback را بدون LifecycleOwner (با استفاده از addCallback قدیمی بدون پارامتر) ثبت کنید، callback برای همیشه زنده میماند و ممکن است اگر Activity قبلاً نابود شده باشد NullPointerException ایجاد کند. همیشه از addCallback(this, callback) با LifecycleOwner استفاده کنید.
برای پنجرههای مدال (Bottom Sheet, Dialog) همیشه isEnabled = true را فقط زمانی تنظیم کنید که پنجره قابل مشاهده است. از addCallback با لامبدا که وضعیت پنجره را بررسی میکند استفاده کنید. Navigation Component این را به طور خودکار برای NavHost مدیریت میکند.
فشار سریع دوبل «بازگشت» میتواند منجر به فراخوانی دوبل finish() شود. از flag یا 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 برای دستگاههای دارای سه دکمه و ناوبری ژستی یکسان است. کد رهگیری به نوع ناوبری بستگی ندارد — سیستم خود ژست را به فراخوانی توزیعکننده تبدیل میکند.
فعال کنید predictive back را در Developer Options شبیهساز Android 13+. از ADB استفاده کنید: `adb shell settings put global enable_back_animation 1`. پس از فعالسازی، انیمیشن سیستمی پیشنمایش انتقال را هنگام فشار «بازگشت» نشان میدهد.
دو شرط را بررسی کنید: LifecycleOwner باید در حالت STARTED یا RESUMED باشد و isEnabled callback باید true باشد. اگر هر دو شرط برآورده شدهاند، مطمئن شوید که callback به OnBackPressedDispatcher صحیح اضافه شده است — در Fragment از requireActivity().onBackPressedDispatcher استفاده کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید