Как-то возникла у меня задача передавать данные из сервиса в активити. Начались поиски решения в стандартном SDK, но так как времени не было, то сваял плохое решение в виде использования базы данных. Но вопрос был открыт и спустя некоторое время я разобрался с более верным способом, который есть в SDK — использование классов Message, Handler, Messenger.
Идея
Нам нужно передавать данные из активити в сервис и обратно. Как нам это сделать? Для решения нашей задачи у нас уже есть все необходимое. Все что нужно — это привязать сервис к ативити, используя bindService, передать нужные параметры и немного магии в виде использования классов Message. А магия заключается в том, чтобы использовать переменные экземпляра Message и в частности, replyTo. Данная переменная нужна нам, чтобы мы могли обратиться к экземпляру Messanger сервиса из активити и в сервисе к экземпляру Messanger-а активити. На самом деле, не так уж и просто. По крайней мере для моего не самого одаренного ума. Отчасти я просто улучшаю документацию, которая уже есть — Services Также, есть хороший пример на StackOverflow. В любом случае, надеюсь статья будет полезна хоть кому-то и я потрудился не зря.
Пример
В качестве примера реализуем сервис, который будем увеличивать и уменьшать значение счетчика и возвращать результат в активити, в TextView. Код макета опущу, ибо там две кнопки и текстовое поле — все просто.
Реализация
Приведу полностью код активити:
public class MainActivity extends Activity {
public static final String TAG = "TestService";
TestServiceConnection testServConn;
TextView testTxt;
final Messenger messenger = new Messenger(new IncomingHandler());
Messenger toServiceMessenger;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testTxt = (TextView)findViewById(R.id.test_txt);
bindService(new Intent(this, TestService.class),
(testServConn = new TestServiceConnection()),
Context.BIND_AUTO_CREATE);
}
@Override
public void onDestroy(){
super.onDestroy();
unbindService(testServConn);
}
public void countIncrClick(View button){
Message msg = Message.obtain(null, TestService.COUNT_PLUS);
msg.replyTo = messenger;
try {
toServiceMessenger.send(msg);
}
catch (RemoteException e) {
e.printStackTrace();
}
}
public void countDecrClick(View button){
Message msg = Message.obtain(null, TestService.COUNT_MINUS);
msg.replyTo = messenger;
try {
toServiceMessenger.send(msg);
}
catch (RemoteException e) {
e.printStackTrace();
}
}
private class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg){
switch (msg.what) {
case TestService.GET_COUNT:
Log.d(TAG, "(activity)...get count");
testTxt.setText(""+msg.arg1);
break;
}
}
}
private class TestServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
toServiceMessenger = new Messenger(service);
//отправляем начальное значение счетчика
Message msg = Message.obtain(null, TestService.SET_COUNT);
msg.replyTo = messenger;
msg.arg1 = 0; //наш счетчик
try {
toServiceMessenger.send(msg);
}
catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) { }
}
}
Поясню. При создании активити мы сразу привязываемся к сервису, реализуя интерфейс ServiceConnection и в нем оправляем сообщение сервису «установить значение счетчика», передавая ноль и создавая toServiceMessanger, передавая в конструктор интерфейс IBinder. Кстати, в сервисе обязательно нужно вернуть этот экемпляр, иначе будет NPE. С помощью этого класса мы и отправляем сообщения сервису. И вот она магия — в переменную replyTo мы сохраняем наш другой экземпляр Messenger — тот который получает ответ от сервера и именно через него и будет осуществляться связь с активити.
Для получения сообщения от сервиса используем свой Handler и просто ищем нужные нам переменные и делаем по ним действия. По кликам на кнопки(методы countIncrClick, countDecrClick) отправляем запросы к сервису, указывая нужное действие в переменной msg.what.
Далее, полный код сервиса:
package com.example.servicetest;
import android.app.Service;
import android.content.*;
import android.os.*;
import android.os.Process;
import android.util.Log;
public class TestService extends Service {
public static final int COUNT_PLUS = 1;
public static final int COUNT_MINUS = 2;
public static final int SET_COUNT = 0;
public static final int GET_COUNT = 3;
int count = 0;
IncomingHandler inHandler;
Messenger messanger;
Messenger toActivityMessenger;
@Override
public void onCreate(){
super.onCreate();
HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
inHandler = new IncomingHandler(thread.getLooper());
messanger = new Messenger(inHandler);
}
@Override
public IBinder onBind(Intent arg0) {
return messanger.getBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
//обработчик сообщений активити
private class IncomingHandler extends Handler {
public IncomingHandler(Looper looper){
super(looper);
}
@Override
public void handleMessage(Message msg){
//super.handleMessage(msg);
toActivityMessenger = msg.replyTo;
switch (msg.what) {
case SET_COUNT:
count = msg.arg1;
Log.d(MainActivity.TAG, "(service)...set count");
break;
case COUNT_PLUS:
count++;
Log.d(MainActivity.TAG, "(service)...count plus");
break;
case COUNT_MINUS:
Log.d(MainActivity.TAG, "(service)...count minus");
count--;
break;
}
//отправляем значение счетчика в активити
Message outMsg = Message.obtain(inHandler, GET_COUNT);
outMsg.arg1 = count;
outMsg.replyTo = messanger;
try {
if( toActivityMessenger != null )
toActivityMessenger.send(outMsg);
}
catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
Все по аналогии с логикой в активити. Даже не знаю, нужно ли что-то пояснять. Единственный момент — это то, что я сразу отправляю запрос обратно в активити в handleMessage, используя для этого волшебную переменную replyTo и вытаскивая выше нужный Messenger. И второй момент о котором я уже говорил — это:
@Override
public IBinder onBind(Intent arg0) {
return messanger.getBinder();
}
без которого все упадет. Именно данный экземпляр интерфейса и будет передан в ServiceConnection
Заключение
Вцелом все. Такой вот надуманный пример взаимодействия активити и сервиса. Мне кажется, довольно таки нетривиальное взаимодействие, хотя кому-то может показаться иначе.
Код проекта есть на Bitbucket
Вопросы, уточнения и прочее в личку. Могут быть неточности по поводу каких-либо аспектов, поэтому смело пишите и поправляйте.
Надеюсь, пост был полезен читателям.
Автор: Pyjamec
Отличная статья, очень помогла, но возник затуп, строку не получается передать из активити в сервис(