سرویس ها یکی از Component های اصلی اندروید است. سرویس های در واقع تسک های پس زمینه (background tasks) هستند که به صورت موازی با برنامه اصلی اجرا میشوند؛ مثلا برای پخش آهنگ یا برای دریافت فایلی در محیط یک browser.
سرویس ها یکی از ابزارهایی هستند که برای multi-threading استفاده میشود و Async وابسته و attach شده به یک fragment یا activity نیست. سرویس چرخه حیات خود را دارد و همانطور که گفته شد Component اصلی است. این یعنی توسط سیستم عامل مدیریت میشود و برای ارتباط با آن مثل بقیه Component ها به intent نیاز داریم و register کردن آن در manifest قطعا الزامیست.
پس کلا سرویس کارهای background انجام میدهد. نوع های مختلفی برای سرویس داریم.
Foreground Service
سرویسی است که توسط کاربر قابل مشاهده است و کاربر از وقوع آن باخبر است و در drawer area به صورت یک notification نمایش داده میشود. پس برای ساختن آن به notification نیاز خواهیم پیدا کرد. نکته قابل توجه این است که بعد از بسته شدن activity این سرویس به کار خودش ادامه خواهد داد. همیشه این نکته را باید به خاطر داشت که تا زمانی که واسط کاربری برنامه ما موجود باشد حالا برای مثال در drawer area متوقف شدن آن توسط سیستم عامل به دلیل کمبود منابع بسیار پایین است و از اولویت کمی برخوردار است.
Background Service
بر خلاف foreground کاربر از آن خبر ندارد و مشاهده نمیشود. در نتیجه واسط کاربری ای (UI) برای آن نداریم.
سرویس های Bounded و Unbounded
در Bounded Service کامپوننت اندرویدی مانند اکتیویتی یا کلاینت، امکان تعامل با سرویس را داشته و دائما پیغامهای بین سرویس و کامپوننت مبادله میشود. در واقع یک Bounded Service یک واسط کلاینت سرور (client-server) ارائه میدهد و به کامپوننتها اجازه میدهد تا با Service ارتباط برقرار کنند، درخواستهای خود را به آن ارسال و همچنین نتایج را نیز دریافت نمایند. با استفاده از متد bindService ارتباط بین کامپوننت و سرویس برقرار شده و توسط unbindService این ارتباط قطع میگردد.
ساختن یک سرویس در برنامه نویسی اندروید
برای ساختن یک سرویس کلاس باید از Service ارث ببرد. و فراموش نکنید باید آن را در manifest رجیستر نمایید. کد زیر را در تگ application بگذارید.
<service android:name=".ServiceClassName" android:enabled="true" android:exported="false" />
سروریس زیر را برای نمونه ببینید.
public class ServiceClassName extends Service { @Override public int onStartCommand(Intent intent, int flags, int id) { // unpack any parameters that were passed to us String value1 = intent.getStringExtra("key1"); String value2 = intent.getStringExtra("key2"); // do the work that the service needs to do . return START_STICKY; // stay running } @Override public IBinder onBind(Intent intent) { return null; // disable binding } }
چرخه حیات یک سرویس از لحاظ داشتن onCreate و onDestory به یک Activity شبیه است. callback اصلی که همان onStartCommand است ممکن است در صورت قطع شدن بارها صدا زده شود. کار اصلی در سرویس در همین onStartCommend صورت میگیرد. این که رفتار سرویس ما چگونه باشد بستگی به مقدار برگردانده شده دارد. دقت کنید حتی اگر به onBind نیاز ندارید باید پیاده سازی کنید و null برگردانید.
حالت های مختلف اجرای سرویس
۱) START_NOT_STICKY: در این حالت بعد از توقف سرویس توسط سیستم، سرویس را در حالت ساخته شده نگه نمیدارد و صرفا هنگامی سرویس دوباره ساخته خواهد شد که startService فراخوانی شود. این گزینه مناسب سرویسهایی است که یک کار موقت را انجام داده و نیاز به تکرار ندارند.
۲) START_STICKY: بعد از توقف سرویس توسط سیستم، سرویس را در حالت ساخته شده نگه میدارد. یعنی متد onCreate و onStartCommand را بلافاصله مجدد اجرا میکند ولی مقدار intent ای که برای آن درنظر میگیرد برابر با null است و نه intent ای که در مرتبه قبل برای آن ارسال شده. در این صورت شاهد سرعت بیشتری در اجرای درخواستهای بعدی خواهیم بود که از کامپوننت دریافت میشوند.
۳) START_REDELIVER_INTENT: در این حالت علاوه بر اینکه مانند حالت دوم، سرویس را در حالت ساخته شده نگه میدارد، intent را هم برابر آخرین intent ای که به متد پاس داده شده قرار میدهد. این گزینه مناسبترین انتخاب برای مواردی است که سرویس باید عملیات را از جایی که قبلا متوقف شده ادامه دهد. مانند ادامه پخش یک فایل صوتی یا دانلود یک فایل.
اجرای سرویس
برای اجرا کردن سرویس لازم است یک intent ساخته و از متد startService استفاده کنید. مانند بقیه آبجکتهای intent میتوانید برای آن action نیز set نمایید.
// In Your Activity Intent intent = new Intent(this, ServiceClassName.class); intent.putExtra("key1", "value1"); intent.putExtra("key2", "value2"); startService(intent); // not startActivity!
Intent Service چیست؟
با توجه به اخطار بالا راه حل چیست؟ کلاس Service یک کلاس خام است راه حل سختتر پیاده سازی یک MessageLoop در ساختار آن است تا بتواند مشکل ما را حل کند.
MessageLoop شامل یک صف از Message ها و Looper و Handler است. بطور خلاصه کارها در صف توسط looper قرار گرفته و برداشته میشود و handler ها آن را انجام میدهند.
راه سادهتری نیز وجود دارد: کلاسی به نام IntentService که دقیقا همین موارد را در خود دارد و MessageLoop را در خود پیادهسازی نموده است. پس غالبا نیازی به کشیدن زحمت پیادهسازی آن نیست. یک نمونه از IntentService به صورت زیر است:
public class HelloIntentService extends IntentService { public HelloIntentService() { super("HelloIntentService"); } @Override protected void onHandleIntent(Intent intent) { // Normally we would do some work here, like download a file. // For our sample, we just sleep for 5 seconds. try { Thread.sleep(5000); } catch (InterruptedException e) { // Restore interrupt status. Thread.currentThread().interrupt(); } } }
تمام چیزی که نیاز دارید همین یک constructor و پیادهسازی onHandleIntent است.
Foreground Services
کدها و سرویس های ساخته شده تا الان همه جزو background services بوده اند. برای مثال فرض کنید یک HandlerThread دارید و میخواهید حتی عملیاتی را حتی در صورت بسته شدن برنامه انجام دهید. این نکته بسیار حائز اهمیت است که اگر یک برنامه ui نداشته باشد اولویت بسیاری خوبی برای سیستم عامل پیدا میکند تا در هنگام کمبود منابع سختافزاری این عملیاتها را kill کند تا منابع تقویت شوند. برای همین شاید بهترین انتحاب همین استفاده از یک foreground service باشد که دارای ui بوسیله یک notification است که در قسمتهای قبل اشاره شده است. برای مثال وقتی یک برنامه پخش موسیقی آهنگی را پخش میکند با بستن activity نباید آنگ قطع شود.
برای ساختن ابتدا همان مراحل را با کلاس Serivce ادامه میدهیم. در activity مورد نظر به جای متد startService باید از startForegroundService استفاده کنیم.
Intent intent = new Intent(this, MusicPlayerService.class); startForegroundService(intent);
همانطور که گفته شد یک notification میخواهیم و آن را در MusicPlayerService.java میسازیم:
// Define Actions … // Do not Forget Notification Channel this.notificationChannel = new NotificationChannel( MAIN_MUSIC_CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT ); this.notificationManager.createNotificationChannel(notificationChannel); this.mediaNotification = new Notification.Builder(this, MAIN_MUSIC_CHANNEL_ID) .setSmallIcon(R.drawable.notification_icon) .setPriority(Notification.PRIORITY_HIGH) .setVisibility(Notification.VISIBILITY_PUBLIC) // Actions .addAction(R.drawable.ic_skip_previous, "Previous", previousPI) // #0 .addAction(R.drawable.ic_pause, "Pause", resumePI) // #1 .addAction(R.drawable.ic_skip_next, "Next", nextPI) // #2 .setContentTitle("Wonderful music") .setContentText("My Awesome Band") .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.logo)) .setCustomBigContentView(notificationLayoutBig) .build();
در نهایت notification ساخته میشود و کافی است startForeground را اجرا کنیم:
startForeground(NOTIFICATION_ID, mediaNotification);
بدین صورت تا زمانی که نوتیفیکیشن باید و برنامه ForceStop نشده باشد سرویس اجرا میشود.
خاتمه اجرای سرویس
بوسیله متدهای stopSelf یا stopService سرویس ها متوقف و نابود میشوند.
اگر بخواهیم از تمام شدن یک task در سرویسها کاری را انجام بدهیم راه هایی پیش روی ما است. یکی از آن ها استفاده از BroadcastReciever است؛ البته شاید استفاده از کتابخانههایی مثل EventBus بهتر باشد.
همانطور که میدانید BroadcastReciever نیز یکی دیگر از component های اندروید است. مانند یک رادیو آماده گرفتن BoradcastIntent ها است تا متد onRecieve خود را اجرا کند. پس ما میتوانیم بعد از اتمام کار در onStartCommand یک BroadcastIntent زده و بدین شکل متوجه اتمام کار سرویس میشویم.
public class ServiceClassName extends Service { @Override public int onStartCommand(Intent tent, int flags, int id) { // do the work that the service needs to do ... // broadcast that the work is done Intent done = new Intent(); done.setAction("action"); done.putExtra("key1", value1); ... sendBroadcast(done); return START_STICKY; // stay running } }
بدین شکل از پایان کار سرویس با خبر میشویم.