В этом уроке:
- обмен данными в биндинге
Подключаться к сервису мы теперь умеем. В этом уроке попробуем обменяться с ним данными.
В сервисе метод onBind возвращает объект, наследующий интерфейс IBinder. Проще всего использовать для этого объект Binder и расширить его необходимыми нам методами. Т.е. создаем свой класс MyBinder с предком Binder и рисуем в нем свои методы.
При биндинге, в методе onServiceConnected мы получаем объект Binder. Мы можем привести его к типу MyBinder (из сервиса) и вызывать его методы, а реализация будет срабатывать в сервисе, где мы описывали этот класс.
Как вы понимаете, это сработает только, если сервис и приложение выполняются в одном процессе. Поэтому такой биндинг называется локальным.
Нарисуем пример. У нас будет сервис, который с определенным интервалом будет что-то выводить в лог. Мы к нему подключимся, и будем повышать или понижать интервал и получать новое значение интервала обратно.
Создадим проект:
Project name: P0981_ServiceBindingLocal
Build Target: Android 2.3.3
Application name: ServiceBindingLocal
Package name: ru.startandroid.develop.p0981servicebindinglocal
Create Activity: MainActivity
Добавим в strings.xml строки:
<string name="start">Start</string> <string name="up">Up</string> <string name="down">Down</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"> <TextView android:id="@+id/tvInterval" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text=""> </TextView> <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/btnUp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onClickUp" android:text="@string/up"> </Button> <Button android:id="@+id/btnDown" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onClickDown" android:text="@string/down"> </Button> </LinearLayout>
Кнопки для запуска сервиса, повышения интервала и понижения интервала.
Создаем сервис MyService.java:
package ru.startandroid.develop.p0981servicebindinglocal; import java.util.Timer; import java.util.TimerTask; 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"; MyBinder binder = new MyBinder(); Timer timer; TimerTask tTask; long interval = 1000; public void onCreate() { super.onCreate(); Log.d(LOG_TAG, "MyService onCreate"); timer = new Timer(); schedule(); } void schedule() { if (tTask != null) tTask.cancel(); if (interval > 0) { tTask = new TimerTask() { public void run() { Log.d(LOG_TAG, "run"); } }; timer.schedule(tTask, 1000, interval); } } long upInterval(long gap) { interval = interval + gap; schedule(); return interval; } long downInterval(long gap) { interval = interval - gap; if (interval < 0) interval = 0; schedule(); return interval; } public IBinder onBind(Intent arg0) { Log.d(LOG_TAG, "MyService onBind"); return binder; } class MyBinder extends Binder { MyService getService() { return MyService.this; } } }
Здесь мы используем таймер – Timer. Он позволяет повторять какое-либо действие через заданный промежуток времени. Кратко распишу принцип действия. TimerTask – это задача, которую Timer будет периодически выполнять. В методе run – кодим действия этой задачи. И далее для объекта Timer вызываем метод schedule, в который передаем задачу TimerTask, время через которое начнется выполнение, и период повтора. Чтобы отменить выполнение задачи, необходимо вызвать метод cancel для TimerTask. Отмененную задачу нельзя больше запланировать, и если снова надо ее включить – необходимо создать новый экземпляр TimerTask и скормить его таймеру.
Итак, в методе onCreate мы создаем таймер и выполняем метод schedule, в котором стартует задача.
Метод schedule проверяет, что задача уже создана и отменяет ее. Далее планирует новую, с отложенным на 1000 мс запуском и периодом = interval. Т.е. можно сказать, что этот метод перезапускает задачу с использованием текущего интервала повтора (interval), а если задача еще не создана, то создает ее. Сама задача просто выводит в лог текст run. Если interval = 0, то ничего не делаем.
Метод upInterval получает на вход значение, увеличивает interval на это значение и перезапускает задачу. Соответственно задача после этого будет повторяться реже.
Метод downInterval получает на вход значение, уменьшает interval на это значение (но так, чтоб не меньше 0) и перезапускает задачу. Соответственно задача после этого будет повторяться чаще.
onBind возвращает binder. Это объект класса MyBinder.
MyBinder расширяет стандартный Binder, мы добавляем в него один метод getService. Этот метод возвращает наш сервис MyService.
Т.е. в подключаемом Activity, в методе onServiceConnected мы получим объект, который идет на выход метода onBind. Далее преобразуем его к типу MyBinder, вызовем getService и вуаля - у нас в Activity будет ссылка на объект-сервис MyService.
Кодим MainActivity.java:
package ru.startandroid.develop.p0981servicebindinglocal; 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; import android.widget.TextView; public class MainActivity extends Activity { final String LOG_TAG = "myLogs"; boolean bound = false; ServiceConnection sConn; Intent intent; MyService myService; TextView tvInterval; long interval; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tvInterval = (TextView) findViewById(R.id.tvInterval); intent = new Intent(this, MyService.class); sConn = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder binder) { Log.d(LOG_TAG, "MainActivity onServiceConnected"); myService = ((MyService.MyBinder) binder).getService(); bound = true; } public void onServiceDisconnected(ComponentName name) { Log.d(LOG_TAG, "MainActivity onServiceDisconnected"); bound = false; } }; } @Override protected void onStart() { super.onStart(); bindService(intent, sConn, 0); } @Override protected void onStop() { super.onStop(); if (!bound) return; unbindService(sConn); bound = false; } public void onClickStart(View v) { startService(intent); } public void onClickUp(View v) { if (!bound) return; interval = myService.upInterval(500); tvInterval.setText("interval = " + interval); } public void onClickDown(View v) { if (!bound) return; interval = myService.downInterval(500); tvInterval.setText("interval = " + interval); } }
В onCreate создаем Intent для доступа к сервису и ServiceConnection. В методе onServiceConnected мы берем binder, преобразуем его к MyService.MyBinder, вызываем метод getService и получаем наш сервис MyService. Теперь мы можем выполнять методы сервиса. Нас интересуют методы увеличения и понижения интервала.
В методах onStart и onStop мы соответственно подключаемся к сервису и отключаемся.
В onClickStart запускаем сервис.
В onClickUp и onClickDown проверяем, что соединение с сервисом есть, и вызываем соответствующие методы сервиса для увеличения или понижения интервала. В качестве дельты изменения интервала передаем 500. В ответ эти методы передают нам новое значение интервала, и мы выводим его в TextView.
Все сохраним и запускаем.
Жмем Start – запускаем сервис. В логах:
MyService onCreate
MyService onBind
MainActivity onServiceConnected
Создается сервис, и мы к нему подключаемся. Биндинг был ранее вызван в onStart и, когда сервис стартанул, мы автоматически подключились. В прошлом уроке мы разбирали такую ситуацию, когда биндинг идет к незапущенному пока сервису.
run
run
run
…
run
Далее с интервалом в одну секунду в лог падает текст run.
Попробуем увеличить интервал. Жмем Up.
На экране появилось текущее значение интервала. И по времени записей в логах видим, что именно с этим интервалом срабатывает задача (вывод текста run в лог) .
Далее можно нажимать Up и Down и наблюдать, как меняется интервал выполнения задачи. Таким образом, мы из Activity подключились к сервису и управляем его работой.
В начале урока я отметил, что все это будет работать, если приложение и сервис в одном процессе. Если же они в разных процессах, то используется немного более сложный вариант для обмена данными. Но это гораздо менее используемый случай, поэтому сейчас я про него не рассказываю. Но попозже об этом обязательно будет статья.
На следующем уроке:
- шлем уведомление из сервиса
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня