قسمت دوم - لابه‌لای زندگی اکتیویتی - ۱

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

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

اکتیویتی‌ها یکی از بلاک‌های سازنده اپلیکیشن‌های اندرویدی هستند، درواقع هر صفحه در اپلیکیشن‌ها حتما یک اکتیویتی است. اکتیوتی‌ چیزی فراتراز یک کلاس‌ جاوا در فریمورک اندروید نیست که رابط کاربری اپلیکیشن‌های ما را می‌سازد. پس چیز عجیب و غریبی نیست. اگر به android.app.Activity یا android.support.v7.app.AppCompatActivity یا androidx.appcompat.app.AppCompatActivity در اندرویدایکس بروید، این کلاس را می‌بینید. که البته همه بچه‌های کلاس android.app.Activity هستند. (از اینجا به بعد هرموقع از اکتیویتی صحبت کردیم، منظورمان هر کلاسی است که است که یکی از اجدادش android.app.Activity باشد.)

مثل اپلیکیشن‌های جاوا که از متد main() شروع می‌شوند، اغلب اپلیکیشن‌های اندرویدی هم از یک اکتیوتی‌ شروع می‌شوند و حتی می‌توانیم در آن برنامه اکتیویتی‌های دیگری هم داشته باشیم.

کار نمونه‌سازی اکتیوتی‌ها و مدیریت‌ آن‌ها به عهده سیستم‌عامل اندروید است. یعنی خود اندروید آن‌ها را مدیریت می‌کند و ما فقط می‌توانیم درخواست خود را برای استفاده از آن‌ها به وسیله یک Intent به اندروید بفرستیم و اندروید کار موردنظر را برای ما انجام دهد.

اکتیویتی‌ها به درخواست ما ولی توسط خود اندروید ساخته و نگهداری می‌شوند. ما هیچ‌وقت در برنامه یک نمونه جدید از یک اکتیویتی را نمی‌سازیم، بلکه با استفاده از startActivity() به اندروید می‌گوییم تا این کار را برای ما انجام دهد. البته کنترل این فرآیند‌ها به صورت غیرمستقیم توسط خود ما انجام می‌شود. مثلا وقتی به اندروید می‌گوییم که اکتیویتی را start کند، او هم این کار را انجام می‌دهد، یا حتی می‌توانیم بگوییم که این اکتیویتی جدید را در یک تسک جداگانه اجرا کند و ... .

پس این کارها به صورت مستقیم در اختیار ما نیست ولی کنترل کاملی روی آن داریم و هر کاری که بخواهیم را با استفاده از ابزار‌هایی که اندروید دراختیار ما قرار داده انجام می‌دهیم.

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

با این کار اندروید مدیریت تمام پروسه اپ و ساخت اکتیویتی‌ها را به عهده می‌گیرید. اما اینجا برای ما به عنوان برنامه‌نویس یک سری سوال به وجود می‌آید:

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

۲. در این حالت باید چه کار کنم؟

۳. اگر اندروید اکتیویتی‌هایی که به کاربر نمایش داده نمی‌شوند را پاک می‌کند، پس چطور کاربر با زدن دکمه back در گوشی به اکتیویتی قبلی برمی‌گردد؟

۴. و ...

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

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




خب حالا می‌خواهیم این اتفاقات خاصی که ممکن است برای اکتیویتی ما بیفتد را بررسی کنیم.

ابتدا وضعیت‌هایی که اکتیویتی می‌تواند داشته باشد را بررسی می‌کنیم:

وضعیت‌های مختلف اکتیویتی
وضعیت‌های مختلف اکتیویتی


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

وارد‌شدن و خارج‌شدن اکتیویتی از هر وضعیت با کال‌بک‌هایی در اکتیویتی اعلام می‌شود. مثلا زمانیکه اکتیویتی ساخته می‌شود، متد onCreate() صدا زده می‌شود.

وضعیت ۱ (Non-existence):

این وضعیت زمانی است که اکتیویتی وجود ندارد. یعنی هنوز به وجود نیامده، یا اگر وجود داشته است، از بین رفته.

