برخی از پیکربندیهای دستگاه میتوانند در حین اجرای برنامه تغییر کنند. این موارد شامل موارد زیر است، اما محدود به آنها نیست:
- اندازه نمایش برنامه
- جهت گیری صفحه نمایش
- اندازه و وزن فونت
- محلی
- حالت تاریک در مقابل حالت روشن
- در دسترس بودن صفحه کلید
بیشتر این تغییرات پیکربندی به دلیل برخی تعاملات کاربر رخ میدهد. برای مثال، چرخاندن یا تا کردن دستگاه، میزان فضای صفحه نمایش موجود برای برنامه شما را تغییر میدهد. به همین ترتیب، تغییر تنظیمات دستگاه مانند اندازه فونت، زبان یا تم مورد نظر، مقادیر مربوط به آنها را در شیء Configuration تغییر میدهد.
این پارامترها معمولاً به تغییرات بزرگی در رابط کاربری برنامه شما نیاز دارند، به طوری که پلتفرم اندروید برای زمان تغییر آنها، مکانیزمی هدفمند دارد. این مکانیزم Activity recreation نام دارد.
فعالیت تفریحی
سیستم هنگامی که تغییری در پیکربندی رخ میدهد، یک Activity دوباره ایجاد میکند. برای انجام این کار، سیستم onDestroy() را فراخوانی میکند و نمونه Activity موجود را از بین میبرد. سپس با استفاده از onCreate() یک نمونه جدید ایجاد میکند و این نمونه Activity جدید با پیکربندی جدید و بهروزرسانیشده مقداردهی اولیه میشود. این همچنین به این معنی است که سیستم رابط کاربری را نیز با پیکربندی جدید دوباره ایجاد میکند.
رفتار بازسازی به برنامه شما کمک میکند تا با پیکربندیهای جدید سازگار شود، به این صورت که به طور خودکار برنامه شما را با منابع جایگزینی که با پیکربندی جدید دستگاه مطابقت دارند، مجدداً بارگذاری میکند.
مثال تفریحی
یک TextView را در نظر بگیرید که یک عنوان استاتیک را با استفاده از android:text="@string/title" نمایش میدهد، همانطور که در یک فایل XML طرحبندی تعریف شده است. وقتی View ایجاد میشود، متن را دقیقاً یک بار، بر اساس زبان فعلی، تنظیم میکند. اگر زبان تغییر کند، سیستم Activity را دوباره ایجاد میکند. در نتیجه، سیستم View را نیز دوباره ایجاد میکند و آن را بر اساس زبان جدید با مقدار صحیح مقداردهی اولیه میکند.
این بازسازی همچنین هر حالتی را که به عنوان فیلد در Activity یا در هر یک از Fragment ، View یا سایر اشیاء موجود در آن نگهداری میشود، پاک میکند. دلیل این امر آن است که بازسازی Activity یک نمونه کاملاً جدید از Activity و رابط کاربری ایجاد میکند. علاوه بر این، Activity قدیمی دیگر قابل مشاهده یا معتبر نیست، بنابراین هرگونه ارجاع باقی مانده به آن یا اشیاء موجود در آن قدیمی است. آنها میتوانند باعث ایجاد اشکالات، نشت حافظه و خرابی شوند.
انتظارات کاربران
کاربر یک برنامه انتظار دارد که وضعیت (state) حفظ شود. اگر کاربری در حال پر کردن فرمی باشد و برنامه دیگری را در حالت چند پنجرهای برای مراجعه به اطلاعات باز کند، اگر به فرم پاک شده یا به جای دیگری در برنامه بازگردد، تجربه کاربری بدی خواهد داشت. به عنوان یک توسعهدهنده، شما باید از طریق تغییرات پیکربندی و بازآفرینی فعالیتها، یک تجربه کاربری پایدار ارائه دهید.
برای تأیید اینکه آیا وضعیت (state) در برنامه شما حفظ شده است یا خیر، میتوانید اقداماتی را انجام دهید که باعث تغییرات پیکربندی هم در زمانی که برنامه در پیشزمینه (foreground) و هم در زمانی که در پسزمینه (background) است، میشوند. این اقدامات عبارتند از:
- چرخاندن دستگاه
- ورود به حالت چند پنجرهای
- تغییر اندازه برنامه در حالت چند پنجرهای یا پنجره آزاد
- تا کردن یک دستگاه تاشو با چندین نمایشگر
- تغییر تم سیستم، مانند حالت تاریک در مقابل حالت روشن
- تغییر اندازه فونت
- تغییر زبان سیستم یا برنامه
- اتصال یا قطع اتصال صفحه کلید سخت افزاری
- اتصال یا قطع اتصال داک
سه رویکرد اصلی وجود دارد که میتوانید برای حفظ وضعیت مربوطه از طریق Activity Recreation اتخاذ کنید. استفاده از هر کدام به نوع وضعیتی که میخواهید حفظ کنید بستگی دارد:
- ماندگاری محلی برای مدیریت مرگ فرآیند برای دادههای پیچیده یا بزرگ. ذخیرهسازی محلی پایدار شامل پایگاههای داده یا
DataStoreمیشود. - اشیاء حفظشده مانند نمونههای
ViewModelبرای مدیریت وضعیت مربوط به رابط کاربری در حافظه در حالی که کاربر به طور فعال از برنامه استفاده میکند. - حالت نمونه ذخیره شده برای مدیریت مرگ فرآیند آغاز شده توسط سیستم و حفظ حالت گذرا که به ورودی یا پیمایش کاربر بستگی دارد.
برای مطالعهی جزئیات APIهای مربوط به هر یک از این موارد، و زمان مناسب استفاده از هر یک، به بخش «ذخیره حالتهای رابط کاربری» مراجعه کنید.
محدود کردن فعالیتها و تفریحات
شما میتوانید از اجرای خودکار activity برای تغییرات پیکربندی خاص جلوگیری کنید. اجرای خودکار Activity منجر به ایجاد مجدد کل رابط کاربری و هر شیء مشتق شده از Activity میشود. ممکن است دلایل خوبی برای جلوگیری از این کار داشته باشید. به عنوان مثال، ممکن است برنامه شما نیازی به بهروزرسانی منابع در طول یک تغییر پیکربندی خاص نداشته باشد، یا ممکن است محدودیت عملکرد داشته باشید. در این صورت، میتوانید اعلام کنید که activity شما خودش تغییر پیکربندی را مدیریت میکند و از راهاندازی مجدد activity شما توسط سیستم جلوگیری کنید.
برای غیرفعال کردن بازآفرینی فعالیت برای تغییرات پیکربندی خاص، نوع پیکربندی را به android:configChanges در ورودی <activity> در فایل AndroidManifest.xml خود اضافه کنید. مقادیر ممکن در مستندات مربوط به ویژگی android:configChanges آمده است.
کد مانیفست زیر، هنگام تغییر جهت صفحه نمایش و در دسترس بودن صفحه کلید، قابلیت بازآفرینی Activity را برای MyActivity غیرفعال میکند:
<activity
android:name=".MyActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:label="@string/app_name">
برخی از تغییرات پیکربندی همیشه باعث میشوند که فعالیت مجدداً راهاندازی شود. شما نمیتوانید آنها را غیرفعال کنید. برای مثال، نمیتوانید تغییر رنگهای پویای معرفی شده در اندروید ۱۲L (سطح API ۳۲) را غیرفعال کنید.
واکنش به تغییرات پیکربندی در سیستم View
در سیستم View ، وقتی تغییری در پیکربندی رخ میدهد که شما Activity Creation را برای آن غیرفعال کردهاید، Activity فراخوانی Activity.onConfigurationChanged() را دریافت میکند. هر View پیوست شده نیز فراخوانی View.onConfigurationChanged() را دریافت میکند. برای تغییرات پیکربندی که شما به android:configChanges اضافه نکردهاید، سیستم Activity را طبق معمول دوباره ایجاد میکند.
متد فراخوانی onConfigurationChanged() یک شیء Configuration دریافت میکند که پیکربندی جدید دستگاه را مشخص میکند. فیلدهای موجود در شیء Configuration را بخوانید تا پیکربندی جدید شما مشخص شود. برای ایجاد تغییرات بعدی، منابعی را که در رابط خود استفاده میکنید، بهروزرسانی کنید. هنگامی که سیستم این متد را فراخوانی میکند، شیء Resources فعالیت شما بهروزرسانی میشود تا منابع را بر اساس پیکربندی جدید برگرداند. این به شما امکان میدهد عناصر رابط کاربری خود را بدون راهاندازی مجدد سیستم فعالیت خود، مجدداً تنظیم کنید.
برای مثال، پیادهسازی onConfigurationChanged() زیر بررسی میکند که آیا صفحهکلید موجود است یا خیر:
کاتلین
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// Checks whether a keyboard is available
if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_YES) {
Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show()
} else if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_NO) {
Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show()
}
}
جاوا
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks whether a keyboard is available
if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show();
} else if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO){
Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show();
}
}
اگر نیازی به بهروزرسانی برنامه خود بر اساس این تغییرات پیکربندی ندارید، میتوانید از onConfigurationChanged() استفاده نکنید. در این صورت، تمام منابعی که قبل از تغییر پیکربندی استفاده میشدند، همچنان مورد استفاده قرار میگیرند و شما فقط از راهاندازی مجدد فعالیت خود جلوگیری کردهاید. به عنوان مثال، یک برنامه تلویزیونی ممکن است نخواهد هنگام اتصال یا جدا شدن یک صفحه کلید بلوتوث واکنش نشان دهد.
حفظ حالت
وقتی از این تکنیک استفاده میکنید، باید همچنان وضعیت (state) را در طول چرخه حیات عادی اکتیویتی حفظ کنید. این به دلایل زیر است:
- تغییرات اجتنابناپذیر: تغییرات پیکربندی که نمیتوانید از آنها جلوگیری کنید، میتوانند برنامه شما را مجدداً راهاندازی کنند.
- مرگ فرآیند: برنامه شما باید بتواند مرگ فرآیند آغاز شده توسط سیستم را مدیریت کند. اگر کاربر برنامه شما را ترک کند و برنامه به پسزمینه برود، سیستم ممکن است برنامه را از بین ببرد.
واکنش به تغییرات پیکربندی در Jetpack Compose
Jetpack Compose به برنامه شما اجازه میدهد تا راحتتر به تغییرات پیکربندی واکنش نشان دهد. با این حال، اگر Activity recreation را برای همه تغییرات پیکربندی که امکان انجام آن وجود دارد غیرفعال کنید، برنامه شما همچنان باید تغییرات پیکربندی را به درستی مدیریت کند.
شیء Configuration در سلسله مراتب رابط کاربری Compose با ترکیب LocalConfiguration local در دسترس است. هر زمان که تغییر کند، توابع composable که از LocalConfiguration.current میخواند، آن را دوباره ترکیب میکنند. برای اطلاعات بیشتر در مورد نحوه کار ترکیبهای محلی، به دادههای محلی با CompositionLocal مراجعه کنید.
مثال
در مثال زیر، یک composable تاریخ را با فرمت خاصی نمایش میدهد. composable با فراخوانی ConfigurationCompat.getLocales() به همراه LocalConfiguration.current به تغییرات پیکربندی زبان سیستم واکنش نشان میدهد.
@Composable
fun DateText(year: Int, dayOfYear: Int) {
val dateTimeFormatter = DateTimeFormatter.ofPattern(
"MMM dd",
ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
)
Text(
dateTimeFormatter.format(LocalDate.ofYearDay(year, dayOfYear))
)
}
برای جلوگیری از بازتولید Activity هنگام تغییر زبان، Activity میزبان کد Compose باید از تغییرات پیکربندی زبان انصراف دهد. برای انجام این کار، android:configChanges روی locale|layoutDirection تنظیم میکنید.
تغییرات پیکربندی: مفاهیم کلیدی و بهترین شیوهها
اینها مفاهیم کلیدی هستند که هنگام کار بر روی تغییرات پیکربندی باید بدانید:
- پیکربندیها: پیکربندیهای دستگاه، نحوه نمایش رابط کاربری به کاربر را تعریف میکنند، مانند اندازه نمایش برنامه، زبان یا قالب سیستم.
- تغییرات پیکربندی: پیکربندیها از طریق تعامل کاربر تغییر میکنند. برای مثال، کاربر ممکن است تنظیمات دستگاه یا نحوه تعامل فیزیکی خود با دستگاه را تغییر دهد. هیچ راهی برای جلوگیری از تغییرات پیکربندی وجود ندارد.
- بازآفرینی
Activity: تغییرات پیکربندی به طور پیشفرض منجر به بازآفرینیActivityمیشود. این یک مکانیزم داخلی برای مقداردهی مجدد وضعیت برنامه برای پیکربندی جدید است. - تخریب
Activity: بازسازیActivityباعث میشود سیستم نمونهActivityقدیمی را از بین ببرد و یک نمونه جدید به جای آن ایجاد کند. نمونه قدیمی اکنون منسوخ شده است. هرگونه ارجاع باقی مانده به آن منجر به نشت حافظه، اشکالات یا خرابی میشود. - وضعیت: وضعیت موجود در نمونه
Activityقدیمی در نمونهActivityجدید وجود ندارد، زیرا آنها دو نمونه شیء متفاوت هستند. وضعیت برنامه و کاربر را همانطور که در بخش «ذخیره وضعیتهای رابط کاربری» توضیح داده شده است، حفظ کنید. - انصراف: انصراف از فعالیت تفریحی برای نوعی تغییر پیکربندی، یک بهینهسازی بالقوه است. این امر مستلزم آن است که برنامه شما در واکنش به پیکربندی جدید به درستی بهروزرسانی شود.
برای ارائه یک تجربه کاربری خوب، نکات زیر را رعایت کنید:
- برای تغییرات مکرر پیکربندی آماده باشید: فرض نکنید که تغییرات پیکربندی نادر هستند یا هرگز اتفاق نمیافتند، صرف نظر از سطح API، فرم فاکتور یا ابزار رابط کاربری. وقتی کاربری باعث تغییر پیکربندی میشود، انتظار دارد برنامهها بهروزرسانی شوند و با پیکربندی جدید به درستی کار کنند.
- حفظ وضعیت: وقتی
Activityدوباره اجرا میشود، وضعیت کاربر از دست نرود. وضعیت را همانطور که در بخش «ذخیره وضعیتهای رابط کاربری» توضیح داده شده است، حفظ کنید. - از انصراف به عنوان یک راه حل سریع خودداری کنید: انصراف از
Activityrecreation را به عنوان یک میانبر برای جلوگیری از از دست دادن وضعیت (state) در نظر نگیرید. انصراف از activity recreation مستلزم آن است که شما به وعده مدیریت تغییر عمل کنید و همچنان ممکن است به دلیلActivityrecreation از سایر تغییرات پیکربندی، مرگ فرآیند یا بستن برنامه، وضعیت را از دست بدهید. غیرفعال کردن کاملActivityrecreation غیرممکن است. وضعیت را همانطور که در بخش «ذخیره وضعیتهای رابط کاربری» توضیح داده شده است، حفظ کنید. - از تغییرات پیکربندی اجتناب نکنید: برای جلوگیری از تغییرات پیکربندی و ایجاد مجدد
Activity، محدودیتهایی روی جهتگیری، نسبت ابعاد یا قابلیت تغییر اندازه اعمال نکنید. این کار تأثیر منفی بر کاربرانی میگذارد که میخواهند از برنامه شما به روش دلخواه خود استفاده کنند.
مدیریت تغییرات پیکربندی مبتنی بر اندازه
تغییرات پیکربندی مبتنی بر اندازه میتواند در هر زمانی اتفاق بیفتد و زمانی که برنامه شما روی یک دستگاه با صفحه نمایش بزرگ اجرا میشود که کاربران میتوانند وارد حالت چند پنجرهای شوند، احتمال وقوع آن بیشتر است. آنها انتظار دارند برنامه شما در آن محیط به خوبی کار کند.
دو نوع کلی تغییر اندازه وجود دارد: قابل توجه و غیر قابل توجه. تغییر اندازه قابل توجه ، تغییری است که در آن مجموعه متفاوتی از منابع جایگزین به دلیل تفاوت در اندازه صفحه نمایش، مانند عرض، ارتفاع یا کمترین عرض، برای پیکربندی جدید اعمال میشود. این منابع شامل مواردی است که خود برنامه تعریف میکند و منابعی که از هر یک از کتابخانههای آن گرفته شدهاند.
محدود کردن فعالیتهای تفریحی برای تغییرات پیکربندی مبتنی بر اندازه
وقتی که شما قابلیت بازسازی Activity creation) را برای تغییرات پیکربندی مبتنی بر اندازه غیرفعال میکنید، سیستم Activity را دوباره ایجاد نمیکند. در عوض، فراخوانی Activity.onConfigurationChanged() را دریافت میکند. هر view پیوست شده فراخوانی View.onConfigurationChanged() را دریافت میکند.
وقتی در فایل مانیفست خود android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout" دارید، بازآفرینی Activity برای تغییرات پیکربندی مبتنی بر اندازه غیرفعال است.
اجازه دهید فعالیتها برای تغییرات پیکربندی مبتنی بر اندازه، دوباره فعال شوند
در اندروید ۷.۰ (سطح API 24) و بالاتر، بازسازی Activity فقط برای تغییرات پیکربندی مبتنی بر اندازه در صورتی که تغییر اندازه قابل توجه باشد، رخ میدهد. هنگامی که سیستم به دلیل اندازه ناکافی، Activity دوباره ایجاد نمیکند، ممکن است به جای آن Activity.onConfigurationChanged() و View.onConfigurationChanged() را فراخوانی کند.
در مورد فراخوانیهای Activity و View زمانی که Activity دوباره ایجاد نشده است، باید به چند نکته توجه داشت:
- در اندروید ۱۱ (سطح API 30) تا اندروید ۱۳ (سطح API 33)، تابع
Activity.onConfigurationChanged()فراخوانی نمیشود. - یک مشکل شناختهشده وجود دارد که در آن ممکن است
View.onConfigurationChanged()در برخی موارد در اندروید ۱۲L (سطح API ۳۲) و نسخههای اولیه اندروید ۱۳ (سطح API ۳۳) فراخوانی نشود. برای اطلاعات بیشتر، به این مشکل عمومی مراجعه کنید. این مشکل از آن زمان در نسخههای بعدی اندروید ۱۳ و اندروید ۱۴ برطرف شده است.
برای کدی که وابسته به گوش دادن به تغییرات پیکربندی مبتنی بر اندازه است، توصیه میکنیم به جای تکیه بر Activity recreation یا Activity.onConfigurationChanged() از یک View کاربردی با View.onConfigurationChanged() () لغو شده استفاده کنید.