4.5/5 (16)

سلام خدمت شما همراهان سایت اسمارت دولوپرز . در این مقاله قصد دارم در مورد Service ها در اندروید صحبت کنم و در موردشون توضیح کاملی بدم . تا مفهوم سرویس ها و فرق بین Started Services با Bound Services رو متوجه و کمی هم با نحوه ساخت این دو نوع سرویس و کد نویسی آنها آشنا بشیم .

مفهوم Background Thread و UI Thread و Main Thread :

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

اصلا چرا به Background Thread نیاز داریم ؟

فرض کنید میخوایم که یک کاری رو انجام بدیم که چند ثانیه وقت نیاز داره تا کارش تموم شه برای مثال فایلی از اینترنت دانلود کنیم یا چند ثانیه بخوایم برای کاری Sleep کنیم . حالا اگه این کار در همون Main Thread اجرا بشه چه اتفاقی میفته ؟

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

برای همین منظور ما به Background Thread ها نیاز داریم که کارهایی مثل دانلود کردن فایل ها رو به این ترد ها بسپاریم تا از قفل شدن UI برناممون جلوگیری کنیم .

مشکل Background Thread ها در اجرای کارهای طولانی

طبق مثال بالا فرض کنید فایلی که میخواهیم دانلود کنیم حجم زیادی داره ، یعنی در این مواقع باید تا پایان دانلود، برنامه ما باز باشه و ما نتونیم کار دیگه ای با دستگاه اندرویدی خودمون انجام بدیم ؟ چون با بستن برنامه تمام پروسس های مربوط به اون برنامه ها هم بسته میشه از جمله Thread های استارت شده ! خب در این مواقع باید چیکار کنیم ؟

راه حل این مشکلات Service ها هستند .

Service در اندروید چیست ؟

Service ها یکی از کامپوننت های اندروید هستند که UI ندارند و میتونند به مدت طولانی کارهایی رو در پس زمینه اجرا کنند حتی اگه برنامه اندرویدی که سرویس رو اجرا کرده بسته شده باشه .

انواع Service ها در اندروید :
  • Started Services
  • Bound Services

خود Started Services به دو نوع زیر تقسیم میشن :

  • Background Services
  • Foreground Services

Started Services :

این نوع سرویس ها با دستور

