آموزش سرویس‌ها در برنامه‌نویسی اندروید (android services)

سرویس ها یکی از 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!
سرویس پیاده سازی شده در کد بالا، با این که ممکن است یک background task انجام بدهد اما با استفاده از کلاس Service بدین شکل این کار در همان main thread اجرا میشود. برای همین ممکن است کار با ui دچار مشکل شود چون نمایش ui نیز در همان thread در حال انجام است. در واقع باید توجه داشت کار background در background thread انجام نشده است.

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
   }
}

بدین شکل از پایان کار سرویس با خبر میشویم.

Was this article helpful?
Dislike 0
قبلی: مفهوم Thread در برنامه‌نویسی اندروید
بعدی: پخش صوت در اندروید با استفاده از کلاس MediaPlayer