به محض آنکه اکتیویتی در مموری ساخته شود، متد onCreate() صدا زده می‌شود و اکتیوتی وارد وضعیت بعدی، یعنی وضعیت ۲ می‌شود.

اگر هم اکتیویتی بخواهد از بین برود قبل از آن متد onDestroy() صدا زده می‌شود.

وضعیت ۲ (Stopped):

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

زمانی‌که از وضعیت ۲ وارد وضعیت ۳ شویم متد () صدا زده می‌شود.

و زمانی‌که از وضعیت ۳ وارد وضعیت ۲ می‌شویم مت () صدا زده می‌شود.

وضعیت ۳ (Paused):

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

زمانی‌که از وضعیت ۳ وارد وضعیت ۴ می‌شویم، متد () صدا زده می‌شود.

و زمانی‌که از وضعیت ۴ وارد وضعیت ۳ می‌شویم، متد () صدا زده می‌شود.

وضعیت ۴ (Running):

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

نکته: در هر لحظه و در کل سیستم‌عامل فقط یک اکتیویتی می تواند در وضعیت Running قرار داشته باشد.




حالا بیایید از زاویه‌ای دیگر به همین وضعیت‌ها نگاه کنیم، از زاویه اکتیویتی‌ای که درحال توسعه آن هستیم.

چرخه‌حیات اکتیویتی
چرخه‌حیات اکتیویتی


این تصویر همان وضعیت‌های قبلی را از زاویه اکتیویتی نمایش می‌دهد. درواقع کال‌بک‌های هروضعیت و شرایط صدا زده‌ شدن کال‌بک بعدی را نمایش می‌دهد. یعنی وقتی یک اکتیویتی را start می‌کنیم، ابتدا متد onCreate آن صدا زده می‌شود و بعد از آن متد و همینطور الی آخر.

در ادامه درمورد همه‌ی این متد‌هایی که در اکتیویتی صدا زده می‌شوند و اینکه چه زمانی صدا زده می‌شوند و همچنین اینکه در هرکدام از این متد‌ها باید چه کارهایی را انجام دهیم، صحبت می‌کنیم:

onCreate()

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

حالت‌هایی که ممکن است اکتیویتی ساخته شود اینها هستند:

۱. در زمانی‌که برای اولین بار اکتیویتی توسط برنامه‌نویس با startActivity ساخته می‌شود.

۲. زمانی‌که برنامه را minimize کنیم و اندروید برای اجرای برنامه دیگری نیاز به مموری داشته باشد، پروسه اپ را کاملا از مموری پاک می‌کند. در این حالت اگر دوباره به برنامه برگردیم، اندروید خودش پروسه قبلی را از ابتدا ساخته و به همین دلیل اکتیویتی دوباره ساخته می‌شود.

۳. زمانی‌که کاربر گوشی را بچرخاند و از حالت portrait به landscape برود و یا بلعکس. اکتیویتی توسط خود اندروید از بین رفته و اکتیویتی دیگری با تنظیمات جدید ساخته می‌شود. برای همین این متد صدا زده می‌شود.

همانطور که دیدید، اکتیویتی توسط دو نفر ممکن است ساخته شود، ۱. برنامه‌نویس ۲. اندروید

تنها در زمان‌هایی اکتیویتی توسط اندروید ساخته می‌شود که خود اندروید قبلا instance آن را از بین برده باشد.

همانطور که از نام این متد (onCreate) مشخص است باید هر چیزی که می‌خواهیم بسازیم را در این قسمت بسازیم و آبجکت‌ها را مقداردهی کنیم. فایل xml مربوط به ui را به اکتیویتی وصل کنیم و آبجکت‌های آن را مقداردهی کنیم. و هر کار دیگری که در این دسته قرار می‌گیرد. اما به عنوان یک قانون کلی، در اینجا هرکاری که می‌خواهیم در طول کل زندگی اکتیویتی از آن استفاده کنیم را انجام می‌دهیم.

()