استارت میشن . پس از فراخوانی(startService(intent ، متد ()onCreate مربوط به سرویس اجرا میشه و سپس متد ()onStartCommand  . بعد از اولین بار استارت کردن یک سرویس، متد ()onCreate اجرا شده و بعد از این، هر بار که متد (context.startService(intent رو فراخوانی کنیم فقط متد ()onStartCommand مربوط به سرویس اجرا میشه . به عبارت دیگه متد ()onCreate  فقط یک بار اجرا میشه .

هنگام استارت کردن یک سرویس میتونیم به intent مقدارهای Extra نسبت بدیم و این Extra ها رو در متد ()onStartCommand بررسی کنیم . درواقع intent ی که به متد ()onStartCommand داده میشه همون intent ی هست که ما با استفاده از اون، سرویس رو استارت کردیم  یعنی (context.startService(intent

استارت کردن یک سرویس :

خب بیایم یه سرویس بسازیم و استارتش کنیم :

و کد های مربوط به MainActivity :

و اضافه کردن تگ سرویس در فایل AndroidManifast.xml

با این کار ما یک سرویس رو اجرا کردیم و 5 ثانیه صبر کردیم تا فایل دانلود بشه .

اما کد های بالا یک سری ایراداتی دارند . اول اینکه وقتی کار سرویس تموم شد سرویس همچنان در حال اجراست و از منابع سیستم استفاده میکنه . دوم اینکه سرویس ها در همان Thread ی که آن ها رو استارت کرده اجرا میشن ینی در Main Thread مربوط به برنامه . پس وقتی شما برای 5 ثانیه sleep میکنید UI برنامتون قفل میشه و هنگ میکنه .

برای استاپ کردن یک سرویس دو روش وجود داره :

  • فراخوانی متد (cotext.stopService(intent  از داخل اکتیویتی یا فرگمنت یا یک سرویس دیگه
  • فراخوانی متد ()stopSelf داخل خود سرویس و پس از اتمام کار سرویس .

جدا کردن یک سرویس از Main Thread :

برای اینکه سرویس ما از Main Thread جدا بشه و باعث هنگ کردن برنامه و UI نشه باید پس از ساخته شدن سرویس یک Thread جدید درست کرده و وظایف سرویس ایجاد شده رو درون Thread جدید بنویسیم .

کدهای مربوط به سرویس رو به صورت زیر تغییر میدیم :

البته شما میتونید روش های multi Threading دیگه ای هم استفاده کنید مهم اینه که وقتی سرویس اجرا شد وظایف سرویس باید در یک ترد دیگه اجرا بشن .

همینطور که تاحالا متوجه شدید متد ()onStartCommand یک int برگشت میده که مقادیر برگشتیش به این صورته  :

START_NOT_STICKY : اگه سیستم بعد از مقدار بازگشتی ()onStartCommand سرویس رو از بین ببره سرویس دوباره ساخته نمیشه . این گزینه بهترین گزینه برای سرویس هاییه که کار موقتی انجام میدن .

START_STICKY : اگه سیستم بعد از مقدار بازگشتی ()onStartCommand سرویس رو از بین ببره سرویس دوباره ساخته و متد ()onStartCommand هم فراخوانی میشه ولی آخرین Intent که به این متد پاس شده بود دوباره بهش پاس نمیشه و به ازای مقدار intent مقدار null تحویل داده میشه .

START_REDELIVER_INTENT : اگه سیستم بعد از مقدار بازگشتی ()onStartCommand سرویس رو از بین ببره سرویس دوباره ساخته و متد ()onStartCommand هم فراخوانی میشه و آخرین Intent ی که به این متد پاس شده بود نیز دوباره بهش تحویل داده میشه . این مورد برای سرویس هایی مناسبه که نیاز دارند تا کار قبلیشون رو ادامه بدن مثل ادامه دادن دانلود یک فایل .

نکته : متد onDestroy وقتی به وسیله اندروید اجرا میشه که شما به سرویس اجازه بدید که بفهمه کارش تموم شده یا نه . سرویس نمیدونه که در کدهای ترد شما چه میگذره و چه وقتی شروع و چه موقع به پایان میرسه . این شما هستید که باید به سرویس پایان عملیات ترد رو اطلاع بدید.

استفاده از IntentService :

اما روش ساده تری هم برای جدا کردن یک سرویس از Main Thread وجود داره که با استفاده از کلاس IntentService به جای کلاس Service هست. این کلاس که از کلاس Service مشتق شده خودش یک Worker Thread آماده داره و کارهایی که به این کلاس میسپریم رو در ترد جداگانه ای اجرا میکنه . و بعد از اینکه کار سرویس تموم شد خودش سرویس رو استاپ میکنه و دیگه نیازی به استفاده از متد stopSelf نیست .

ولی مشکلی که این روش داره اینه که اگه شما نیاز به استفاده از چند ترد دیگه در کد هاتون داشته باشید این روش مناسب نیست و این کلاس نمیتونه ترد های دیگه رو مدیریت کنه و ممکنه قبل از تموم شدن کار ترد های دیگه سرویس رو استاپ کنه .

روش استفاده از IntentService به این صورته :

Foreground Services :

اجرا شدن یک سرویس در Foreground به این معنی نیست که سرویس در Main Thread اجرا میشه بلکه به این معنیه که سیستم عامل به این سرویس یک اولویت بالا نسبت میده و در مواقعی که سیستم در شرایط منابع کمی قرار داره این سرویس رو از بین نمیبره .

وقتی یک سرویس قراره به Foreground Service تبدیل بشه باید یک نوتیفیکیشن نمایش بده تا به کاربر بفهمونه که سرویسی در حال اجراست و اگه کاربر نیاز به توقف سرویس داشته باشه ، بتونه اون سرویس رو استاپ کنه و یا اجازه ادامه کار بهش بده .

برای اینکه یک Background Service رو به Foreground Service تبدیل کنیم، متد (startForeground(id,notification رو از درون خود سرویس فراخوانی میکنیم این متد یک مقدار int برای id میگیره و یک Notification ؛ و تا زمانی که سرویس در حال اجراست این نوتیفیکیشن نمایش داده میشه . در مورد Notification هم میتونید مبحث آموزش نوتیفیکیشن در اندروید رو مشاهده کنید .

تغییرات Android O :

در اندروید O تغییرات مهمی در سرویس ها داده شده که اجازه اجرای یک Background Service رو بدون داشتن یک نوتیفیکیشن، (رفتن به حالت Foreground ) وقتی که برنامه ای که این سرویس رو اجرا کرده خودش در حال اجرا نباشه و به اصطلاح در حالت idle یا در حالت Background باشه ، رو نمیده .اما تا زمانی که برنامه در Foreground مشغول به کاره میتونه آزادانه از Background Service ها و Foreground Service ها استفاده کنه .زمانی که برنامه به بکگراند رفت یا از دید کاربر خارج شد تا چند دقیقه میتونه از سرویس استفاده کنه تا موقعی که به حالت Idle بره یا به عبارت دیگه به حالت بیکار در سیستم تبدیل بشه . در همین زمان سیستم سرویس رو خاتمه میده و انگار اینکه متد stopSelf فراخوانی شده.

قبل از اندروید 8 روش معمول برای ساخت یک Foreground Service این بود که ابتدا یک Background Service ایجاد میکردند و بعد اون رو به Forground می بردند، اما  برای استارت کردن سرویس ها در اندروید 8 به بالا، باید از متد (startForegroundService(intent استفاده کنید تا بتونید سرویس رو به حالت Foreground ببرید . و پس از فراخوانی متد (startForegroundService(intent  و استارت شدن سرویس فقط 5 ثانیه مهلت دارید تا با استفاده از متد ()startForeground سرویس رو به حالت Foreground برده و نوتیفیکیشن بهش نسبت بدید . اگه تو این مدت سرویس به Forground نره، سیستم سرویس رو متوقف میکنه و یک ANR به کاربر نمایش میده .

نکته 1: به طور پیش فرض این محدودیت ها فقط برای اندروید 8 و برنامه هایی که Target اونها اندورید 8 و بالاتر هست اعمال میشن . پس اگه میخاین این محدودیت ها برای برنامتون به طور پیش فرض اعمال نشن Target برنامه رو به اندروید 8 به پایین قرار بدید تا مشکل سرویس ها در اندروید 8 حل بشه البته این راه حل قطعی نیست و ممکنه با این وجود برنامتون بعدا با مشکل مواجه بشه و بهتره برای کارهای پریودیک از JobScheduler استفاده کنید !

نکته 2 :در هر صورت کاربری که از اندروید 8 استفاده میکنه میتونه بعضی از این محدودیت هارو روی برنامه شما حتی اگه Target برنامه اندروید 8 هم نباشه ، اعمال کنه .

نکته 3 : برای متوقف کردن یک Foreground Service ابتدا باید متد (stopForeground(true رو از درون سرویس فراخوانی کرده و سپس متد ()stopSelf . متد (stopForeground(true فقط نوتیفیکیشن سرویس رو حذف میکنه و سرویس رو متوقف نمیکنه .

فرق بین Started Service وBound Service :

Started Service با استفاده از یک intent استارت میشه و شئی که این سرویس رو اجرا کرده دیگه ارتباطی با سرویس استارت شده نداره و اگه نیاز به این باشه که دوباره با سرویس اجرا شده ارتباط برقرا کنه، باید سرویس رو با یک intent جدید و با مقادیر جدید استارت کنه ( یک سرویس چندین بار میتونه استارت بشه ) . و این تفاوتیه که یک Started Service با یک Bound Service داره . وقتی یک اکتیویتی یک binder مربوط به سرویس استارت شده رو داشته باشه میتونه متد های public مربوط به اون سرویس رو به طور مستقیم اجرا کنه .

Bound Services :

برخلاف Started Service ، سرویس های Bound Service اجازه ارتباط بین اجزای اندروید و سرویس ها رو میدند .و این ارتباط با استفاده از شیء IBinder امکان پذیر است . تو این مقاله و مثال هایی که میزنیم شیء IBinder ما به کلاینت اجازه دسترسی به متد های public رو میده.

در اینجا مقداری تفاوت بین Started Service و Bound Service وجود داره :

  • در سرویس های Started Service اجزای یک اکتیویتی ارتباط مستقیم با سرویس ندارند و فقط با استفاده از intent و بررسی مقادیر extra های intent در داخل متد ()onStartCommand مربوط به سرویس، میتونه با سرویس ارتباط برقرار کنه .
  • وقتی اجزای کلاینت مثل اکتیویتی ، فرگمنت یا سرویس های دیگه به یک Bound Service متصل میشن، در اینجا شیء IBinder  است که ارتباط را با اجزای کلاینت برقرار میکنه و اجازه استفاده از متد های public سرویس رو به کلاینت میده .
  • در هر دو حالت وقتی یک سرویس بخواد به یک جز از کلاینت پیغام ارسال کنه باید با استفاده از ارسال Broadcast یا استفاده از چیزی شبیه به LoacalBroadcastManager این کار رو انجام بده . یک سرویس نمیتونه مستقیم با یک جز از کلاینت مرتبط بشه .

برای bind کردن یک سرویس از طریق یک اکتیویتی ، فرگمنت یا یک سرویس دیگه، این اجزا باید از متد ()bindService استفاده کنند.

پارامتر BIND_AUTO_CREATE در کد بالا به سرویس میگه که اگه هنوز متد ()onCreate مربوط به سرویس اجرا نشده این متد رو اجرا کن.

وقتی ()bindService فراخوانی میشه سرویس به یک راهی نیاز داره که به کلاینت پاسخ بده و یک شیء IBinder به کلاینت تحویل بده که کلاینت بتونه از متد های public سرویس استفاده کنه . این کار با استفاده از پارامتر mServiceConnection در کد بالا انجام میشه . mServiceConnection یک ServiceConnection Callback هستش که با استفاده از اون، سرویس میتونه به کلاینت خبر بده که فرایند Bound شدن تمام شده یا نه .

Service Binder :

بذارید یک نگاهی به سمت Bound Service وقتی که کلاینت ()bindService  رو فراخوانی میکنه بندازیم .

در سرویس Bound شده شما باید متد ()onBind رو override کنید. این متد وقتی که کلاینت به این سرویس bind میشه فراخوانی میشه .

سرویس ما شیء mBinder رو که از IBinder مشتقش شده رو تولید کرده . اما این mBinder چیه ؟ Binder یک کلاس پایه اندرویده که اجازه ساخت یک شیء Remotable رو میده

در کد بالا ما متد getService رو ساختیم که فقط شیء this مربوط به سرویس ما رو return میکنه . با استفاده از این شیء IBinder ،کلاینت میتونه به متدهای public سرویس دسترسی داشته باشه . توجه داشته باشید که این متدهای فراخوانی شده از طرف کلاینت، در ترد خود کلاینتی که این متد ها رو فراخوانی کرده اجرا میشن، به طور مثال اگه از یک اکتیویتی این متد ها رو فراخوانی کنیم متدهای فراخوانی شده در Main Thread اجرا میشن . پس باید حواسمون به اجرای متد هایی از سرویس که فرایند طولانی دارند یا باعث هنگ کردن UI میشن ، باشیم .

onBind  و onDestroy :

برای unBind کردن کافیه که متد (unBindService(mServiceConnection رو فراخوانی کنیم . در این صورت سیستم متد ()onUnBind مربوط به سرویس رو اجرا میکنه . و اگه کلاینت دیگه ای به این سرویس bind نشده باشه سیستم متد ()onDestroy رو اجرا میکنه ( مگر اینکه سرویس در حالت Started باشه ).

ما برای متد onUnbinde مقدار false رو بازگشت دادیم . اگه مقدار true بازگشت داده بشه وقتی کلاینت دیگه ای به این سرویس bind بشه، متد onRebind به جای متد on‌Bind فراخوانی میشه .

در بعضی مواقع ممکنه کلاینت از سرویس به هر دلیلی unBind بشه . حالا اگه سرویس در حال اجرای کاری باشه و همچنین سرویس در حالت Started هم نباشه اینجاست که سرویس متوقف میشه و وظیفه اش نیمه تمام باقی میمونه . اگه نیاز دارید که سرویس شما به ادامه کارش بپردازه ابتدا باید سرویس رو استارت کنید (به حالت Started ببرید ) و سپس به حالت Foreground .

نکته : تا زمانی که کلاینت به سرویس bind شده ، سرویس هنوز به حالت Started نرفته . برای اینکه هر دو حالت Bound Service و Started Service رو داشته باشیم باید سرویس خودش ، خودش رو به حالت Started ببره.

این آموزش هم به پایان رسید و امیدوارم که براتون مفید واقع شده باشه .  🙂

نظرات و پیشنهادات خودتون رو با ما در میون بذارید .

0 0 vote
Article Rating

به این مطلب چه امتیازی میدهید ؟

اشتراک
باخبر شدن از
guest
3 Comments
Inline Feedbacks
View all comments
abbas
abbas
3 ماه پیش

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

alireza
alireza
2 ماه پیش

مرسی خیلی کمکم کرد 🙂
لطفا بازم ادامه بده