How to Have Android Service Communicate With Activity

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.

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

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.

Communicate to a service from an activity in android

Use BroadcastReceivers.
In the activity declare the Broadcastreceiver:

public static final String ACTION_MY_ACTION = "com.example.test.MY_ACTION";

private BroadcastReceiver my_broadcast_receiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
//extract the data from intent here
String data = intent.getStringExtra("my_key");
}
}


@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);

IntentFilter filter = new IntentFilter(ACTION_MY_ACTION);

//we register the receiver
registerReceiver(my_broadcast_receiver, filter);
}

@Override
protected void onDestroy()
{
super.onDestroy();
unregisterReceiver(my_broadcast_receiver);
}

In the service whenever you want to send data to the activity:

Intent i = new Intent(MyActivity.ACTION_MY_ACTION);
i.putExtra("my_key", "data");
sendBroadcast(i);

EDIT:

If its a concern that other apps can intercept the broadcast then use LocalBroadcastManager, and send a local broadcast, this way the data you are broadcasting won't leave your app, so don't need to worry about leaking private data.



Related Topics



Leave a reply



Submit