Android Kotlin Fundamentals 04.2: موقعیت های پیچیده چرخه حیات

این کد لبه بخشی از دوره آموزشی Android Kotlin Fundamentals است. اگر به ترتیب روی کدها کار کنید، بیشترین ارزش را از این دوره خواهید گرفت. همه کدهای دوره در صفحه فرود کد لبه های کدهای Android Kotlin Fundamentals فهرست شده اند.

مقدمه

در آخرین کد لبه، شما در مورد چرخه های حیات Activity و Fragment یاد گرفتید و روش هایی را که زمانی که حالت چرخه حیات در فعالیت ها و قطعات تغییر می کند فراخوانی می شوند، بررسی کردید. در این کد لبه، شما چرخه حیات فعالیت را با جزئیات بیشتری بررسی می کنید. همچنین با کتابخانه چرخه حیات Android Jetpack آشنا می‌شوید، که می‌تواند به شما کمک کند رویدادهای چرخه حیات را با کدهایی که سازمان‌دهی‌تر و نگهداری آسان‌تر است، مدیریت کنید.

آنچه از قبل باید بدانید

  • فعالیت چیست و چگونه در برنامه خود ایجاد کنید.
  • اصول اولیه چرخه‌های حیات Activity و Fragment و تماس‌هایی که هنگام حرکت یک اکتیویتی بین حالت‌ها فراخوانی می‌شوند.
  • نحوه نادیده گرفتن متدهای onCreate() و onStop() onStop بازگشت به تماس چرخه حیات برای انجام عملیات در زمان‌های مختلف در چرخه حیات اکتیویتی یا قطعه.

چیزی که یاد خواهید گرفت

  • نحوه راه‌اندازی، راه‌اندازی و توقف بخش‌هایی از برنامه‌تان در چرخه حیات.
  • نحوه استفاده از کتابخانه چرخه حیات اندروید برای ایجاد ناظر چرخه حیات، و مدیریت چرخه حیات فعالیت و قطعه.
  • چگونه خاموش شدن فرآیند Android روی داده‌های برنامه شما تأثیر می‌گذارد، و چگونه می‌توان آن داده‌ها را به‌طور خودکار هنگامی که Android برنامه شما را می‌بندد، ذخیره و بازیابی کرد.
  • چگونه چرخش دستگاه و سایر تغییرات پیکربندی تغییراتی را در حالت‌های چرخه زندگی ایجاد می‌کند و بر وضعیت برنامه شما تأثیر می‌گذارد.

کاری که خواهی کرد

  • برنامه DessertClicker را تغییر دهید تا یک تابع تایمر را شامل شود و آن تایمر را در زمان‌های مختلف در چرخه عمر فعالیت شروع و متوقف کنید.
  • برنامه را برای استفاده از کتابخانه چرخه حیات اندروید تغییر دهید و کلاس DessertTimer را به یک مشاهده گر چرخه حیات تبدیل کنید.
  • پل اشکال زدایی اندروید ( adb ) را برای شبیه‌سازی خاموش شدن فرآیند برنامه و برگشت‌های تماس چرخه حیات که در آن زمان رخ می‌دهد، راه‌اندازی و استفاده کنید.
  • روش onSaveInstanceState() را برای حفظ داده های برنامه که ممکن است در صورت بسته شدن غیرمنتظره برنامه از بین بروند، اجرا کنید. پس از راه اندازی مجدد برنامه، کدی را برای بازیابی آن داده ها اضافه کنید.

در این کد لبه، برنامه DessertClicker را از کد لبه قبلی گسترش می دهید. یک تایمر پس‌زمینه اضافه می‌کنید، سپس برنامه را برای استفاده از کتابخانه چرخه حیات Android تبدیل می‌کنید.

در آزمایشگاه کد قبلی، شما یاد گرفتید که چگونه می‌توانید چرخه‌های عمر فعالیت و قطعه‌سازی را با نادیده گرفتن تماس‌های مختلف چرخه حیات مشاهده کنید، و زمانی که سیستم آن تماس‌ها را فراخوانی می‌کند، ثبت نام کنید. در این کار، نمونه پیچیده تری از مدیریت وظایف چرخه حیات در برنامه DessertClicker را بررسی می کنید. شما از یک تایمر استفاده می کنید که هر ثانیه یک بیانیه گزارش را با شمارش تعداد ثانیه هایی که اجرا کرده است چاپ می کند.

مرحله 1: DessertTimer را تنظیم کنید

  1. برنامه DessertClicker را از آخرین کد لبه باز کنید. (اگر برنامه را ندارید می توانید DessertClickerLogs را از اینجا دانلود کنید .)
  2. در نمای پروژه ، j ava > com.example.android.dessertclicker را باز کنید و DessertTimer.kt را باز کنید. توجه داشته باشید که در حال حاضر همه کدها در نظر گرفته شده اند، بنابراین به عنوان بخشی از برنامه اجرا نمی شود.
  3. تمام کدهای موجود در پنجره ویرایشگر را انتخاب کنید. Code > Comment with Line Comment را انتخاب کنید یا Control+/ ( Command+/ در Mac) را فشار دهید. این دستور تمام کدهای موجود در فایل را از کامنت خارج می کند. (ممکن است Android Studio خطاهای مرجع حل نشده را تا زمانی که برنامه را دوباره بسازید نشان دهد.)
  4. توجه داشته باشید که کلاس DessertTimer شامل startTimer startTimer() و stopTimer() است که تایمر را شروع و متوقف می کند. هنگامی که startTimer() در حال اجرا است، تایمر یک پیام گزارش را در هر ثانیه با تعداد کل ثانیه هایی که زمان اجرا شده است چاپ می کند. stopTimer() به نوبه خود تایمر و عبارات گزارش را متوقف می کند.
  1. MainActivity.kt باز کنید. در بالای کلاس، درست زیر متغیر dessertsSold ، یک متغیر برای تایمر اضافه کنید:
