Service (Сервис) или Служба является одним из основных компонентов Android. Он используется повсеместно: от функционала для фонового скачивания файлов с интернета до проигрывания музыки с файловой системы устройства. Поэтому любому андроид разработчику необходимо понимать как работает компонент Service и уметь его правильно использовать.
Теория по Service в Android
Служба работает в фоновом режиме, а это означает, что она не имеет пользовательского интерфейса и не зависит от Activity. Таким образом, Activity может быть уничтожена операционной системой, а Service продолжит работать в фоновом режиме. Чаще всего служба используется для выполнения длительных операций, с которыми не справится какой-нибудь AsyncTask.
Есть 2 типа служб в Android:
- Непривязанная служба (Unbound service)
- Привязанная служба (Bound service)
Unbound service — это служба, которая запускается Activity или с помощью BroadcastReceiver и не зависит от Activity. Он всегда будет работать в фоновом режиме. Такую службу может убить только система Android, когда ей недостаточно памяти для выполнения более приоритетных задачи.
Есть 2 типа непривязанной службы:
- Служба, которая наследуется от класса Service.
- Служба, которая наследуется от класса IntentService.
Рассмотрим разницу между ними и где лучше использовать каждый тип:
Служба, которая наследуется от класса Service, будет работать в фоновом режиме не зависимо от того закончена ли операция ради которой она была запущена. Она остановится только когда вы явно вызовете метод stopSelf()
для нее. Очень часто такие службы являются причиной быстрого разряда батареи вашего смартфона.
Внутри службы можно запускать несколько задач и выполнять их параллельно. Например, скачивать несколько mp3 файлов с интернета. Мы не знаем какая задача будет завершена первой, а какая — последней, поэтому контролировать процессы внутри службы нам помогает жизненный цикл компонента Service.
Проходит он так:
onCreate()
-> onStartCommand()
-> onDestroy()
Вызов onCreate()
означает, что служба создана. После этого вызывается onStartCommand()
, который свидетельствует о том, что служба начала работу в фоновом режиме. Именно в этом методе мы должны описать задачи, которые необходимо выполнить в компоненте Service. Вызов метода onDestroy()
даст нам знать, что сервис уничтожен.
Пример создания Service в Android
Наже представлен просто пример работы сервиса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.support.annotation.Nullable; public class SimpleService extends Service { @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // вызываем здесь метод, который будет работать в фоне doStuff(startId); return super.onStartCommand(intent, flags, startId); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); } private void doStuff(int startId) { // останавливаем сервис, если метод уже отработал stopSelf(startId); } } |
Предположим, что мы пишем приложение, в котором SimpleService в фоне выполняет какую-то важную работу. Из теории выше мы знаем, что Service может быть уничтожен системой Android если ей понадобится дополнительная память. Поэтому нам нужно позаботиться о том, чтобы служба снова перезапустилась, когда появится доступная память. В этом случае мы можем задать поведение сервиса с помощью необходимого флага поведения:
- Флаг START_STICKY означает, что вашу службу нужно перезапустить если что-то пошло не так. Однако в этом случае любые данные переданные службе будут утеряны, потому что система перезапуская ее передаст null на вход метода
startCommand()
. - Флаг START_NOT_STICKY означает, что в случае проблем система не перезапустит вашу службу. То есть, такой сервис нужно перезапускать вручную.
Задавать флаг нужно в методе onStartCommand()
:
1 2 3 4 5 6 7 8 |
@Override public int onStartCommand(Intent intent, int flags, int startId) { // вызываем здесь метод, который будет работать в фоне doStuff(startId); // устанавливаем флаг START_STICKY или START_NOT_STICKY return START_STICKY; } |
Вторым типом непривязанной службы является IntentService
.
IntentService в Android
Служба IntentService запускается рабочим потоком, а не основным потоком или потоком пользовательского интерфейса. Из IntentService нельзя обновлять пользовательский интерфейс, потому что он всегда работает в фоне. Чаще всего он используется для длительных операций, например, загрузки файлов с сервера.
IntentService может быть запущен из Activity или BroadcastReceiver. При запуске всегда происходит цепочка вызовов метода жизненного цикла:
onCreate()
-> onHandleIntent()
-> onDestroy()
Служба IntentService создается после вызова метода onCreate()
, а выполнение задач в фоне происходит в методе onHandleIntent()
. Уничтожение службы происходит вызовом методома onDestroy()
. Также IntentService сам автоматически останавливается после завершения задачи.
Интересной особенностью IntentService является то, что если у вас запущено несколько задач, то он будет обрабатывать задачи по очереди: одну один раз, а другие задачи будут ждать в очереди. Как только текущая задача будет завершена, IntentService приступает к выполнению следующей задачи из очереди.
Простейший пример IntentService:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import android.app.IntentService; import android.content.Intent; public class SimpleIntentService extends IntentService { public TestIntentService(String s){ super(s); } @Override protected void onHandleIntent(Intent intent) { // выполнение какой-то задачи в фоне doStuff(); } private void doStuff() {} @Override public void onDestroy() { super.onDestroy(); } } |
Bound Service — привязанная служба в Android
Вторым типом службы в Android является Bound service.
Bound service — служба, которая позволяет привязывать службу к клиенту. Эта служба начнет работать, после вызова метода bindService()
. Также она предоставляет возможность работы с ней в том же процессе или же в другом процессе. Для другого процесса привязывание происходит с помощью IPC (межпроцессной связи).
Межпроцессная связь обеспечивает запуск службы в отдельном потоке или нескольких потоках. Для однопоточного процесса будет работать Messenger (о нем мы поговорим в следующих статьях), а для нескольких потоков будет использован AIDL (Язык определения интерфейса Android), например, воспроизведение аудио, видео и т.п.
Bound Service может привязываться либо с Activity, либо с BroadcastReceiver. Что нам нужно сделать, так это просто вызвать метод bindService()
. Для привязки службы необходимо передать экземпляр IBinder
в метод onBind()
жизненного цикла. Когда задача завершается, нужно вызывать метод unBind()
клиента.
IBinder — базовый интерфейс просто и легковесного механизма обработки удаленных процедур.
Давайте посмотрим, как это работает в приложении. У нас вот такой Bound service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.support.annotation.Nullable; public class SimpleService extends Service{ private final IBinder mBinder = new LocalService(); @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } public class LocalService extends Binder{ SimpleService getService(){ return SimpleService.this; } } public String getStubMessage(){ return "Я лишь для демонстрации возможностей"; } public String getAnotherStub(){ return "И я тоже"; } } |
Давайте создадим Activity для привязки этой службы к клиенту.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { SimpleService mService; private boolean isBound; // Здесь необходимо создать соединение с сервисом ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // используем mService экземпляр класса для доступа к публичному LocalService SimpleService.LocalService localService = (SimpleService.LocalService) service; mService = localService.getService(); isBound = true; } @Override public void onServiceDisconnected(ComponentName name) { isBound = false; } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button buttonStartService = (Button) findViewById(R.id.btn_start_service); buttonStartService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getServiceMessage(); } }); } private void getServiceMessage() { // здесь вызываем методы сервиса String stubMessage = mService.getStubMessage(); String anotherMessage = mService.getAnotherStub(); Toast.makeText(MainActivity.this, stubMessage + " " + anotherMessage, Toast.LENGTH_LONG).show(); } @Override protected void onStart() { super.onStart(); Intent intent = new Intent(this, SimpleService.class); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); if (isBound) { unbindService(serviceConnection); } } } |
Для создания соединения между клиентом и сервером используем экземпляр специального ServiceConnection. Мы можем привязать службу в методе onStart()
и отвязать unbindService()
в методе onStop()
. Вот и все.
В следующей статье мы рассмотрим IPC Remote connection для привязки компонентов Service. Следите за обновлениями в соц. сетях и подписывайтесь на новые статьи.
Полезная статья, спасибо!