Урок 97. Service. Биндинг. ServiceConnection
В этом уроке:
- используем биндинг для подключения к сервису
В прошлых уроках мы общались с сервисом асинхронно. Т.е. мы отправляли запрос через startService, а ответ нам приходил когда-нибудь потом посредством PendingIntent или BroadcastReceiver.
Но есть и синхронный способ взаимодействия с сервисом. Он достигается с помощью биндинга (binding, я также буду использовать слово «подключение»). Мы подключаемся к сервису и можем взаимодействовать с ним путем обычного вызова методов с передачей данных и получением результатов. В этом уроке передавать данные не будем. Пока что разберемся, как подключаться и отключаться.
Как вы помните, для запуска и остановки сервиса мы использовали методы startService и stopService. Для биндинга используются методы bindService и unbindService.
Создадим два Application. В одном будет приложение, в другом сервис.
Создадим первый проект:
Project name: P0971_ServiceBindClient
Build Target: Android 2.3.3
Application name: ServiceBindClient
Package name: ru.startandroid.develop.p0971servicebindclient
Create Activity: MainActivity
Добавим в strings.xml строки:
<string name="start">Start</string>
<string name="stop">Stop</string>
<string name="bind">Bind</string>
<string name="unbind">Unbind</string>
Экран main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickStart"
android:text="@string/start">
</Button>
<Button
android:id="@+id/btnStop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickStop"
android:text="@string/stop">
</Button>
<Button
android:id="@+id/btnBind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickBind"
android:text="@string/bind">
</Button>
<Button
android:id="@+id/btnUnBind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickUnBind"
android:text="@string/unbind">
</Button>
</LinearLayout>
4 кнопки: для запуска, остановки и биндинга сервиса
MainActivity.java:
package ru.startandroid.develop.p0971servicebindclient;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
public class MainActivity extends Activity {
final String LOG_TAG = "myLogs";
boolean bound = false;
ServiceConnection sConn;
Intent intent;
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
intent = new Intent("ru.startandroid.develop.p0972servicebindserver.MyService");
sConn = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.d(LOG_TAG, "MainActivity onServiceConnected");
bound = true;
}
public void onServiceDisconnected(ComponentName name) {
Log.d(LOG_TAG, "MainActivity onServiceDisconnected");
bound = false;
}
};
}
public void onClickStart(View v) {
startService(intent);
}
public void onClickStop(View v) {
stopService(intent);
}
public void onClickBind(View v) {
bindService(intent, sConn, BIND_AUTO_CREATE);
}
public void onClickUnBind(View v) {
if (!bound) return;
unbindService(sConn);
bound = false;
}
protected void onDestroy() {
super.onDestroy();
onClickUnBind(null);
}
}
В onCreate мы создаем Intent, который позволит нам добраться до сервиса.
Объект ServiceConnection позволит нам определить, когда мы подключились к сервису и когда связь с сервисом потеряна (если сервис был убит системой при нехватке памяти). При подключении к сервису сработает метод onServiceConnected. На вход он получает имя компонента-сервиса и объект Binder для взаимодействия с сервисом. В этом уроке мы этим Binder пока не пользуемся. При потере связи сработает метод onServiceDisconnected.
Переменную bound мы используем для того, чтобы знать – подключены мы в данный момент к сервису или нет. Соответственно при подключении мы переводим ее в true, а при потере связи в false.
Далее идут обработчики кнопок. В onClickStart мы стартуем сервис, в onClickStop – останавливаем.
В onClickBind – соединяемся с сервисом, используя метод bindService. На вход передаем Intent, ServiceConnection и флаг BIND_AUTO_CREATE, означающий, что, если сервис, к которому мы пытаемся подключиться, не работает, то он будет запущен.
В onClickUnBind с помощью bound проверяем, что соединение уже установлено. Далее отсоединяемся методом unbindService, на вход передавая ему ServiceConnection. И в bound пишем false, т.к. мы сами разорвали соединение. Метод onServiceDisconnected не сработает при явном отключении.
Создадим второй проект, без Activity:
Project name: P0972_ServiceBindServer
Build Target: Android 2.3.3
Application name: ServiceBindServer
Package name: ru.startandroid.develop.p0972servicebindserver
Создаем сервис MyService.java:
package ru.startandroid.develop.p0972servicebindserver;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class MyService extends Service {
final String LOG_TAG = "myLogs";
public void onCreate() {
super.onCreate();
Log.d(LOG_TAG, "MyService onCreate");
}
public IBinder onBind(Intent intent) {
Log.d(LOG_TAG, "MyService onBind");
return new Binder();
}
public void onRebind(Intent intent) {
super.onRebind(intent);
Log.d(LOG_TAG, "MyService onRebind");
}
public boolean onUnbind(Intent intent) {
Log.d(LOG_TAG, "MyService onUnbind");
return super.onUnbind(intent);
}
public void onDestroy() {
super.onDestroy();
Log.d(LOG_TAG, "MyService onDestroy");
}
}
Методы onCreate и onDestroy нам знакомы – они вызываются при создании и уничтожении сервиса. А onBind, onRebind и onUnbind используются при биндинге и мы далее будем смотреть как именно. Метод onStartCommand не используем.
В методе onBind возвращаем пока объект-заглушку Binder. В этом уроке он не будет использован.
Прописываем сервис в манифесте и настраиваем для него IntentFilter с Action = ru.startandroid.develop.p0972servicebindserver.MyService.
Все сохраняем, инсталлим сервис и запускаем приложение.
На самом экране ничего происходить не будет. Все внимание на логи.
Попробуем подключиться к неработающему сервису. Жмем Bind. В логах:
MyService onCreate
MyService onBind
MainActivity onServiceConnected
Сервис создался и сработал его метод onBind. Также сработал метод onServiceConnected в ServiceConnection, т.е. Activity теперь знает, что подключение к сервису установлено.
Попробуем отключиться. Жмем Undind.
MyService onUnbind
MyService onDestroy
Сработал метод Unbind в сервисе и сервис закрылся. Т.е. если мы биндингом запустили сервис, он будет жить, пока живет соединение. Как только мы отключаемся, сервис останавливается. onServiceDisconnected не сработал, т.к. мы сами отключились.
Теперь попробуем соединиться с сервисом и убить его. Посмотрим, что станет с соединением. Жмем Bind.
MyService onCreate
MyService onBind
MainActivity onServiceConnected
Подключились.
Теперь в процессах убиваем процесс сервиса
Смотрим лог:
MainActivity onServiceDisconnected
Сработал метод onServiceDisconnected объекта ServiceConnection. Тем самым Activity уведомлено, что соединение разорвано.
Теперь немного подождем (у меня это заняло 5 секунд) и в логах появляются строки:
MyService onCreate
MyService onBind
MainActivity onServiceConnected
Сервис запустился и соединение восстановилось. Очень удобно.
Жмем Unbind и отключаемся.
Попробуем снова подключиться к незапущенному сервису, но уже без флага BIND_AUTO_CREATE. Перепишем onClickBind в MainActivity.java:
public void onClickBind(View v) {
bindService(intent, sConn, 0);
}
Вместо флага BIND_AUTO_CREATE мы написали 0.
Сохраняем, запускаем. Жмем Bind. В логах ничего. Мы убрали флаг, сервис сам не создается при подключении.
Давайте запустим его методом startService. Жмем Start. В логах:
MyService onCreate
MyService onBind
MainActivity onServiceConnected
Сервис создался и приложение подключилось к нему. Т.е. попыткой биндинга мы оставили некую «заявку» на подключение, и когда сервис был запущен методом startService, он эту заявку увидел и принял подключение.
Жмем UnBind.
MyService onUnbind
Отключились от сервиса. Но сервис продолжает жить, потому что он был запущен не биндингом, а методом startService. А там уже свои правила закрытия сервиса. Это мы проходили в прошлых уроках.
Жмем Stop.
MyService onDestroy
Сервис завершил работу.
Попробуем запустить сервис методом startService и, пока он работает, несколько раз подключимся и отключимся. Жмем Start.
MyService onCreate
Сервис запущен.
Подключаемся и отключаемся, т.е. жмем Bind
MyService onBind
MainActivity onServiceConnected
а затем Unbind.
MyService onUnbind
Сработали методы onBind и onUnbind в сервисе, и onServiceConnected в ServiceConnection.
Еще раз подключаемся и отключаемся - жмем Bind, а затем Unbind
MainActivity onServiceConnected
При повторном подключении к сервису методы onBind и onUnbind не сработали. Только onServiceConnected.
И далее, сколько бы мы не подключались, так и будет.
Остановим сервис – нажмем Stop.
Это поведение можно скорректировать. Для этого необходимо возвращать true в методе onUnbind. Сейчас мы там вызываем метод супер-класса, а он возвращает false.
Перепишем метод Unbind в MyService.java:
public boolean onUnbind(Intent intent) {
Log.d(LOG_TAG, "MyService onUnbind");
return true;
}
Сохраним и инсталлим сервис. Жмем Start, а затем жмем поочередно Bind и Unbind, т.е. подключаемся и отключаемся. Смотрим логи:
MyService onCreate
MyService onBind
MainActivity onServiceConnected
MyService onUnbind
Сервис создан, подключились и отключились. Продолжаем подключаться и отключаться.
MyService onRebind
MainActivity onServiceConnected
MyService onUnbind
MyService onRebind
MainActivity onServiceConnected
MyService onUnbind
Последующие подключения и отключения сопровождаются вызовами методов onRebind и onUnbind. Таким образом, у нас есть возможность обработать в сервисе каждое повторное подключение/отключение.
Вот примерно такой Lifecycle имеет биндинг сервиса. Разумеется, я рассмотрел не все возможные комбинации запуска методов startService, stopService, bindService и unbindService. Оставляю это вам, если есть интерес. Я рассмотрел только типичные случаи.
Также я не рассматривал возможность одновременного подключения нескольких приложений или сервисов к одному сервису. А такая возможность существует. В этом случае, если сервис запущен биндингом, то он будет жить, пока не отключатся все подключившиеся.
Насколько я понял по хелпу, не рекомендуется оставлять незавершенные подключения к сервисам по окончании работы приложения. В хелповских примерах подключение к сервису производится в методе onStart, а отключение - в onStop. Но, разумеется, если вам надо, чтобы подключение оставалось при временном уходе Activity в background, то используйте onCreate и onDestroy. Также есть четкая рекомендация от гугла - не использовать для этих целей onResume и onPause.
На следующем уроке:
- обмен данными в биндинге