Communication Between Activity and Service

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.

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

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 service and activity from other projects

In android we have several ways to share data between application, it's depend on - how many data do you want to share between applications, cyber issues, Battery consumption and timing issues:

1) You can use aidl in both app in order to share data, please be aware that you are not able to send objects, you should convert the data to parcelable or byte-array. pros - great solutions for short data.

2)SharedPreferences and use the same android:sharedUserId="android.uid.share" on both application - not recommended due a battery consumption and timing issue, great to share big data.

3)You can use client/server model and open a unix socket on bot applications - pros - reliable and can be secured, cons - short battery life.

4)Broadcast receiver - [not recommended] due a potentially timing issue.

5)using shared file at sdcard - not recommended.



Related Topics



Leave a reply



Submit