همه چیز درباره context در اندروید

نویسنده : سید ایوب کوکبی ۱۸ مرداد ۱۳۹۷

همه چیز درباره context در اندروید

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

مقدمه

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

شروع داستان

آیا تا الان شده این سوال را از خودتان بپرسید که فرق بین ()getContext(), this, getBaseContext و ()getApplicationContext چیست؟ اگر بله این مقاله خیلی خوب شما را از سردرگمی خارج می‌کند.

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

context چیست؟

Context یکی از ضعیف‌ترین طراحی‌ها را در Android API دارد. در واقع Conrtext را می‌توانید یک God Object بدانید.

اپلیکیشن اندروید یا پکیج خروجی آن (با پسوند APK) مجموعه‌ای از کامپوننت‌هاست که در فایلی تحت عنوان مانیفست تعریف می‌شوند که معمولاً مواردی همچون (Activity (UI), Service (Background), BroadcastReceiver (Action), ContentProvider (Data و ریسورس‌ها شامل تصاویر، رشته‌ها و … را در بر می‌گیرد.

توسعه‌دهنده با استفاده از intent-filterها تصمیم به استفاده از این کامپوننت‌ها در برنامه‌اش می‌گیرد (مثلاً ارسال کردن یک ایمیل یا اشتراک تصاویر).

سیستم‌عامل اندروید نیز به گونه‌ای طراحی شده که مبتنی بر کامپوننت‌ها مختلفی همچون WifiManager، Vibrator و packageManager است.

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

کامپوننت‌های خودتان

ما از context برای نمونه‌سازی کامپوننت‌ها با استفاده از Activity, Context Provider، BroadcastReceiver و … استفاده می‌کنیم. همچنین از Context برای دسترسی به ریسورس‌ها و فایل‌های سیستمی استفاده می‌کنیم.

کامپوننت‌های خودتان و کامپوننت‌های سیستمی

Context در حکم یک نقطه‌ی ورود یا entry point در سیستم اندروید است. از کامپوننت‌های سیستمی می‌توانم به WifiManager, Vibrator و packageManager اشاره کنم. مثلاً برای دسترسی به WifiManager می‌توانید از این کد استفاده کنید:

context.getSystemService(Context.WIFI_SERVICE)

به همین روش از context می‌توانید برای دسترسی به فایل‌های سیستمی استفاده نمایید.

کامپوننت‌های خودتان و سایر برنامه‌ها

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

Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);

خلاصه

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

بخوانید  تست کدها در اندروید - بخش اول

تفاوت انواع مختلف Context

روش‌های مختلفی برای دریافت Context وجود دارد. اغلب اوقات وقتی به context نیاز داریم از روش‌های پایین استفاده می‌کنیم:

  • نمونه‌ای از Application به عنوان context
  • Activity
    • نمونه‌ای از اکتیویتی با کلمه‌ی کلیدی this
    • ()getApplicationContext داخل اکتیویتی
    • ()getBaseContext داخل اکتیویتی
  • Fragment: استفاده از ()getContext داخل فرگمنت
  • View: استفاده از ()getContext داخل View
  • BroadCast receiver: استفاده از context دریافتی
  • Service
    • نمونه‌ای از سرویس با کلمه‌ی کلیدی this
    • استفاده از ()getApplicationContext داخل سرویس
  • Context: استفاده از ()getApplicationContext داخل نمونه‌ی ایجاد شده از Context

من context ها را به دو دسته تقسیم می‌کنم: UI Context و Non-UI Context. این کار درک موضوع را آسان‌تر می‌کند.

UI-Context

در واقعیت تنها Context ای که در دسته‌ی UI Context قرار می‌گیرد ContextThemeWrapper است که معنی‌اش این است:Context + Theming

اکتیویتی ContextThemeWrapper را اکستند می‌کند. دقیقاً به همین دلیل وقتی یک فایل xml را inflate می‌کنید، ویوی شما استایل یا تم داده می‌‌شود. اگر با یک Non-UI Context عمل inflate را انجام دهید مسلماً Layout شما تم داده نمی‌شود. امتحان کنید.

وقتی از Activity به عنوان Context استفاده می‌کنید در واقع دارید از یک UI Context استفاده می‌کنید. وقتی داخل فرگمنت از متد getContext استفاده می‌کنید درواقع به صورت غیرمستقیم از اکتیویتی استفاده می‌کنید (البته اگر فرگمنت را از طریق fragmentManager به اکتیویتی متصل کرده باشید).

اما ()view.getContext تضمین نمی‌کند که یک UI Context باشد.

اگر ویو توسط Layout Inflater نمونه‌سازی شود و UI Context به آن پاس داده شود، یک UI Context برگردانده می‌شود ولی اگر UI Context پاس داده نشود، Context دیگری پس داده می‌شود.

  • UI Context
    • Activity: نمونه‌ای از اکتیویتی (this)
    • Fragment: استفاده از متد ()getContext در فرگمنت
    • View: استفاده از متد ()getContext در ویو (اگر ویو توسط یک UI Context ساخته شده باشد)

Non-UI Context

هر چیزی به جزء UI Context در این دسته قرار می‌گیرد. به لحاظ فنی هر چیزی که ContextThemeWrapper نباشد یک Non-UI Context است.

Non-UI Context قادر است تقریباً تمام آن چیزی که UI-Context می‌تواند انجام دهد را انجام دهد. اما همانطور که بالاتر اشاره کردیم تم را از دست خواهیم داد.

  • Non-UI Context
    • نمونه‌ای از Application به عنوان context
    • Activity
      • ()getApplicationContext داخل اکتیویتی
    • BroadCast receiver: استفاده از context دریافتی
    • Service
      • نمونه‌ای از سرویس با کلمه‌ی کلیدی this
      • استفاده از ()getApplicationContext داخل سرویس
    • Context: استفاده از ()getApplicationContext داخل نمونه‌ی ایجاد شده از Context

نکته:  به جزء Application Context سایر انواع Context عمر کوتاهی دارند. منظورمان از Application Context همانی است که کلاس Application می‌گیرید یا با متد ()getAplicationContext به آن دسترسی پیدا می‌کنید.

خلاصه

با تقسیم Context به دو دسته، توانستیم آشنایی بهتری با آن پیدا کنیم. UI Context به Context+Theming گفته می‌شود و به لحاظ فنی هر کلاسی که زیرکلاسی از ContextThemeWrapper باشد در این دسته قرار می‌گیرد. Non-UI Context به سایر انواع Context گفته می‌شود.

هر یک را در کجا استفاده کنیم؟

سوال مطرح می‌شود که چه می‌شود اگر از یک Context در جای نادرستی استفاده کنیم؟ سناریوهای پایین را در نظر بگیرید:

سناریوی اول

فرض کنید در حال Inflate کردن Layout ای هستید و از Non-UI Context استفاده می‌کنید. اشتباه کار کجاست؟ احتمالاً حدس زده‌اید، Layout شما فاقد تم است. این مسئله خیلی بد نیست. قابل تحمل است.

سناریوی دوم

شما UI-Context را به جایی پاس می‌دهید که نیازمند دسترسی به ریسورس‌ها و فایل‌هاست. اینجا مرتکب اشتباهی نشده‌ایم. به خاطر آوررید UI-Context=Context+Theme. بنابراین به راحتی می‌توانید از Context برای اهداف خود بهره ببرید.

بخوانید  قسمت سوم :کلید های کوتاه در اندروید استودیو

سناریوی سوم

شما UI-Context را به جایی ارسال می‌کنید که همانند سناریوی دوم با دسترسی ریسورس‌ها و فایل‌ها سروکار دارد اما عملیاتی که انجام می‌دهد طولانی است و معمولاً در زمینه انجام می‌شود. مثلاً دانلود کردن یک فایل. اینجا مشکل کجاست؟ پاسخ کوتاه: نشت حافظه (Memory Leak).

اگر خوش‌شانس باشید و فایل را به صورت کامل و سریع دانلود کنید. شی به موقع آزاد و همه چیز اوکی است ولی معمولاً کم پیش می‌آید اینقدر خوش‌شانس باشید و اصلا نباید روی شانس، برنامه‌نویسی کنید. این یکی از بزرگترین اشتباهات توسعه‌دهندگان است که مرتکب آن می‌شوند. آن‌ها ارجاعی به UI-Context را به اشیائی با عمر طولانی ارسال می‌کنند که باعث بروز اثرات جانبی می‌شود.

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

نشت حافظه یا کرش

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

خجالت برای زمانی است که اشتباهی را دقیقاً به همان شکل سابق بارها تکرار کنید. اگر هربار به شکل متفاوتی با نشت حافظه برخورد می‌کنید نشانه‌ی رشد شماست و جای نگرانی ندارد. ولی وقتی سناریویی که قبلاً به خاطرش با این مشکل مواجه شده‌اید را دوباره تجربه کنید نشان‌دهنده پسرفت شماست.

خب درست، ولی این‌ها چه ربطی به Context دارد؟

تقریباً هرچیزی در اندروید نیازمند دسترسی به Context است. برنامه‌نویسان مبتدی معمولاً با همان UI-Context کارشان را راه می‌اندازند چرا که دسترسی راحت‌تری به آن دارند. آن‌ها اغلب یک context کوتاه‌عمر (معمولاً Activity Context) را به یک شی طویل‌عمر ارسال می‌کنند ولی قبل از اینکه حافظه به سیستم برگردد با بحران مواجه می‌شوند.

آسان‌ترین روش برای مقابله با این مشکل استفاده از Async Task یا Broadcast Reciever است. اما بحث در این زمینه از حوصله‌ی این مقاله خارج است.

خلاصه

  • آیا نیازمند دسترسی به چیزهای مرتبط با UI هستید؟ اگر اینطور است از UI-Context استفاده کنید. مثلاً Inflate کردن ویوها یا نمایش دیالوگ‌ها دو نمونه‌ای است که به ذهنم می‌رسد؛
  • در غیر این صورت از Non-UI Context استفاده کنید؛
  • مطمئن شوید که یک context کوتاه‌عمر را به یک شی طویل‌عمر پاس ندهید.

ترفندها و نکات

فرق بین ()this, getApplicationcontext و ()getBaseContext چیست؟

این سوالی است که هر برنامه‌نویس اندرویدی یکبار ذر طول عمرش پرسیده است؟ سعی می‌کنم تا حد امکان این موضوع را ساده و روان موشکافی کنم. ابتدا مروری بر مبانی داشته باشیم.

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

همه‌ی این تغییرات باعث می‌شود تا برنامه‌ها مجددا ساخته شوند تا بتوانند ریسورسهای مناسب با آن پیکربندی را تنظیم کنند. مثلاً برای حالت عمودی یا Portrait ممکن است Layout و تصاویری استفاده شود که در حالت افقی یا Landscape متفاوت است، یا وقتی زبان گوشی به چینی، آلمانی و سایر زبان‌ها تغییر می‌دهید شاید بخواهید اپلیکیشن خود را بر همین مبنا تنظیم کنید. مثلاً برای یک نرم‌افزار حسابداری ممکن است با تغییر زبان بخواهید واحد پولی را تغییر دهید یا رشته‌های موجود در برنامه را به آن زبان نشان دهید. این وظیفه Context است که بهترین ریسورس‌ها را تحویل دهد.

بخوانید  برنامه‌نویسی پیشرفته اندروید با زبان کاتلین - بخش سوم (RxJava)

سعی کنید به این سوال جواب دهید:

پیکربندی فعلی گوشی روی حالت portrait است و شما می‌خواهید به ریسورس‌های Landscape دسترسی پیدا کنید. یا user local روی en است ولی بخواهید به ریسورس‌های uk سوئیچ کنید. این کار را چگونه انجام می‌دهید؟

در تصویر پایین تعدادی از متدهای جادویی Context را می‌بینید:

متدهای مختلف Context

تعداد زیادی متد createX وجود دارد ولی ما به createConfigurationContext نیاز داریم. باید اینطور از آن استفاده کنیم:

Configuration configuration = getResources().getConfiguration();
configuration.setLocale(your_custom_locale);
context = createConfigurationContext(configuration);

به این صورت می‌توانید هر نوع Context ای که دوست دارید را به کار برید. هر متدی که روی Context جدید صدا می‌زنید به ریسورس‌های مرتبط با پیکربندی تنظیم شده‌ی آن دسترسی خواهد داشت.

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

ContextThemeWrapper ctw = new ContextThemeWrapper(this, R.style.YOUR_THEME);

خب به سوالمان برگردیم و درباره‌ی Activity Context صحبت کنیم.

تفاوت بین ()this, getApplicationcontext و ()getBaseContext چیست؟

این سه روش، راه‌های دریافت Context در محدوده‌ی Activity است.

this به خودِ اکتیویتی اشاره دارد، UI Context و با عمری کوتاه.

()getApplicationContext اشاره دارد به نمونه‌ی ایجاد شده از اپلیکیشن شما که یک Non-UI Context با عمر طولانی است.

baseContext پایه و اساس و Activity Context است که با الگوی delegate می‌توانید تنظیمش کنید. پیش‌تر دانستید که Context را می‌توانید با هر پیکربندی xyz ای ایجاد کنید. شما می‌توانید تنظیمات پیکربندی xyz را با Base Context ترکیب کنید تا اکتیویتی شما ریسورس‌های مورد نظرتان را برگرداند.

این متدی است که شما می‌توانید استفاده کنید:

@Overide
protected void attachBaseContext (Context base) {
super.attachBaseContext(useYourCustomContext);
}

به محض اتچ شدن BaseContext اکتیویتی شما به این شی فراخوانده می‌شود. اگر به اکتیویتی اتچ نکنید به همان صورت baseContext باقی می‌ماند و شما وقتی getBaseContext را صدا می‌زنید، اکتیویتی را دریافت خواهید کرد.

نتیجه‌گیری

می‌توانم اینطور بگویم که Context زندگی اپلیکیشن اندرویدی شماست. از دید اندروید، Context همان برنامه‌ی شماست. بدون Context تقریباً هیچ کاری نمی‌توانید انجام دهید. بدون آن برنامه‌ی شما تنها یک کد ساده‌ی جاواست.

Context+Java Code =>Android

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

در بخش بعدی دانستیم که Context به دو دسته UI Context و Non-UI Context یا به بیانی دیگر Context کوتاه‌عمر و طویل‌عمر تقسیم می‌شود.

در ادامه متوجه شدید که Context را باید درست و به جا استفاده کرد در غیر این صورت با مشکل نشت حافظه و مسائل مربوط به UI مواجه خواهید شد.

نهایتا توضحی دادم Context مسئول لود کردن بهترین ریسورس‌ها بر اساس پیکربندی ارسالی به آن است. همچنین تفاوت this, applicationContext و baseContext را هم فهمیدیم.

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

امیدوارم اطلاعات کاملی درباره Context ارائه کرده باشم. اگر سوالی داشتید کامنت بگذارید.

سید ایوب کوکبی

نویسنده و مترجم...

2 دیدگاه

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *




    حمید

    چهارشنبه ۱۴ شهریور ۱۳۹۷

    عالی بود ، همیشه این سوال تو ذهنم بود و نمیتونستم جوابش بدم … کلی مرسی

    mahdi

    یکشنبه ۱۳ آبان ۱۳۹۷

    ((Non-UI Context قادر است تقریباً تمام آن چیزی که UI-Context می‌تواند انجام دهد را انجام دهد. اما همانطور که بالاتر اشاره کردیم تم را از دست خواهیم داد.))
    شماامشب دنیا رو برای من روشن کردیددمتون گرم