Communication between Activity and Service

Somehow I had a task to transfer data from the service to the activity. The search for a solution in the standard SDK began, but since there was no time, a bad solution was made in the form of using a database. But the question was open and after a while I figured out the more accurate way that the SDK has - using the Message, Handler, Messenger classes.

Idea


We need to transfer data from the activity to the service and vice versa. How do we do this? To solve our problem, we already have everything we need. All that is needed is to bind the service to the affinity, using bindService, pass the necessary parameters and a little magic in the form of using the Message classes. And the magic is to use Message instance variables and, in particular, replyTo. We need this variable so that we can access the instance of the Messanger service from the activity and, in the service, the instance of the Messanger activity. In fact, not so simple. At least for my not very gifted mind. In part, I am improving the documentation that already exists - Services. I improve it by adding a connection to the activity, transferring the data back and forth, which is not in the documentation. Also, there is a good example on StackOverflow. In any case, I hope the article will be useful to at least someone and I did not work in vain.

Example


As an example, we implement a service that will increase and decrease the counter value and return the result to the activity, in the TextView. I’ll omit the layout code, because there are two buttons and a text field - everything is simple.

Implementation


I will give the complete activation code:

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) {	}
    }
}


I will explain. When creating an activity, we immediately become attached to the service, implementing the ServiceConnection interface and send a message to the “set counter value” service, passing zero and creating toServiceMessanger, passing the IBinder interface to the constructor. By the way, you must return this instance in the service, otherwise there will be NPE. Using this class, we send messages to the service. And here it is magic - we save our other instance of Messenger into the replyTo variable - the one that receives the response from the server and it is through it that the connection with the activity will be carried out.

To receive a message from the service, we use our Handler and just look for the variables we need and do actions on them. By clicking on the buttons (methods countIncrClick, countDecrClick) we send requests to the service, indicating the desired action in the variable msg.what.

Next, the full service code:

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();
			}
		}
	}
}


All by analogy with the logic in activity. I don’t even know if something needs to be explained. The only thing is that I immediately send the request back to the activity in handleMessage, using the replyTo magic variable and pulling the desired Messenger above. And the second point that I have already mentioned is:

@Override
	public IBinder onBind(Intent arg0) {
		return messanger.getBinder();
	}


without which everything will fall. It is this instance of the interface that will be passed to the ServiceConnection

Conclusion


All in all. Such a far-fetched example of the interaction of activity and service. It seems to me quite a non-trivial interaction, although it might seem otherwise to someone.

The project code is on Bitbucket

Questions, clarifications, etc. in PM. There may be inaccuracies about some aspects, so feel free to write and correct.
I hope the post was useful to habrayuzery.

Also popular now: