Коммуникация между Activity и Service

в 19:25, , рубрики: Песочница, Разработка под android

Как-то возникла у меня задача передавать данные из сервиса в активити. Начались поиски решения в стандартном 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

Источник


  1. Роман:

    Отличная статья, очень помогла, но возник затуп, строку не получается передать из активити в сервис(

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js