Example: Communication Between Activity and Service Using Messaging

Communication between Activity and Service

I have implemented communication between Activity and Service using Bind and Callbacks interface.

For sending data to the service I used Binder which retruns the Service instace to the Activity, and then the Activity can access public methods in the Service.

To send data back to the Activity from the Service, I used Callbacks interface like you are using when you want to communicate between Fragment and Activity.

Here is some code samples for each:
The following example shows Activity and Service bidirectional relationship:
The Activity has 2 buttons:
The first button will start and stop the service.
The second button will start a timer which runs in the service.

The service will update the Activity through callback with the timer progress.

My Activity:

    //Activity implements the Callbacks interface which defined in the Service  
public class MainActivity extends ActionBarActivity implements MyService.Callbacks{

ToggleButton toggleButton;
ToggleButton tbStartTask;
TextView tvServiceState;
TextView tvServiceOutput;
Intent serviceIntent;
MyService myService;
int seconds;
int minutes;
int hours;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
serviceIntent = new Intent(MainActivity.this, MyService.class);
setViewsWidgets();
}

private void setViewsWidgets() {
toggleButton = (ToggleButton)findViewById(R.id.toggleButton);
toggleButton.setOnClickListener(btListener);
tbStartTask = (ToggleButton)findViewById(R.id.tbStartServiceTask);
tbStartTask.setOnClickListener(btListener);
tvServiceState = (TextView)findViewById(R.id.tvServiceState);
tvServiceOutput = (TextView)findViewById(R.id.tvServiceOutput);

}

private ServiceConnection mConnection = new ServiceConnection() {

@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
Toast.makeText(MainActivity.this, "onServiceConnected called", Toast.LENGTH_SHORT).show();
// We've binded to LocalService, cast the IBinder and get LocalService instance
MyService.LocalBinder binder = (MyService.LocalBinder) service;
myService = binder.getServiceInstance(); //Get instance of your service!
myService.registerClient(MainActivity.this); //Activity register in the service as client for callabcks!
tvServiceState.setText("Connected to service...");
tbStartTask.setEnabled(true);
}

@Override
public void onServiceDisconnected(ComponentName arg0) {
Toast.makeText(MainActivity.this, "onServiceDisconnected called", Toast.LENGTH_SHORT).show();
tvServiceState.setText("Service disconnected");
tbStartTask.setEnabled(false);
}
};

View.OnClickListener btListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if(v == toggleButton){
if(toggleButton.isChecked()){
startService(serviceIntent); //Starting the service
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); //Binding to the service!
Toast.makeText(MainActivity.this, "Button checked", Toast.LENGTH_SHORT).show();
}else{
unbindService(mConnection);
stopService(serviceIntent);
Toast.makeText(MainActivity.this, "Button unchecked", Toast.LENGTH_SHORT).show();
tvServiceState.setText("Service disconnected");
tbStartTask.setEnabled(false);
}
}

if(v == tbStartTask){
if(tbStartTask.isChecked()){
myService.startCounter();
}else{
myService.stopCounter();
}
}
}
};

@Override
public void updateClient(long millis) {
seconds = (int) (millis / 1000) % 60 ;
minutes = (int) ((millis / (1000*60)) % 60);
hours = (int) ((millis / (1000*60*60)) % 24);

tvServiceOutput.setText((hours>0 ? String.format("%d:", hours) : "") + ((this.minutes<10 && this.hours > 0)? "0" + String.format("%d:", minutes) : String.format("%d:", minutes)) + (this.seconds<10 ? "0" + this.seconds: this.seconds));
}
}

And here is the service:

 public class MyService extends Service {
NotificationManager notificationManager;
NotificationCompat.Builder mBuilder;
Callbacks activity;
private long startTime = 0;
private long millis = 0;
private final IBinder mBinder = new LocalBinder();
Handler handler = new Handler();
Runnable serviceRunnable = new Runnable() {
@Override
public void run() {
millis = System.currentTimeMillis() - startTime;
activity.updateClient(millis); //Update Activity (client) by the implementd callback
handler.postDelayed(this, 1000);
}
};


@Override
public int onStartCommand(Intent intent, int flags, int startId) {

//Do what you need in onStartCommand when service has been started
return START_NOT_STICKY;
}

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
//returns the instance of the service
public class LocalBinder extends Binder{
public MyService getServiceInstance(){
return MyService.this;
}
}

//Here Activity register to the service as Callbacks client
public void registerClient(Activity activity){
this.activity = (Callbacks)activity;
}

public void startCounter(){
startTime = System.currentTimeMillis();
handler.postDelayed(serviceRunnable, 0);
Toast.makeText(getApplicationContext(), "Counter started", Toast.LENGTH_SHORT).show();
}

public void stopCounter(){
handler.removeCallbacks(serviceRunnable);
}


//callbacks interface for communication with service clients!
public interface Callbacks{
public void updateClient(long data);
}
}

How to have Android Service communicate with Activity

There are three obvious ways to communicate with services:

  1. Using Intents
  2. Using AIDL
  3. Using the service object itself (as singleton)

In your case, I'd go with option 3. Make a static reference to the service it self and populate it in onCreate():

void onCreate(Intent i) {
sInstance = this;
}

Make a static function MyService getInstance(), which returns the static sInstance.

