ایجاد یک سرویس شروع شده
یک سرویس شروع شده، سرویسی است هر مؤلفه ای میتواند با فراخوانی متد startService() آن را شروع کند که منجر به فراخوانی متد ()onStartCommand سرویس می شود. هنگامی که یک سرویس راه اندازی می شود، چرخه حیاتی دارد که مستقل از مؤلفه ای است که آن را راه اندازی کرده است. این سرویس می تواند به طور نامحدود در پس زمینه اجرا شود، حتی اگر مؤلفه ای که آن را راه اندازی کرده از بین برود. به این ترتیب، سرویس باید زمانی که کارش تمام شد با فراخوانی stopSelf() خود را متوقف کند، یا مؤلفه دیگری می تواند با فراخوانی stopService() آن را متوقف کند.
یک کامپوننت از برنامه مانند یک اکتیویتی می تواند سرویس را با فراخوانی startService() و ارسال یک Intent که سرویس را مشخص می کند و شامل هر داده ای برای استفاده سرویس است، آن را راه اندازی کند. سرویس این Intent را در متد onStartCommand() دریافت می کند.
به عنوان مثال، فرض کنید یک اکتیویتی باید مقداری داده را در یک دیتابیس آنلاین ذخیره کند. این اکتیویتی میتواند یک سرویس همراه راهاندازی کند و دادههای ذخیرهسازی را با ارسال یک intent به startService() تحویل دهد. این سرویس intent را در متد onStartCommand دریافت می کند، به اینترنت متصل می شود و انتقال داده را به پایگاه داده انجام می دهد. هنگامی که انتقال کامل شد، سرویس خود به خود متوقف می شود و از بین می رود.
کلاس Service
کلاس Service کلاس پایه برای همه سرویس ها است. هنگامی که این کلاس را گسترش می دهید، ایجاد یک ترد جدید که در آن سرویس بتواند تمام کارهای خود را تکمیل کند، مهم است. این سرویس به طور پیش فرض از ترد اصلی برنامه شما استفاده می کند، که می تواند عملکرد هر اکتیویتی را که برنامه شما در حال اجرا آن است کند کند.
اندروید همچنین زیرکلاس IntentService را ارائه میکند که از یک ترد کارگر برای رسیدگی به تمام درخواستهای شروع، یک به یک استفاده میکند. استفاده از این کلاس برای برنامههای جدید توصیه نمیشود، زیرا با شروع اندروید 8 اوریو، به دلیل معرفی محدودیتهای اجرای پسزمینه، به خوبی کار نمیکند. علاوه بر این، با انتشار اندروید 11 این کلاس منسوخ شده است.
می توانید از JobIntentService به عنوان جایگزینی برای IntentService که با نسخه های جدیدتر اندروید سازگار است استفاده کنید.
بخشهای زیر نحوه پیادهسازی سرویس سفارشی خود را توضیح میدهند، با این حال باید حتما استفاده از WorkManager را برای بیشتر موارد استفاده در نظر بگیرید.
گسترش کلاس Service
می توانید کلاس Service را برای مدیریت هر Intent ورودی گسترش دهید. در پایین یک پیاده سازی اولیه را اماده کرده ایم:
public class HelloService extends Service { private Looper serviceLooper; private ServiceHandler serviceHandler; // کنترل کننده ای که پیام ها را از ترد دریافت می کند private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { // به طور معمول ما برخی از کارها را در اینجا انجام می دهیم، مانند دانلود یک فایل. // برای نمونه ما فقط 5 ثانیه انتظار مصنوعی ایجاد میکنیم. try { Thread.sleep(5000); } catch (InterruptedException e) { // بازیابی وضعیت وقفه Thread.currentThread().interrupt(); } // سرویس را با استفاده از startId متوقف کنید تا متوقف نشویم // سرویس در وسط رسیدگی به کار دیگری stopSelf(msg.arg1); } } @Override public void onCreate() { // تردی را که سرویس را اجرا می کند راه اندازی کنید. توجه داشته باشید که ما فقط یکی باید راه اندازی کنیم. // ترد را جدا کنید زیرا این سرویس معمولاً در ترد اصلی اجرا می شود //ترد اصلی، که ما نمی خواهیم آن را مسدود کنیم. برای همین ما هم یک ترد جدا ایجاد میکنیم HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); //HandlerThread's Looper را دریافت کنید و از آن برای Handler خود استفاده کنید serviceLooper = thread.getLooper(); serviceHandler = new ServiceHandler(serviceLooper); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "سرویس شروع شد", Toast.LENGTH_SHORT).show(); // برای هر درخواست شروع، یک پیام برای شروع کار ارسال کنید و آن را تحویل دهید // شناسه شروع را باید بدانیم تا موقع توقف بدانیم که کدام یکی را باید متوقف کنیم Message msg = serviceHandler.obtainMessage(); msg.arg1 = startId; serviceHandler.sendMessage(msg); // اگر سرویس کشته شد, مجدد با این مقدار میتوانیم دوباره راه اندازی کنیم. return START_STICKY; } @Override public IBinder onBind(Intent intent) { // ما قصد نداریم به سرویس متصل شویم پس nullرا بر میگردانیم. return null; } @Override public void onDestroy() { Toast.makeText(this, "سرویس تکمیل شد", Toast.LENGTH_SHORT).show(); } }
نمونه کدهای بالا مثال تمام متدهای دریافتی در onStartCommand() را مدیریت میکند و کار را به یک Handler در حال اجرا بر روی یک ترد پسزمینه ارسال میکند. درست مانند IntentService کار می کند و تمام درخواست ها را به صورت سریالی یکی پس از دیگری پردازش می کند. برای مثال، اگر میخواهید چندین درخواست را به طور همزمان اجرا کنید، میتوانید کد را برای اجرای کار روی یک Thread Pool تغییر دهید.
توجه داشته باشید که متد onStartCommand() باید یک عدد صحیح برگرداند. عدد صحیح مقداری است که توضیح می دهد که چگونه سیستم باید سرویس را در صورتی که سیستم آن را از بین ببرد ادامه دهد. مقدار بازگشتی از onStartCommand() باید یکی از ثابت های زیر باشد:
START_NOT_STICKY
اگر سیستم پس از بازگشت ()onStartCommand سرویس را از بین برد، سرویس را دوباره ایجاد نکنید، مگر اینکه اهداف معلقی برای ارائه وجود داشته باشد. این امن ترین گزینه برای جلوگیری از اجرای سرویس شما در مواقعی است که ضروری نیست و زمانی که برنامه شما می تواند کارهای ناتمام را دوباره راه اندازی کند.
START_STICKY
اگر سیستم بعد از بازگشت onStartCommand() سرویس را از بین برد، سرویس را دوباره ایجاد کنید و onStartCommand() را فراخوانی کنید، اما آخرین intent را دوباره تحویل ندهید. در عوض، سیستم onStartCommand() را با یک intent خالی فراخوانی می کند، مگر اینکه اهداف معلقی برای شروع سرویس وجود داشته باشد. در آن صورت، آن intent تحویل داده میشود. این برای پخش کننده های رسانه ای (یا سرویس های مشابه) که دستورات را اجرا نمی کنند اما به طور نامحدود در حال اجرا هستند و منتظر کار هستند مناسب است.
START_REDELIVER_INTENT
اگر سیستم پس از بازگشت onStartCommand() سرویس را از بین برد، سرویس را دوباره ایجاد کنید و onStartCommand() را با آخرین intent که به سرویس تحویل داده شد فراخوانی کنید. هر هدف معلق به نوبه خود تحویل داده می شود. این برای سرویس هایی مناسب است که به طور فعال کاری را انجام می دهند که باید فوراً از سر گرفته شود، مانند دانلود یک فایل.
راه اندازی یک سرویس
شما می توانید با ارسال یک Intent به startService() یا startForegroundService() یک سرویس را از یک Activity یا سایر مؤلفه های برنامه شروع کنید. سیستم اندروید متد ()onStartCommand سرویس را فراخوانی می کند و به آن Intent می دهد که مشخص می کند کدام سرویس را شروع کند.
به عنوان مثال، یک اکتیویتی میتواند سرویس نمونه در بخش قبلی (HelloService) را با استفاده از یک intent واضح با startService()، شروع کند، همانطور که در پایین نشان داده ایم:
Intent intent = new Intent(this, HelloService.class); startService(intent);
متد startService() بلافاصله برمی گردد و سیستم اندروید متد ()onStartCommand سرویس را فراخوانی می کند. اگر سرویس از قبل اجرا نشده باشد، سیستم ابتدا متد onCreate() و سپس متد onStartCommand() را فراخوانی می کند.
اگر سرویس نیز binding را ارائه نمیکند، intentی که با startService() ارائه میشود تنها حالت ارتباطی بین مؤلفه برنامه و سرویس است. با این حال، اگر میخواهید که سرویس نتیجه ای را بازگرداند، کلاینتی که سرویس را راهاندازی میکند میتواند یک PendingIntent برای broadcast (با getBroadcast()) ایجاد کند و آن را به سرویسی که Intent آن را شروع میکند تحویل دهد. سپس سرویس می تواند از broadcast برای ارائه نتیجه استفاده کند.
درخواست های متعدد برای اجرای یک سرویس, منجر به تماس های متناظر متعدد با متد onStartCommand سرویس می شود. با این حال، تنها یک درخواست برای توقف سرویس (با stopSelf() یا stopService()) برای متوقف کردن آن مورد نیاز است.
توقف یک سرویس
یک سرویس started باید چرخه عمر خود را مدیریت کند. یعنی سیستم سرویس را متوقف یا از بین نمی برد مگر اینکه باید حافظه سیستم را بازیابی کند. که پس از بازگشت ()onStartCommand به کار خود ادامه دهد. سرویس باید خود را با فراخوانی stopSelf() متوقف کند، یا مؤلفه دیگری می تواند با فراخوانی stopService() آن را متوقف کند.
پس از درخواست توقف با stopSelf() یا stopService()، سیستم در اسرع وقت سرویس را از بین می برد.
اگر سرویس شما چندین درخواست به onStartCommand() را به طور همزمان مدیریت می کند. پس از اتمام پردازش درخواست شروع، نباید سرویس را متوقف کنید. زیرا ممکن است یک درخواست اجرای جدید دریافت کرده باشید. توقف بعد پایان درخواست اول باعث توقف درخواست دوم شما نیز میشود.
برای جلوگیری از این مشکل، می توانید از متد stopSelf(int) استفاده کنید. با اینکار اطمینان حاصل کنید که درخواست شما برای توقف سرویس همیشه بر اساس آخرین درخواست شروع است. یعنی وقتی متد stopSelf(int) فراخوانی میشود، شناسه درخواست شروعی را که درخواست توقف شما با آن مطابقت دارد، ارسال میکنید.startId یک شناسه است که به عنوان پارامتر ورودی متد onStartCommand() ارسال میشود. سپس، اگر سرویس قبل از اینکه بتوانید stopSelf(int) را فراخوانی کنید، درخواست شروع جدیدی دریافت کند، شناسه مطابقت ندارد و سرویس متوقف نمیشود.
نکات مهم
توجه: اگر برنامه شما سطح API 26 یا بالاتر را هدف قرار میدهد، سیستم محدودیتهایی را برای استفاده یا ایجاد سرویسهای پسزمینه اعمال میکند، مگر اینکه خود برنامه در پیشزمینه باشد. اگر یک برنامه نیاز به ایجاد یک سرویس پیش زمینه داشته باشد، برنامه باید متد startForegroundService() را فراخوانی کند. این متد یک سرویس پسزمینه ایجاد میکند، اما این متد به سیستم سیگنال میدهد که سرویس خود را در پیشزمینه ارتقا میدهد. پس از ایجاد سرویس، سرویس باید متد startForeground() خود را در عرض پنج ثانیه فراخوانی کند.
احتیاط: برای جلوگیری از هدر رفتن منابع سیستم و مصرف انرژی باتری، مطمئن شوید که برنامه شما پس از اتمام کار، سرویس خود را متوقف می کند. در صورت لزوم، سایر مؤلفه ها می توانند با فراخوانی stopService() سرویس را متوقف کنند. حتی اگر binding را برای سرویس فعال کنید، همیشه باید خودتان سرویس را متوقف کنید.
احتیاط: یک سرویس به طور پیش فرض در ترد اصلی آن برنامه اجرا می شود. اگر سرویس شما حین کار کردن کاربر با برنامه، عملیات مسدود کننده انجام دهد, سرویس عملکرد اکتیویتی را کاهش می دهد. برای جلوگیری از تأثیرگذاری بر عملکرد برنامه، یک ترد جدید در داخل سرویس شروع کنید.