IntentService

Background

  |   5 min czytania

Wstęp

IntentService jest rozszerzeniem klasy Service o obsługę zadań przetwarzanych na w tle na dedykowanym wątku roboczym przy pomocy klasy Handler. Umożliwia wykonanie pracy w sposób asynchroniczny niezależnie od wątku głównego, jednakże obsługuje tylko jedno zadanie w danej chwili. Wysyłane żądania w postaci obiektu Intent zostają natychmiast obsłużone lub trafiają do kolejki oczekujących. Gdy cała kolejka zadań zostanie zrealizowana wówczas usługa automatycznie kończy pracę. Większość zdarzeń cyklu życia interfejsu użytkownika nie ma wpływu na działanie IntentService (w przeciwieństwie do AsyncTask). Przeznaczony jest przede wszystkim do prostych operacji w tle, które niekoniecznie wymagają reakcji interfejsu użytkownika na otrzymany rezultat.

Implementacja

Aby stworzyć komponent IntentService należy zdefiniować klasę rozszerzającą klasę IntentService, nadpisać metodę onHandleIntent odpowiedzialną za przechwytywanie żądań i podobnie jak przy klasycznej usłudze (Service) dodać do pliku manifestu (AndroidManifest).

class CustomIntentService : IntentService("name") {

    override fun onHandleIntent(intent: Intent?) {
        //retrieve data
        val action = intent?.action
        val data = intent?.dataString
        //do some work based on action
    }

    //avoid to override Service's callback like onStartCommand
    //they are automatically invoked by IntentService
}

class CustomActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_custom)
        button.setOnClickListener { startIntentService() }
    }

	//just call startService to run IntentService
    private fun startIntentService() {
        val intent = Intent(this, CustomIntentService::class.java)
        intent.action = "action"
        intent.putExtra("extra", "value")
        startService(intent)
    }
}

Komunikacja

IntentService nie dostarcza mechanizmu odpowiedzi zwrotnej do klientów. W celu implementacji komunikacji do interfejsu użytkownika lub innych fragmentów kodu aplikacji można posłużyć się m.in. BroadcastReceiver lub szyną zdarzeń.

class IntentServiceCommunication : IntentService("name") {

    override fun onHandleIntent(intent: Intent?) {
        //retrieve data
        val action = intent?.action
        val data = intent?.getStringExtra("extra")
        //do some work based on action

        sendBroadcast()
    }

    private fun sendBroadcast() {
        val broadcastIntent = Intent("FILTER_ACTION")
        broadcastIntent.putExtra("message", "result")
        sendBroadcast(broadcastIntent)
        Log.d("IntentService", "sendBroadcast")
    }
}

class CommunicationActivity : AppCompatActivity() {

    private val receiver = CustomBroadcastReceiver()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_communication)
        button.setOnClickListener { startIntentService() }

        registerReceiver(receiver, IntentFilter("FILTER_ACTION"))
    }

    override fun onDestroy() {
        //if service is running and don't need anymore callback then stop IntentService
        unregisterReceiver(receiver)
        super.onDestroy()
    }

    fun startIntentService() {
        val intent = Intent(this, CustomIntentService::class.java)
        intent.action = "action"
        intent.putExtra("extra", "value")
        startService(intent)
    }

    //implement as nested class if only need to interact with this activity
    class CustomBroadcastReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {
            val message = intent?.getStringExtra("message")
            //do something like update UI
        }
    }
}

Ograniczenia

Poza koniecznością zapewnienia mechanizmu odpowiedzi zwrotnej do klienta oraz ograniczeniem przepływu pracy do jednego wątku należy uwzględnić także restrykcje procesów w tle dla Service (w tym IntentService) w wyniku których usługa musi działać w trybie foreground (inaczej zostanie po pewnym czasie zatrzymana). Rozwiązaniem tego problemu może być użycie JobIntentService lub wystartowanie usługi w trybie foreground (startForegroundService) i wyświetlenie notyfikacji z poziomu usługi (startForeground).

JobIntentService

JobIntentService w zależności od wersji systemu dobiera optymalną strategię wykonywania kolejki zadań w tle. W przypadku wersji systemu od Android Oreo zadania zostaną wykonane przy użyciu JobScheduler, natomiast dla starszych wersji usługa zostanie wywołana w standardowy sposób przy pomocy metody startService. Klasa rozszerzająca JobIntentService powinna nadpisać metodę onHandleWork w której będzie wykonywana praca (podobnie jak w onHandleIntent w IntentService). Klient dodaje zadanie do kolejki wywołując enqueueWork.

class CustomJobIntentService : JobIntentService() {

    companion object {
        const val ID = 100

        //method for enqueuing work to this service, just call from client to start
        fun enqueueWork(context : Context, work : Intent) {
            enqueueWork(context, CustomJobIntentService::class.java, ID, work)
        }
    }

    override fun onHandleWork(intent: Intent) {
        //do some job here
        //use BroadcastReceiver or something else to post the result
    }
}
<!-- remember to add Service to AndroidManifest in the same way like base JobService 
//with required BINB_JOB_SERVICE permission -->
<service
    android:name=".CustomJobIntentService"
    android:permission="android.permission.BIND_JOB_SERVICE"/>