Урок 98. Service. Локальный биндинг


В этом уроке:

- обмен данными в биндинге


Подключаться к сервису мы теперь умеем. В этом уроке попробуем обменяться с ним данными.

В сервисе метод 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 подключились к сервису и управляем его работой.


В начале урока я отметил, что все это будет работать, если приложение и сервис в одном процессе. Если же они в разных процессах, то используется немного более сложный вариант для обмена данными. Но это гораздо менее используемый случай, поэтому сейчас я про него не рассказываю. Но попозже об этом обязательно будет статья.

На следующем уроке:

- шлем уведомление из сервиса