private lateinit var dessertTimer : DessertTimer;
  1. به پایین onCreate() بروید و یک شی DessertTimer جدید درست بعد از فراخوانی setOnClickListener() ایجاد کنید:
dessertTimer = DessertTimer()


اکنون که یک شی تایمر دسر دارید، در نظر بگیرید که از کجا باید شروع کنید و تایمر را متوقف کنید تا فقط زمانی که فعالیت روی صفحه است اجرا شود. در مراحل بعدی به چند گزینه نگاه می کنید.

مرحله 2: تایمر را شروع و متوقف کنید

متد onStart() درست قبل از قابل مشاهده شدن اکتیویتی فراخوانی می شود. متد onStop() پس از اینکه فعالیت قابل مشاهده نیست فراخوانی می شود. به نظر می رسد این تماس ها کاندیدهای خوبی برای شروع و توقف تایمر هستند.

  1. در کلاس MainActivity ، تایمر را در پاسخ به تماس onStart() شروع کنید:
override fun onStart() {
   super.onStart()
   dessertTimer.startTimer()

   Timber.i("onStart called")
}
  1. تایمر را در onStop() متوقف کنید:
override fun onStop() {
   super.onStop()
   dessertTimer.stopTimer()

   Timber.i("onStop Called")
}
  1. برنامه را کامپایل و اجرا کنید. در اندروید استودیو، روی صفحه Logcat کلیک کنید. در کادر جستجوی Logcat، dessertclicker را وارد کنید، که با هر دو کلاس MainActivity و DessertTimer می شود. توجه داشته باشید که پس از شروع برنامه، تایمر نیز بلافاصله شروع به کار می کند.
  2. روی دکمه برگشت کلیک کنید و متوجه شوید که تایمر دوباره متوقف می شود. تایمر متوقف می شود زیرا هم فعالیت و هم تایمری که کنترل می کند از بین رفته اند.
  3. برای بازگشت به برنامه از صفحه نمایش اخیر استفاده کنید. در Logcat توجه کنید که تایمر از 0 دوباره راه اندازی می شود.
  4. روی دکمه اشتراک گذاری کلیک کنید. در Logcat توجه کنید که تایمر هنوز در حال اجرا است.

  5. روی دکمه Home کلیک کنید. در Logcat توجه کنید که تایمر متوقف می شود.
  6. برای بازگشت به برنامه از صفحه نمایش اخیر استفاده کنید. در Logcat توجه کنید که تایمر دوباره از جایی که متوقف شده است شروع به کار می کند.
  7. در MainActivity ، در متد onStop( onStop() ، فراخوانی stopTimer() را نظر دهید. نظر دادن stopTimer() را نشان می‌دهد که شما عملیاتی را در onStart() شروع می‌کنید، اما فراموش می‌کنید که دوباره آن را در onStop() () متوقف کنید.
  8. برنامه را کامپایل و اجرا کنید و بعد از شروع تایمر روی دکمه Home کلیک کنید. حتی اگر برنامه در پس‌زمینه است، تایمر در حال اجرا است و به طور مداوم از منابع سیستم استفاده می‌کند. ادامه کار تایمر برای برنامه شما نشت حافظه است و احتمالاً رفتاری که می خواهید نیست.

    الگوی کلی این است که وقتی چیزی را در یک تماس برگشتی راه‌اندازی یا شروع می‌کنید، آن مورد را در پاسخ تماس مربوطه متوقف یا حذف می‌کنید. به این ترتیب، از اجرای هر چیزی در زمانی که دیگر مورد نیاز نیست، جلوگیری می کنید.
  1. خطی را در onStop() که در آن تایمر را متوقف می کنید، از نظر خارج کنید.
  2. فراخوانی startTimer( startTimer() را از onStart() به onCreate() برش داده و جایگذاری کنید. این تغییر حالتی را نشان می‌دهد که شما هم منبعی را در onCreate() مقداردهی اولیه می‌کنید و هم شروع می‌کنید، نه اینکه از onCreate() برای مقداردهی اولیه و onStart() برای شروع آن استفاده کنید.
  3. برنامه را کامپایل و اجرا کنید. توجه داشته باشید که تایمر همانطور که انتظار دارید شروع به کار می کند.
  4. برای توقف برنامه روی صفحه اصلی کلیک کنید. همانطور که انتظار دارید تایمر کار نمی کند.
  5. برای بازگشت به برنامه از صفحه نمایش اخیر استفاده کنید. توجه داشته باشید که در این حالت تایمر دوباره شروع نمی‌شود ، زیرا onCreate() تنها زمانی فراخوانی می‌شود که برنامه شروع به کار کند—زمانی که برنامه به پیش‌زمینه بازگردد فراخوانی نمی‌شود.

نکات کلیدی که باید به خاطر بسپارید:

  • وقتی منبعی را در یک تماس برگشتی در چرخه حیات تنظیم می‌کنید، منبع را نیز از بین ببرید.
  • راه اندازی و پاک کردن را با روش های مربوطه انجام دهید.
  • اگر چیزی را در onStart() تنظیم کردید، آن را متوقف یا دوباره در onStop() پاره کنید.

در برنامه DessertClicker، به راحتی می توان فهمید که اگر تایمر را در onStart() شروع کرده باشید، باید تایمر را در onStop() () متوقف کنید. فقط یک تایمر وجود دارد، بنابراین به خاطر سپردن توقف تایمر کار سختی نیست.

در یک برنامه پیچیده‌تر اندروید، ممکن است موارد زیادی را در onStart() یا onCreate() تنظیم کنید، سپس همه آنها را در onStop() یا onDestroy() خراب کنید. برای مثال، ممکن است انیمیشن‌ها، موسیقی‌ها، حسگرها یا تایمرهایی داشته باشید که هم باید آن‌ها را راه‌اندازی و از بین ببرید و هم شروع و متوقف کنید. اگر یکی را فراموش کنید، منجر به اشکالات و سردرد می شود.

کتابخانه چرخه حیات ، که بخشی از Android Jetpack است، این کار را ساده می کند. این کتابخانه به ویژه در مواردی مفید است که شما مجبور هستید بسیاری از قطعات متحرک را ردیابی کنید، که برخی از آنها در حالت‌های مختلف چرخه زندگی هستند. کتابخانه نحوه کار چرخه های حیات را بررسی می کند: معمولاً فعالیت یا قطعه به یک مؤلفه (مانند DessertTimer ) می گوید که وقتی یک بازگشت به تماس چرخه حیات رخ می دهد چه کاری انجام دهد. اما وقتی از کتابخانه چرخه حیات استفاده می‌کنید، خود مؤلفه تغییرات چرخه حیات را بررسی می‌کند، سپس وقتی این تغییرات لازم است، کارهای لازم را انجام می‌دهد.

سه بخش اصلی از کتابخانه چرخه حیات وجود دارد:

  • صاحبان چرخه حیات، که اجزایی هستند که یک چرخه حیات دارند (و در نتیجه "مالک" هستند. Activity و Fragment صاحبان چرخه حیات هستند. صاحبان LifecycleOwner رابط LifecycleOwner را پیاده سازی می کنند.
  • کلاس Lifecycle ، که وضعیت واقعی یک مالک چرخه حیات را نگه می‌دارد و زمانی که تغییرات چرخه حیات اتفاق می‌افتد، رویدادهایی را آغاز می‌کند.
  • ناظران چرخه حیات، که وضعیت چرخه حیات را مشاهده می کنند و زمانی که چرخه حیات تغییر می کند وظایف را انجام می دهند. ناظران چرخه حیات رابط LifecycleObserver را پیاده سازی می کنند.

در این کار، برنامه DessertClicker را برای استفاده از کتابخانه چرخه حیات اندروید تبدیل می‌کنید و یاد می‌گیرید که چگونه کتابخانه کار با فعالیت Android و چرخه عمر قطعه را آسان‌تر می‌کند.

مرحله 1: DessertTimer را به LifecycleObserver تبدیل کنید

بخش کلیدی کتابخانه چرخه حیات مفهوم مشاهده چرخه حیات است . مشاهده کلاس‌ها (مانند DessertTimer ) را قادر می‌سازد تا درباره فعالیت یا چرخه حیات قطعه بدانند و در پاسخ به تغییرات آن حالت‌های چرخه حیات، خود را شروع و متوقف کنند. با یک ناظر چرخه حیات، می توانید مسئولیت شروع و توقف اشیا را از روش های فعالیت و قطعه برداری حذف کنید.

  1. کلاس DesertTimer.kt را باز کنید.
  2. امضای کلاس کلاس DessertTimer را به شکل زیر تغییر دهید:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {

این تعریف کلاس جدید دو کار را انجام می دهد:

  • سازنده یک شی Lifecycle را می گیرد، که چرخه حیاتی است که تایمر مشاهده می کند.
  • تعریف کلاس رابط LifecycleObserver را پیاده سازی می کند.
  1. در زیر متغیر runnable ، یک بلوک init به تعریف کلاس اضافه کنید. در بلوک init ، از addObserver() برای اتصال شی چرخه حیات ارسال شده از مالک (activity) به این کلاس (observer) استفاده کنید.
 init {
   lifecycle.addObserver(this)
}
  1. با حاشیه‌نویسی @OnLifecycleEvent روی startTimer startTimer() @OnLifecycleEvent annotation کنید و از رویداد چرخه حیات ON_START استفاده کنید. تمام رویدادهای چرخه حیات که ناظر چرخه حیات شما می تواند مشاهده کند در کلاس Lifecycle.Event هستند.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {
  1. همین کار را با stopTimer() با استفاده از رویداد ON_STOP انجام دهید:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()

مرحله 2: MainActivity را اصلاح کنید

کلاس MainActivity شما در حال حاضر یک مالک چرخه حیات از طریق وراثت است، زیرا سوپرکلاس FragmentActivity LifecycleOwner را پیاده سازی می کند. بنابراین، لازم نیست کاری انجام دهید تا چرخه حیات فعالیت خود را آگاه کنید. تنها کاری که باید انجام دهید این است که شی چرخه حیات فعالیت را به سازنده DessertTimer کنید.

  1. MainActivity باز کنید. در onCreate() مقدار دهی اولیه DessertTimer را تغییر دهید تا این.lifecycle را در بر this.lifecycle :
dessertTimer = DessertTimer(this.lifecycle)

ویژگی lifecycle اکتیویتی دارای شیء Lifecycle حیاتی است که این فعالیت مالک آن است.

  1. تماس با startTimer( startTimer() را در onCreate() و تماس stopTimer() را در onStop( onStop() حذف کنید. دیگر نیازی نیست به DessertTimer بگویید که در فعالیت چه کاری انجام دهد، زیرا DessertTimer اکنون خود چرخه حیات را مشاهده می کند و هنگامی که وضعیت چرخه حیات تغییر می کند به طور خودکار مطلع می شود. تنها کاری که اکنون در این تماس‌های پاسخگو انجام می‌دهید، ثبت یک پیام است.
  2. برنامه را کامپایل و اجرا کنید و Logcat را باز کنید. توجه داشته باشید که طبق انتظار، تایمر شروع به کار کرده است.
  3. برای قرار دادن برنامه در پس زمینه، روی دکمه خانه کلیک کنید. توجه داشته باشید که طبق انتظار، تایمر متوقف شده است.

اگر اندروید آن برنامه را در حالی که در پس‌زمینه است خاموش کند، چه اتفاقی برای برنامه و داده‌های آن می‌افتد؟ درک این مورد لبه حیله‌گر مهم است.

وقتی برنامه شما به پس زمینه می رود، از بین نمی رود، فقط متوقف می شود و منتظر می ماند تا کاربر به آن بازگردد. اما یکی از نگرانی‌های اصلی سیستم‌عامل اندروید این است که فعالیت‌هایی را که در پیش‌زمینه قرار دارند، به خوبی اجرا کنند. به عنوان مثال، اگر کاربر شما از یک برنامه GPS برای کمک به گرفتن اتوبوس استفاده می کند، مهم است که آن برنامه GPS را به سرعت رندر کنید و به نشان دادن مسیرها ادامه دهید. اهمیت کمتری دارد که برنامه DessertClicker را که ممکن است کاربر برای چند روز به آن نگاه نکرده باشد، در پس‌زمینه کار کند.

اندروید برنامه های پس زمینه را تنظیم می کند تا برنامه پیش زمینه بتواند بدون مشکل اجرا شود. برای مثال، اندروید میزان پردازشی را که برنامه‌های در حال اجرا در پس‌زمینه می‌توانند انجام دهند، محدود می‌کند.

گاهی اوقات اندروید حتی کل فرآیند برنامه را که شامل تمام فعالیت های مرتبط با برنامه می شود، خاموش می کند. آندروید این نوع خاموش شدن را زمانی انجام می دهد که سیستم تحت فشار است و در خطر از بین رفتن بصری است، بنابراین هیچ تماس یا کد اضافی در این مرحله اجرا نمی شود. فرآیند برنامه شما به سادگی، بی‌صدا و در پس‌زمینه خاموش می‌شود. اما برای کاربر، به نظر نمی رسد که برنامه بسته شده باشد. هنگامی که کاربر به برنامه ای که سیستم عامل اندروید آن را خاموش کرده است برمی گردد، اندروید آن برنامه را مجددا راه اندازی می کند.

در این کار، خاموش شدن فرآیند اندروید را شبیه‌سازی می‌کنید و بررسی می‌کنید که با راه‌اندازی مجدد برنامه شما چه اتفاقی می‌افتد.

مرحله 1: از adb برای شبیه سازی خاموش شدن فرآیند استفاده کنید

Android Debug Bridge ( adb ) یک ابزار خط فرمان است که به شما امکان می دهد دستورالعمل ها را به شبیه سازها و دستگاه های متصل به رایانه خود ارسال کنید. در این مرحله، از adb برای بستن فرآیند برنامه خود استفاده می کنید و ببینید وقتی اندروید برنامه شما را خاموش می کند چه اتفاقی می افتد.

  1. برنامه خود را کامپایل و اجرا کنید. چند بار روی کیک کوچک کلیک کنید.
  2. دکمه Home را فشار دهید تا برنامه خود را در پس زمینه قرار دهید. اکنون برنامه شما متوقف شده است و اگر Android به منابعی که برنامه استفاده می کند نیاز داشته باشد، برنامه بسته می شود.
  3. در Android Studio، روی تب Terminal کلیک کنید تا ترمینال خط فرمان باز شود.
  4. adb را تایپ کرده و Return را فشار دهید.

    اگر خروجی‌های زیادی مشاهده کردید که با Android Debug Bridge version X.XX.X و با tags to be used by logcat (see logcat —h elp مراجعه کنید)، همه چیز خوب است. اگر به جای آن adb: command not found ، مطمئن شوید که دستور adb در مسیر اجرای شما موجود است. برای دستورالعمل‌ها، به «افزودن adb به مسیر اجرای خود» در بخش Utilities مراجعه کنید.
  5. این نظر را کپی کرده و در خط فرمان قرار دهید و Return را فشار دهید:
adb shell am kill com.example.android.dessertclicker

این دستور به هر دستگاه متصل یا شبیه‌ساز می‌گوید که فرآیند را با نام بسته dessertclicker کند، اما فقط در صورتی که برنامه در پس‌زمینه باشد. از آنجایی که برنامه شما در پس‌زمینه بود، چیزی روی صفحه دستگاه یا شبیه‌ساز نشان نمی‌دهد که نشان دهد فرآیند شما متوقف شده است. در اندروید استودیو، روی تب Run کلیک کنید تا پیامی را مشاهده کنید که می‌گوید «برنامه پایان یافت». روی زبانه Logcat کلیک کنید تا ببینید که پاسخ به تماس onDestroy() هرگز اجرا نشد—فعالیت شما به سادگی پایان یافت.

  1. برای بازگشت به برنامه از صفحه نمایش اخیر استفاده کنید. برنامه شما در موارد اخیر ظاهر می شود، چه در پس زمینه قرار گرفته باشد و چه به طور کلی متوقف شده باشد. هنگامی که از صفحه اخیر برای بازگشت به برنامه استفاده می کنید، فعالیت دوباره شروع می شود. این اکتیویتی از طریق کل مجموعه فراخوانی چرخه حیات راه اندازی، از جمله onCreate() انجام می شود.
  2. توجه داشته باشید که وقتی برنامه دوباره راه اندازی شد، "امتیاز" شما (هم تعداد دسرهای فروخته شده و هم کل دلار) را به مقادیر پیش فرض (0) بازنشانی می کند. اگر اندروید برنامه شما را خاموش کرد، چرا وضعیت شما را ذخیره نکرد؟

    هنگامی که سیستم عامل برنامه شما را برای شما راه اندازی مجدد می کند، اندروید تمام تلاش خود را می کند تا برنامه شما را به حالت قبلی بازنشانی کند. Android وضعیت برخی از نماهای شما را می گیرد و هر زمان که از فعالیت دور می شوید آن را در یک بسته ذخیره می کند. برخی از نمونه‌هایی از داده‌هایی که به‌طور خودکار ذخیره می‌شوند عبارتند از متن موجود در EditText (تا زمانی که یک شناسه در طرح‌بندی تنظیم شده باشد)، و پشته پشتی فعالیت شما.

    با این حال، گاهی اوقات سیستم عامل اندروید از تمام داده های شما اطلاعی ندارد. به عنوان مثال، اگر یک متغیر سفارشی مانند revenue در برنامه DessertClicker دارید، سیستم عامل Android از این داده ها یا اهمیت آن برای فعالیت شما اطلاعی ندارد. شما باید این داده ها را خودتان به بسته اضافه کنید.

مرحله 2: از onSaveInstanceState() برای ذخیره داده های بسته استفاده کنید

متد onSaveInstanceState() است که برای ذخیره هر داده ای که ممکن است در صورتی که سیستم عامل اندروید برنامه شما را از بین ببرد به آن نیاز داشته باشید، استفاده می کنید. در نمودار برگشت تماس چرخه حیات، onSaveInstanceState() پس از توقف فعالیت فراخوانی می شود. هر بار که برنامه شما به پس‌زمینه می‌رود، فراخوانی می‌شود.

فراخوانی onSaveInstanceState() را به عنوان یک اقدام ایمنی در نظر بگیرید. این فرصت را به شما می دهد تا با خروج فعالیت شما از پیش زمینه، مقدار کمی از اطلاعات را در یک بسته ذخیره کنید. سیستم اکنون این داده ها را ذخیره می کند زیرا اگر منتظر بماند تا برنامه شما را خاموش کند، ممکن است سیستم عامل تحت فشار منابع باشد. ذخیره داده‌ها هر بار تضمین می‌کند که داده‌های به‌روزرسانی در بسته برای بازیابی، در صورت نیاز، در دسترس هستند.

  1. در MainActivity ، فراخوانی onSaveInstanceState() را لغو کنید و یک عبارت Timber log اضافه کنید.
override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Timber.i("onSaveInstanceState Called")
}
  1. برنامه را کامپایل و اجرا کنید و روی دکمه Home کلیک کنید تا آن را در پس زمینه قرار دهید. توجه داشته باشید که فراخوانی onSaveInstanceState() onSaveInstanceState درست پس از onPause() onStop() می دهد:
  2. در بالای فایل، درست قبل از تعریف کلاس، این ثابت ها را اضافه کنید:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"

شما از این کلیدها برای ذخیره و بازیابی داده ها از بسته حالت نمونه استفاده خواهید کرد.

  1. به پایین onSaveInstanceState() onSaveInstanceState بروید و به پارامتر outState که از نوع Bundle است توجه کنید.

    بسته نرم افزاری مجموعه ای از جفت های کلید-مقدار است که در آن کلیدها همیشه رشته ای هستند. می توانید مقادیر اولیه مانند مقادیر int و boolean را در بسته قرار دهید.
    از آنجایی که سیستم این بسته را در RAM نگه می‌دارد، بهترین روش کوچک نگه داشتن داده‌های موجود در بسته است. اندازه این بسته نیز محدود است، اگرچه اندازه آن از دستگاهی به دستگاه دیگر متفاوت است. به طور کلی باید بسیار کمتر از 100k ذخیره کنید، در غیر این صورت با خطای TransactionTooLargeException برنامه خود را از کار می‌اندازید.
  2. در onSaveInstanceState() مقدار revenue (یک عدد صحیح) را با putInt() در بسته قرار دهید:
outState.putInt(KEY_REVENUE, revenue)

putInt() و متدهای مشابه از کلاس Bundle مانند putFloat() و putString() دو آرگومان می گیرد: یک رشته برای کلید (ثابت KEY_REVENUE ) و مقدار واقعی برای ذخیره.

  1. همین روند را با تعداد دسرهای فروخته شده و وضعیت تایمر تکرار کنید:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)

مرحله 3: از onCreate() برای بازیابی داده های بسته استفاده کنید

  1. به روی onCreate() بروید و امضای متد را بررسی کنید:
override fun onCreate(savedInstanceState: Bundle) {

توجه داشته باشید که onCreate() با هر بار فراخوانی یک Bundle دریافت می کند. هنگامی که فعالیت شما به دلیل خاموش شدن فرآیند راه اندازی مجدد می شود، بسته ای که ذخیره کرده اید به onCreate() منتقل می شود. اگر فعالیت شما تازه شروع شده بود، این بسته در onCreate() null است. بنابراین اگر بسته نرم افزاری null نباشد، می دانید که فعالیت را از نقطه ای که قبلاً شناخته شده بود، "دوباره" ایجاد می کنید.

  1. این کد را پس از تنظیم DessertTimer به onCreate() اضافه کنید:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

تست null تعیین می‌کند که آیا داده‌ای در بسته وجود دارد یا اینکه بسته نرم‌افزار null است یا خیر، که به نوبه خود به شما می‌گوید که آیا برنامه تازه راه‌اندازی شده است یا پس از خاموش شدن دوباره ایجاد شده است. این تست یک الگوی رایج برای بازیابی داده ها از بسته نرم افزاری است.

توجه داشته باشید که کلیدی که در اینجا استفاده کردید ( KEY_REVENUE ) همان کلیدی است که برای putInt() استفاده کردید. برای اطمینان از اینکه هر بار از یک کلید استفاده می‌کنید، بهترین روش این است که آن کلیدها را به عنوان ثابت تعریف کنید. همانطور که از putInt() برای قرار دادن داده ها در بسته استفاده کردید، از getInt() برای خارج کردن داده ها از بسته استفاده می کنید. getInt() دو آرگومان می گیرد:

  • رشته ای که به عنوان کلید عمل می کند، برای مثال "key_revenue" برای ارزش درآمد.
  • یک مقدار پیش فرض در صورتی که هیچ مقداری برای آن کلید در بسته وجود نداشته باشد.

سپس عدد صحیحی که از بسته دریافت می‌کنید به متغیر revenue اختصاص داده می‌شود و UI از آن مقدار استفاده می‌کند.

  1. برای بازیابی تعداد دسرهای فروخته شده و مقدار تایمر، متدهای getInt() را اضافه کنید:
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount =
       savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}
  1. برنامه را کامپایل و اجرا کنید. کاپ کیک را حداقل پنج بار فشار دهید تا تبدیل به دونات شود. برای قرار دادن برنامه در پس زمینه، روی صفحه اصلی کلیک کنید.
  2. در تب Android Studio Terminal ، adb را اجرا کنید تا فرآیند برنامه خاموش شود.
adb shell am kill com.example.android.dessertclicker
  1. برای بازگشت به برنامه از صفحه نمایش اخیر استفاده کنید. توجه داشته باشید که این بار برنامه با درآمد صحیح و دسرهای فروخته شده از بسته نرم افزاری، برمی گردد. اما توجه داشته باشید که دسر به یک کیک کوچک بازگشته است. یک کار دیگر باقی مانده است که باید انجام دهید تا اطمینان حاصل شود که برنامه از حالت خاموش شدن دقیقاً به همان شکلی که رها شده بود باز می گردد.
  2. در MainActivity ، showCurrentDessert() را بررسی کنید. توجه داشته باشید که این روش بر اساس تعداد فعلی دسرهای فروخته شده و لیست دسرها در متغیر allDesserts تعیین می کند که کدام تصویر دسر باید در فعالیت نمایش داده شود.
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

این روش برای انتخاب تصویر مناسب به تعداد دسرهای فروخته شده متکی است. بنابراین، برای ذخیره ارجاع به تصویر در بسته در onSaveInstanceState() نیازی به انجام کاری ندارید. در آن بسته، شما در حال حاضر تعداد دسرهای فروخته شده را ذخیره می کنید.

  1. در onCreate() ، در بلوکی که وضعیت را از بسته بازیابی می کند، showCurrentDessert() را فراخوانی کنید:
 if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   dessertTimer.secondsCount = 
      savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
   showCurrentDessert()                   
}
  1. برنامه را کامپایل و اجرا کنید و آن را در پس زمینه قرار دهید. از adb برای خاموش کردن فرآیند استفاده کنید. برای بازگشت به برنامه از صفحه نمایش اخیر استفاده کنید. اکنون توجه داشته باشید که هم مقادیر دسرهای گفته شده، درآمد کل و تصویر دسر به درستی بازیابی شده اند.

آخرین مورد خاص در مدیریت چرخه عمر فعالیت و قطعه وجود دارد که درک آن مهم است: اینکه چگونه تغییرات پیکربندی بر چرخه حیات فعالیت ها و قطعات شما تأثیر می گذارد.

تغییر پیکربندی زمانی اتفاق می‌افتد که وضعیت دستگاه به‌قدری تغییر کند که ساده‌ترین راه برای سیستم برای حل این تغییر، خاموش کردن کامل و بازسازی فعالیت باشد. به‌عنوان مثال، اگر کاربر زبان دستگاه را تغییر دهد، ممکن است کل طرح‌بندی تغییر کند تا جهت‌های متنی مختلف را در خود جای دهد. اگر کاربر دستگاه را به یک داک وصل کند یا یک صفحه کلید فیزیکی اضافه کند، طرح‌بندی برنامه ممکن است نیاز به استفاده از اندازه یا طرح‌بندی متفاوتی داشته باشد. و اگر جهت دستگاه تغییر کند - اگر دستگاه از حالت عمودی به منظره یا به صورت دیگر چرخانده شود - ممکن است نیاز باشد که چیدمان تغییر کند تا با جهت جدید مطابقت داشته باشد.

مرحله 1: چرخش دستگاه و بازگشت به تماس چرخه عمر را کاوش کنید

  1. برنامه خود را کامپایل و اجرا کنید و Logcat را باز کنید.
  2. دستگاه یا شبیه ساز را به حالت افقی بچرخانید. می توانید شبیه ساز را با دکمه های چرخش به چپ یا راست بچرخانید یا با کلیدهای Control و پیکان (کلیدهای Command و پیکان در مک).
  3. خروجی را در Logcat بررسی کنید. خروجی را در MainActivity فیلتر کنید.
    توجه داشته باشید که وقتی دستگاه یا شبیه‌ساز صفحه را می‌چرخاند، سیستم تمام چرخه حیات را فراخوانی می‌کند تا فعالیت را خاموش کند. سپس، با ایجاد مجدد اکتیویتی، سیستم برای شروع فعالیت، تمامی تماس های چرخه حیات را فراخوانی می کند.
  4. در MainActivity ، کل متد onSaveInstanceState() را نظر دهید.
  5. برنامه خود را کامپایل و دوباره اجرا کنید. چند بار روی کیک کوچک کلیک کنید و دستگاه یا شبیه ساز را بچرخانید. این بار، هنگامی که دستگاه چرخانده می شود و فعالیت خاموش می شود و دوباره ایجاد می شود، فعالیت با مقادیر پیش فرض شروع می شود.

    هنگامی که یک تغییر پیکربندی رخ می دهد، Android از همان دسته حالت نمونه ای استفاده می کند که در کار قبلی با آن آشنا شده اید تا وضعیت برنامه را ذخیره و بازیابی کند. مانند خاموش کردن فرآیند، از onSaveInstanceState() برای قرار دادن داده های برنامه خود در بسته استفاده کنید. سپس داده‌ها را در onCreate() بازیابی کنید تا در صورت چرخاندن دستگاه، داده‌های وضعیت فعالیت از دست نرود.
  6. در MainActivity ، روش onSaveInstanceState() را حذف کنید، برنامه را اجرا کنید، روی کیک کوچک کلیک کنید و برنامه یا دستگاه را بچرخانید. توجه داشته باشید که این بار اطلاعات دسر در طول چرخش فعالیت حفظ می شود.

پروژه اندروید استودیو: DessertClickerFinal

نکات چرخه زندگی

  • اگر چیزی را در یک بازگشت به تماس چرخه حیات راه‌اندازی یا شروع کردید، آن مورد را در پاسخ تماس مربوطه متوقف یا حذف کنید. با متوقف کردن چیز، مطمئن می‌شوید که در زمانی که دیگر به آن نیاز نیست، به کار خود ادامه نمی‌دهد. برای مثال، اگر یک تایمر را در onStart() تنظیم کنید، باید تایمر را در onStop onStop() ) متوقف یا متوقف کنید.
  • از onCreate() فقط برای مقداردهی اولیه بخشی از برنامه خود استفاده کنید که یک بار اجرا می شوند، زمانی که برنامه برای اولین بار شروع می شود. از onStart() برای شروع بخش هایی از برنامه خود استفاده کنید که هم هنگام شروع برنامه اجرا می شوند و هم هر بار که برنامه به پیش زمینه باز می گردد.

کتابخانه چرخه حیات

  • از کتابخانه چرخه حیات Android برای تغییر کنترل چرخه عمر از فعالیت یا قطعه به مؤلفه واقعی که باید از چرخه حیات آگاه باشد استفاده کنید.
  • صاحبان چرخه زندگی اجزایی هستند که دارای چرخه حیات (و در نتیجه "خود") هستند، از جمله Activity و Fragment . صاحبان LifecycleOwner رابط LifecycleOwner را پیاده سازی می کنند.
  • ناظران چرخه حیات به وضعیت چرخه حیات فعلی توجه می کنند و زمانی که چرخه حیات تغییر می کند وظایف را انجام می دهند. ناظران چرخه حیات رابط LifecycleObserver را پیاده سازی می کنند.
  • اشیاء Lifecycle شامل حالات چرخه حیات واقعی هستند و هنگامی که چرخه حیات تغییر می کند، رویدادها را آغاز می کنند.

برای ایجاد یک کلاس از چرخه حیات:

  • رابط LifecycleObserver را در کلاس هایی که باید از چرخه حیات آگاه باشند، پیاده سازی کنید.
  • یک کلاس ناظر چرخه حیات را با شی چرخه حیات از اکتیویتی یا قطعه راه‌اندازی کنید.
  • در کلاس مشاهده‌گر چرخه حیات، روش‌های آگاه از چرخه حیات را با تغییر حالت چرخه حیات که به آن علاقه‌مند هستند، حاشیه‌نویسی کنید.

    به عنوان مثال، حاشیه نویسی @OnLifecycleEvent(Lifecycle.Event.ON_START) نشان می دهد که این روش در حال تماشای رویداد چرخه حیات onStart است.

خاموش شدن فرآیند و ذخیره وضعیت فعالیت

  • اندروید برنامه های در حال اجرا در پس زمینه را تنظیم می کند تا برنامه پیش زمینه بتواند بدون مشکل اجرا شود. این مقررات شامل محدود کردن میزان پردازشی است که برنامه‌های پس‌زمینه می‌توانند انجام دهند و گاهی اوقات حتی کل فرآیند برنامه شما را خاموش می‌کند.
  • کاربر نمی تواند تشخیص دهد که آیا سیستم یک برنامه را در پس زمینه خاموش کرده است یا خیر. برنامه همچنان در صفحه نمایش اخیر ظاهر می شود و باید در همان حالتی که کاربر آن را ترک کرده است راه اندازی مجدد شود.
  • Android Debug Bridge ( adb ) یک ابزار خط فرمان است که به شما امکان می دهد دستورالعمل ها را به شبیه سازها و دستگاه های متصل به رایانه خود ارسال کنید. می توانید از adb برای شبیه سازی خاموش شدن فرآیند در برنامه خود استفاده کنید.
  • وقتی اندروید فرآیند برنامه شما را خاموش می کند، متد چرخه حیات onDestroy() فراخوانی نمی شود. برنامه فقط متوقف می شود.

حفظ فعالیت و وضعیت قطعه

  • وقتی برنامه شما به پس‌زمینه می‌رود، درست پس از فراخوانی onStop() ، داده‌های برنامه در یک بسته ذخیره می‌شود. برخی از داده های برنامه، مانند محتوای EditText ، به طور خودکار برای شما ذخیره می شود.
  • بسته نمونه ای از Bundle است که مجموعه ای از کلیدها و مقادیر است. کلیدها همیشه رشته هستند.
  • از پاسخ تماس onSaveInstanceState() برای ذخیره داده های دیگر در بسته ای که می خواهید حفظ کنید، استفاده کنید، حتی اگر برنامه به طور خودکار خاموش شود. برای قرار دادن داده ها در بسته، از متدهای bundle که با put شروع می شوند، مانند putInt() استفاده کنید.
  • می‌توانید داده‌ها را در onRestoreInstanceState() یا معمولاً در onCreate() بازگردانید. onCreate() دارای یک پارامتر savedInstanceState است که بسته را نگه می دارد.
  • اگر متغیر savedInstanceState حاوی null ، فعالیت بدون بسته حالت شروع شده است و هیچ داده وضعیتی برای بازیابی وجود ندارد.
  • برای بازیابی اطلاعات از بسته با یک کلید، از متدهای Bundle که با get شروع می شوند، مانند getInt() استفاده کنید.

تغییرات پیکربندی

  • تغییر پیکربندی زمانی اتفاق می‌افتد که وضعیت دستگاه به‌قدری تغییر کند که ساده‌ترین راه برای سیستم برای حل این تغییر، خاموش کردن و بازسازی فعالیت باشد.
  • رایج ترین مثال تغییر پیکربندی زمانی است که کاربر دستگاه را از حالت عمودی به حالت افقی یا از حالت افقی به حالت عمودی می چرخاند. هنگامی که زبان دستگاه تغییر می کند یا صفحه کلید سخت افزاری به برق وصل است، تغییر پیکربندی نیز ممکن است رخ دهد.
  • هنگامی که یک تغییر پیکربندی رخ می دهد، Android تمام تماس های خاموش شدن چرخه حیات فعالیت را فراخوانی می کند. سپس آندروید فعالیت را از ابتدا مجدداً راه اندازی می کند و همه تماس های راه اندازی چرخه حیات را اجرا می کند.
  • هنگامی که Android یک برنامه را به دلیل تغییر پیکربندی خاموش می کند، فعالیت را با بسته حالتی که برای onCreate() در دسترس است، مجدداً راه اندازی می کند.
  • مانند خاموش کردن فرآیند، وضعیت برنامه خود را در بسته موجود در onSaveInstanceState() ذخیره کنید.

دوره بی ادبی:

مستندات توسعه دهنده اندروید:

دیگر:

این بخش، تکالیف احتمالی را برای دانش‌آموزانی که در این آزمایشگاه کد به عنوان بخشی از دوره‌ای که توسط یک مربی هدایت می‌شود، فهرست می‌کند. این وظیفه مربی است که موارد زیر را انجام دهد:

  • در صورت نیاز تکالیف را تعیین کنید.
  • نحوه ارسال تکالیف را با دانش آموزان در میان بگذارید.
  • تکالیف را نمره دهید.

مربیان می‌توانند از این پیشنهادات به اندازه‌ای که می‌خواهند استفاده کنند، و باید با خیال راحت هر تکلیف دیگری را که فکر می‌کنند مناسب است به آنها اختصاص دهند.

اگر به تنهایی بر روی این کدها کار می کنید، از این تکالیف برای آزمایش دانش خود استفاده کنید.

یک برنامه را تغییر دهید

برنامه DiceRoller را از درس 1 باز کنید. (اگر برنامه را ندارید می توانید از اینجا دانلود کنید .) برنامه را کامپایل و اجرا کنید و توجه داشته باشید که اگر دستگاه را بچرخانید، ارزش فعلی تاس از بین می رود. برای حفظ آن مقدار در بسته، onSaveInstanceState() را پیاده سازی کنید و آن مقدار را در onCreate() بازیابی کنید.

یه این سوالات پاسخ دهید

سوال 1

برنامه شما حاوی یک شبیه سازی فیزیک است که برای نمایش به محاسبات سنگین نیاز دارد. سپس کاربر یک تماس تلفنی دریافت می کند. کدام یک از موارد زیر صحیح است؟

  • در طول مکالمه تلفنی، باید به محاسبه موقعیت اجسام در شبیه سازی فیزیک ادامه دهید.
  • در طول تماس تلفنی، باید محاسبه موقعیت اشیاء در شبیه سازی فیزیک را متوقف کنید.

سوال 2

برای متوقف کردن شبیه‌سازی در زمانی که برنامه روی صفحه نیست، کدام روش چرخه حیات را باید لغو کنید؟

  • onDestroy()
  • onStop()
  • onPause()
  • onSaveInstanceState()

سوال 3

برای ایجاد یک کلاس از طریق کتابخانه چرخه حیات اندروید، کلاس باید کدام رابط را پیاده سازی کند؟

  • Lifecycle
  • LifecycleOwner
  • Lifecycle.Event
  • LifecycleObserver

سوال 4

تحت چه شرایطی onCreate() در اکتیویتی شما یک Bundle با داده در آن دریافت می کند (یعنی Bundle null نیست)؟ ممکن است بیش از یک پاسخ اعمال شود.

  • پس از چرخاندن دستگاه، فعالیت مجدداً راه اندازی می شود.
  • فعالیت از صفر شروع شده است.
  • فعالیت پس از بازگشت از پس زمینه از سر گرفته می شود.
  • دستگاه راه اندازی مجدد می شود.

درس بعدی را شروع کنید: 5.1: ViewModel و ViewModelFactory

برای پیوند به دیگر کدهای این دوره، به صفحه فرود کد لبه‌های کد پایه Android Kotlin Fundamentals مراجعه کنید.