وقتی که اکتیویتی وارد وضعیت visible می‌شود،‌ این کال‌بک صدا زده خواهد شد. در این زمان تمام یا قسمتی از اکتیویتی درحال نمایش به کاربر است ولی هنوز کاربر نمی‌تواند با آن تعامل کند. در واقع این مرحله‌ای است که اکتیویتی درحال آماده شدن برای ورود به وضعیت foreground و تعامل با کاربر است.

در این کال‌بک بهتر است کار‌هایی از قبیل نگهداری از ui و یا مقداردهی یک Broadcast Receiver را انجام دهیم. معمولا کارهای خیلی طولانی را نباید در این قسمت انجام دهیم مثل ارتباط با دیتابیس و ... .

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

()

بعد از آنکه اکتیویتی وارد وضعیت Foreground می‌شود این متد صدا زده می‌شود. در این اینجاست که دیگر کاربر می‌تواند با اکتیوتی کار کند. بعد از صدا زده‌شدن متد دیگری صدا زده‌ نمی‌شود تا اتفاقی بیفتد که وضعیت اکتیویتی را تغییر دهد(مثل: کاربر دکمه بک را بزند یا وارد اکتیویتی دیگری شود).

در این متد فقط باید منابعی که کاربر در حین استفاده فقط به آنها نیاز دارد را مقداردهی کنیم، مثل انیمیشن‌ها.

()

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

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

حالت مولتی‌ویندو در اندروید
حالت مولتی‌ویندو در اندروید


حتی اگر یک اکتیویتی دیگر با صفحه transparent لانچ شود، و اکتیویتی قبلی هنوز نمایان باشد، در این حالت باقی می‌ماند.

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

در این متد نباید کارهای طولانی از قبیل ذخیره داده‌ها در دیتابیس و ... را انجام داد. این متد باید خیلی سریع اجرا شود تا اکتیویتی بعد از آن اجرا شود. پس کارهای خیلی طولانی در آن انجام ندهید.

برای انجام کارهای نهایی طولانی و سنگین باید از استفاده کنیم.

()

زمانیکه اکیتویتی دیگر به کاربر نمایش داده نمی‌شود، وارد این وضعیت شده و متد صدا زده خواهد شد.

همچنین در زمانیکه اکتیوتی در حال پایان یافتن (finish) است هم این متد صدازده خواهد شد. زمانیکه در وضعیت Stopped قرار داریم، Instance اکتیویتی در مموری می‌ماند و تمام متغیر‌ها و ... هم باقی خواهند ماند و فقط اکتیوتی به Window Manager متصل نیست و صفحه به کاربر نمایش داده نمی‌شود. همچنین وضعیت همه View های که id دارند نیز توسط اکتیویتی نگه‌داری می‌شود و نیازی نیست تا مقادیر آنها را نگه داریم.

آخرین متد در اکتیویتی که مطمئن هستیم حتما اجرا خواهد شد، است و هرکاری که می خواهیم در آخر اکتیویتی انجام دهیم، اینجاست. کارهایی مثل ذخیره وضعیت یا اطلاعات ورودی توسط کاربر.

تمام کارهای سنگین و آن‌هایی که زمان زیادی برای انجام می‌خواهند را در این متد انجام دهید مثل ارتباط با دیتابیس و ... .

همچنین همه منابعی که در اکتیویتی استفاده کردیم را باید در اینجا آزاد کنیم.

onDestroy()

این متد قبل از آنکه اکتیوتی از مموری پاک‌شود، صدا زده می‌شود. مثلا زمانیکه متد finish را کال می‌کنیم تا اکتیویتی بسته‌شود و یا زمانیکه سیستم‌عامل به خاطر کمبود حافظه activity/Process اپ را از بین می‌برد، بعد از و قبل از آنکه همه‌چیز نابود شود این متد صدا زده خواهد شد.

البته با متد isFinishing در اکتیویتی می‌توانیم تفاوت این دو را متوجه شویم.

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

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

اگر این مطلب براتون مفید بوده، لطفا اون رو به بقیه هم معرفی کنید.

منتظر نظرات و سوالاتتون هستم♥.

منابع

https://developer.android.com/guide/components/activities/intro-activities

https://academy.realm.io/posts/activities-in-the-wild-exploring-the-activity-lifecycle-android/<br/>