В этом руководстве мы рассмотрим компонент Service
и его суперкласс, IntentService
. Вы узнаете, когда и как использовать этот компонент для создания великолепных параллельных решений для длительных фоновых операций. Мы также кратко рассмотрим IPC (межпроцессное взаимодействие), чтобы узнать, как взаимодействовать со службами, работающими в разных процессах.
Чтобы следовать этому руководству, вам понадобится некоторое понимание параллелизма на Android. Если вы не знаете об этом много, вы можете сначала прочитать некоторые другие наши статьи на эту тему.
-
Android SDKAndroid с нуля: фоновые операции
-
AndroidПонимание значений AsyncTask за 60 секунд
-
Android SDKПонимание параллелизма на Android с помощью HaMeR
-
Android SDKПрактический параллелизм на Android с HaMeR
1. Сервисный компонент
Компонент Service
является очень важной частью среды параллелизма Android. Он удовлетворяет необходимость выполнения длительной операции в приложении или предоставляет некоторые функции для других приложений. В этом руководстве мы сконцентрируемся исключительно на возможностях длительных задач Service
и на том, как использовать эту возможность для улучшения параллелизма.
Что такое сервис?
Service
— это простой компонент, который создается системой для выполнения длительной работы, которая не обязательно зависит от взаимодействия с пользователем. Он может быть независимым от жизненного цикла действия, а также может выполняться в совершенно ином процессе.
Прежде чем углубляться в обсуждение того, что представляет собой Service
, важно подчеркнуть, что, хотя сервисы обычно используются для длительных фоновых операций и для выполнения задач в различных процессах, Service
не представляет Thread
или процесс . Он будет работать только в фоновом потоке или в другом процессе, если ему явно предложено это сделать.
Service
имеет две основные функции:
- Возможность приложения сообщать системе о том, что она хочет делать в фоновом режиме.
- Средство для приложения, чтобы показать некоторые его функции другим приложениям.
Услуги и темы
Существует много путаницы по поводу сервисов и потоков. Когда Service
объявлена, она не содержит Thread
. Фактически, по умолчанию он запускается непосредственно в главном потоке, и любая работа, выполняемая над ним, может потенциально заморозить приложение. (Если это не IntentService
, подкласс Service
который уже поставляется с настроенным рабочим потоком.)
Итак, как сервисы предлагают решение для параллелизма? Ну, Service
по умолчанию не содержит потока, но его можно легко настроить для работы с собственным потоком или с пулом потоков. Мы увидим больше об этом ниже.
Несмотря на отсутствие встроенного потока, Service
является отличным решением проблем параллелизма в определенных ситуациях. Основными причинами выбора Service
среди других параллельных решений, таких как AsyncTask
или платформа HaMeR, являются:
-
Service
может быть независимым от жизненных циклов деятельности. -
Service
подходит для выполнения длительных операций. - Услуги не зависят от взаимодействия с пользователем.
- При работе в разных процессах Android может пытаться поддерживать работу служб, даже если в системе недостаточно ресурсов.
-
Service
может быть перезапущена, чтобы возобновить свою работу.
Типы услуг
Существует два типа Service
: запущенный и связанный.
Запущенный сервис запускается через Context.startService()
. Обычно он выполняет только одну операцию и будет работать бесконечно, пока операция не завершится, а затем отключится. Как правило, он не возвращает никакого результата в пользовательский интерфейс.
Связанный сервис запускается через Context.bindService()
, и он обеспечивает двустороннюю связь между клиентом и Service
. Он также может подключаться к нескольким клиентам. Он разрушается, когда к нему не подключен ни один клиент.
Чтобы выбрать между этими двумя типами, Service
должна реализовать несколько обратных вызовов: onStartCommand()
для запуска в качестве запущенной службы и onBind()
для запуска в качестве связанной службы. Service
может выбрать реализацию только одного из этих типов, но она также может принять оба одновременно без каких-либо проблем.
2. Внедрение услуг
Чтобы использовать службу, расширьте класс Service
и переопределите его методы обратного вызова в соответствии с типом Service
. Как упоминалось ранее, для запущенных служб должен быть реализован метод onStartCommand()
а для связанных служб — метод onBind()
. На самом деле, onBind()
Метод должен быть объявлен для любого типа сервиса, но он может возвращать ноль для запущенных сервисов.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
public class CustomService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Execute your operations
// Service wont be terminated automatically
return Service.START_NOT_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
// Creates a connection with a client
// using a interface implemented on IBinder
return null;
}
}
|
-
onStartCommand()
: запускаетсяContext.startService()
. Это обычно вызывается из деятельности. После вызова служба может работать бесконечно, и вы можете остановить ее, вызвавstopSelf()
илиstopService()
. -
onBind()
:onBind()
когда компонент хочет подключиться к сервису. Вызывается в системе с помощьюContext.bindService()
. Он возвращаетIBinder
который предоставляет интерфейс для связи с клиентом.
Жизненный цикл службы также важно учитывать. onCreate()
и onDestroy()
должны быть реализованы для инициализации и завершения любых ресурсов или операций службы.
Объявление услуги по манифесту
Компонент Service
должен быть объявлен в манифесте с элементом <service>
. В этом объявлении также возможно, но не обязательно, установить другой процесс для запуска Service
.
1
2
3
4
5
6
7
8
9
|
<manifest … >
…
<application … >
<service
android:name=».ExampleService»
android:process=»:my_process»/>
…
</application>
</manifest>
|
2.2. Работа с запущенными сервисами
Чтобы инициировать запущенный сервис, вы должны вызвать Context.startService()
. Intent
должно быть создано с Context
и Context
Service
. Любая соответствующая информация или данные также должны быть переданы в этом Intent
.
1
2
3
4
5
6
7
8
|
Intent serviceIntent = new Intent(this, CustomService.class);
// Pass data to be processed on the Service
Bundle data = new Bundle();
data.putInt(«OperationType», 99);
data.putString(«DownloadURL», «http://mydownloadurl.com»);
serviceIntent.putExtras(data);
// Starting the Service
startService(serviceIntent);
|
В вашем классе Service
метод, который вас должен беспокоить, это onStartCommand()
. Именно по этому методу вы должны вызывать любую операцию, которую хотите выполнить в запущенной службе. Вы обработаете Intent
для сбора информации, отправленной клиентом. startId
представляет собой уникальный идентификатор, автоматически создаваемый для этого конкретного запроса, а flags
также могут содержать дополнительную информацию о нем.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle data = intent.getExtras();
if (data != null) {
int operation = data.getInt(KEY_OPERATION);
// Check what operation to perform and send a msg
if ( operation == OP_DOWNLOAD){
// make a download
}
}
return START_STICKY;
}
|
onStartCommand()
возвращает константу int
которая управляет поведением:
-
Service.START_STICKY
: служба перезапускается, если она прекращается. -
Service.START_NOT_STICKY
: Сервис не перезапущен. -
Service.START_REDELIVER_INTENT
: Служба перезапускается после сбоя, и намерения и обработка будут доставлены.
Как упоминалось ранее, запущенная служба должна быть остановлена, в противном случае она будет работать бесконечно. Это может быть сделано либо Service
вызывающей stopSelf()
для себя, либо клиентом, вызывающим stopService()
для нее.
1
2
3
4
5
|
void someOperation() {
// do some long-running operation
// and stop the service when it is done
stopSelf();
}
|
Привязка к услугам
Компоненты могут создавать соединения со службами, устанавливая двустороннюю связь с ними. Клиент должен вызвать Context.bindService()
, передав в качестве параметров Intent
, интерфейс ServiceConnection
и flag
. Service
может быть привязан к нескольким клиентам, и он будет уничтожен, если к нему не подключены клиенты.
1
2
3
4
5
|
void bindWithService() {
Intent intent = new Intent(this, PlayerService.class);
// bind with Service
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
|
Возможна отправка объектов Message
в сервисы. Для этого вам необходимо создать Messenger
на стороне клиента в реализации интерфейса ServiceConnection.onServiceConnected
и использовать его для отправки объектов Message
в Service
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// use the IBinder received to create a Messenger
mServiceMessenger = new Messenger(service);
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
mServiceMessenger = null;
}
};
|
Также возможно передать ответный Messenger
чтобы клиент мог получать сообщения. Остерегайтесь, потому что клиент может больше не быть рядом, чтобы получить сообщение службы. Вы также можете использовать BroadcastReceiver
или любое другое широковещательное решение.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
private Handler mResponseHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// handle response from Service
}
};
Message msgReply = Message.obtain();
msgReply.replyTo = new Messenger(mResponseHandler);
try {
mServiceMessenger.send(msgReply);
} catch (RemoteException e) {
e.printStackTrace();
}
|
Важно отсоединиться от Сервиса, когда клиент уничтожается.
1
2
3
4
5
6
7
8
9
|
@Override
protected void onDestroy() {
super.onDestroy();
// disconnect from service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
|
На стороне Service
вы должны реализовать метод Service.onBind()
, предоставляющий IBinder
предоставленный из Messenger
. Это будет ретранслировать Handler
ответа для обработки объектов Message
полученных от клиента.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
IncomingHandler(PlayerService playerService) {
mPlayerService = new WeakReference<>(playerService);
}
@Override
public void handleMessage(Message msg) {
// handle messages
}
}
public IBinder onBind(Intent intent) {
// pass a Binder using the Messenger created
return mMessenger.getBinder();
}
final Messenger mMessenger = new Messenger(new IncomingHandler(this));
|
3 Параллельное использование сервисов
Наконец, пришло время поговорить о том, как решить проблемы параллелизма с помощью сервисов. Как упоминалось ранее, стандартная Service
не содержит никаких дополнительных потоков и по умолчанию будет работать в основном Thread
. Чтобы преодолеть эту проблему, вы должны добавить рабочий Thread
, пул потоков или запустить Service
в другом процессе. Вы также можете использовать подкласс Service
называемый IntentService
который уже содержит IntentService
.
Запуск службы в рабочем потоке
Чтобы Service
выполнялась в фоновом Thread
вы можете просто создать дополнительный Thread
и запустить его там. Однако Android предлагает нам лучшее решение. Один из способов извлечь максимальную выгоду из системы — внедрить инфраструктуру HaMeR внутри Service
, например, зациклив поток с очередью сообщений, которая может обрабатывать сообщения бесконечно.
Важно понимать, что эта реализация будет обрабатывать задачи последовательно. Если вам нужно получать и обрабатывать несколько задач одновременно, вы должны использовать пул потоков. Использование пулов потоков выходит за рамки данного руководства, и мы не будем говорить об этом сегодня.
Для использования HaMeR вы должны предоставить Сервису Looper
, Handler
и HandlerThread
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// Handler to receive messages from client
private final class ServiceHandler extends Handler {
ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// handle messages
// stopping Service using startId
stopSelf( msg.arg1 );
}
}
@Override
public void onCreate() {
HandlerThread thread = new HandlerThread(«ServiceThread»,
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
|
Если среда HaMeR вам незнакома, прочитайте наши учебники по параллельности HaMer для Android.
-
Android SDKПонимание параллелизма на Android с помощью HaMeR
-
Android SDKПрактический параллелизм на Android с HaMeR
ИнтентСервис
Если нет необходимости поддерживать Service
в течение длительного времени, вы можете использовать IntentService
, подкласс Service
который готов выполнять задачи в фоновых потоках. Внутренне IntentService
является Service
реализация которого очень похожа на предложенную выше.
Чтобы использовать этот класс, все, что вам нужно сделать, это расширить его и реализовать onHandleIntent()
, метод ловушки, который будет вызываться каждый раз, когда клиент вызывает startService()
в этой Service
. Важно помнить, что IntentService
остановится, как только его работа будет завершена.
01
02
03
04
05
06
07
08
09
10
11
|
public class MyIntentService extends IntentService {
public MyIntentService() {
super(«MyIntentService»);
}
@Override
protected void onHandleIntent(Intent intent) {
// handle Intents send by startService
}
}
|
IPC (межпроцессное взаимодействие)
Service
может работать в совершенно другом Process
независимо от всех задач, выполняемых в основном процессе. Процесс имеет собственное распределение памяти, группу потоков и приоритеты обработки. Этот подход может быть действительно полезным, когда вам нужно работать независимо от основного процесса.
Связь между различными процессами называется IPC (межпроцессное взаимодействие) . В Service
есть два основных способа сделать IPC: использование Messenger
или реализация интерфейса AIDL
.
Мы узнали, как отправлять и получать сообщения между службами. Все, что вам нужно сделать, — это создать Messenger
с IBinder
экземпляра IBinder
полученного в процессе подключения, и использовать его для отправки ответного Messenger
обратно в Service
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private Handler mResponseHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// handle response from Service
}
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// use the IBinder received to create a Messenger
mServiceMessenger = new Messenger(service);
Message msgReply = Message.obtain();
msgReply.replyTo = new Messenger(mResponseHandler);
try {
mServiceMessenger.send(msgReply);
} catch (RemoteException e) {
e.printStackTrace();
}
}
|
Интерфейс AIDL
является очень мощным решением, позволяющим осуществлять прямые вызовы методов Service
выполняющихся в разных процессах, и его целесообразно использовать, когда ваша Service
действительно сложна. Однако AIDL
сложен в реализации и используется редко, поэтому его использование не будет обсуждаться в этом руководстве.
4. Заключение
Услуги могут быть простыми или сложными. Это зависит от потребностей вашего приложения. Я попытался охватить как можно больше основ этого учебника, однако я сосредоточился только на использовании сервисов для целей параллелизма, и у этого компонента есть больше возможностей. Если вы хотите узнать больше, посмотрите документацию и руководства по Android .
До скорого!