Then in Activity.onCreate() you start the service, asynchronously wait until the service is actually started (you could have your service notify your app it's ready by sending an intent to the activity.) and get its instance. When you have the instance, register your service listener object to you service and you are set. NOTE: when editing Views inside the Activity you should modify them in the UI thread, the service will probably run its own Thread, so you need to call Activity.runOnUiThread().

The last thing you need to do is to remove the reference to you listener object in Activity.onPause(), otherwise an instance of your activity context will leak, not good.

NOTE: This method is only useful when your application/Activity/task is the only process that will access your service. If this is not the case you have to use option 1. or 2.

Send message from Activity to Service and vice versa

I found a very helpful page with great examples:

Effective communication between Service and Activity

There are three possible ways:

  • Broadcasts and BroadcastReceivers

    The simplest solution uses Broadcasts and BroadcastReceivers. However, this method has the following disadvantages:

    The message sender does not know who will receive the data (it broadcasts the message).
    Broadcasts consume a lot of bandwidth and are not suited for high traffic.

  • Binder

    For Activities and Services used in one application the “Binder” solution is recommended. After binding with the service, the Binder will allow the activity (or other class bound to the service) to call any public method of the service.

  • Messenger

    The recommended method of communication between Activities and Services for different applications’ Inter-Process Communication (IPC) is by using Messengers. In this case, the service defines a handler for different incoming messages. This handler is then used in Messenger for transferring data via messages.

    To get bi-directional communication, the activity also has to register a Messenger and Handler. This Messenger is passed to the service in one of messages. Without this the service wouldn’t know to who it should respond.

For my project I chose the last one.

Communication between activity/service and widget provider?

The best thing you can do in appWidgetProvider is to send messages to your service/activity via intents. You can use the LocalBroadcastManager in support library v4 to broadcast messages internally to your app only.
Example:

@Override
public void onCreate(Bundle savedInstanceState) {

...

// Register to receive messages.
// We are registering an observer (mMessageReceiver) to receive Intents
// with actions named "custom-event-name".
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter("custom-event-name"));
}

// Our handler for received Intents. This will be called whenever an Intent
// with an action named "custom-event-name" is broadcasted.
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Get extra data included in the Intent
String message = intent.getStringExtra("message");
Log.d("receiver", "Got message: " + message);
}
};

@Override
protected void onDestroy() {
// Unregister since the activity is about to be closed.
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
super.onDestroy();
}

And in your widget provider:

private void sendMessage() {
Log.d("sender", "Broadcasting message");
Intent intent = new Intent("custom-event-name");
// You can also include some extra data.
intent.putExtra("message", "This is my message!");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

Communication between Android Services and Activities

I think your approach is ok with BroadCastReceiver. However, BroadCastReceiver should be used for a global purpose (like communicating between 2 applications). If you intend to use BroadCastReceiver for your app only, I prefer using LocalBroadcastManager instead. Using LocalBroadcastManager is faster and more security when it can be caught only by your app.

There's another way to communicate between your activitys and your services is using EventBus. It will be much easier than using BroadCastReceiver (especially in passing data between them).

Update: About your update question:

  1. is it a good approach to just write my 30 seconds updates to the local db and allow the activities to update themselves every few seconds simply reading from the local db? --> Of course NO. You should let your activities update themselves when they need. When you update your local db, you should know that is there any changes or not. If there is any change, use LocalBroadcastmanager to notify your activity to update.
  2. Would that affect the performance too much? --> Yes, that do. The db connection will take time to execute and it will block your UI in some cases. in that case, you should use a thread with ExecutorService for each execute (insert, update...). One more thing to consider is updating that frequently will drain your phone battery very, very fast.

Android: Communication between activity and service

We can use BroadcastReceiver or ResultReceiver to receive the result from Service. In this time, I want to guide you about using ResultReceiver:

First, create a class to receive the result inside your activity and extend ResultReceiver:

private class MyReceiver extends ResultReceiver {
public MyReceiver(Handler handler) {
super(handler);
}

@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
/*
* We can get the type of data passed from Service to Activity:
* int --> getInt("key")
* String --> getString("key")
* boolean --> getBoolean("key")
* /
if (resultCode == MyService.RUNNING) {
String str = resultData.getString("dataStringKey"); // get information within the key, i.e. "dataString"
myTextView.setText(str); // set the text to get the data received
}

if (resultCode == MyService.SERVICE_STOPPED) {
// do something, such as get data received with the key
}
}
}

In your Service:

public static final int RUNNING = 0;
public static final int SERVICE_DESTROYED = 1;
private ResultReceiver receiver;
private Bundle resultData;
private int status;

@Override
public void onCreate() {
super.onCreate();
resultData = new Bundle();
status = RUNNING;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
receiver = intent.getParcelableExtra("receiver");
String dataFromActivity = intent.getStringExtra("text");
/*
* send result/information to activity, also we can put:
* int --> putInt("key", value)
* boolean --> putBoolean("key", value)
* String --> putString("key", "My string.")
* /
resultData.putString("dataStringKey", "Hi, this is a String data.");
receiver.send(status, resultData);

return Service.START_NOT_STICKY;
}

@Override
public void onDestroy() {
status = SERVICE_DESTROYED;
receiver.send(status, resultData);
super.onDestroy();
}

When you trigger the Service:

Intent intent = new Intent(MyActivity.this, MyService.class);
intent.putExtra("text", "Text to be passed to Service.");
intent.putExtra("receiver", new MyReceiver(new Handler()));
this.startService(intent);


Related Topics



Leave a reply



Submit