64

I am trying to make my own MusicPlayer for android. Where i came to a problem is running some things in background. Main activity manages GUI and up to now all the songs are playing. I wanted to separate GUI and music playing classes. I want to put music managing part in Service and leave other things as they are now.

My problem is that i can't organize communication between Activity and Service as lot of communication is happening between them including moving objects in both directions. I tried many techniques that I searched here on Stack Overflow but every time I had problems. I need Service to be able to send objects to Activity and vice versa. When I add widget i also want it to be able to communicate with Service.

Any tips are appreciated, if you need source code place comment bellow but now in this transition it became chaotic.

Is there any more advanced tutorial on this than calling one method that returns random number from service? :P

EDIT: Possible solution is to use RoboGuice library and move objects with injection

Dejan
  • 3,046
  • 3
  • 28
  • 43

8 Answers8

85

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);
    }
}
Jitesh Dalsaniya
  • 1,917
  • 3
  • 20
  • 36
Moti Bartov
  • 3,454
  • 33
  • 42
  • 2
    I recommend using bindService to bind Service with Activity and then call the methods defined in service class. This makes more sense to me, because the Activity may be started or paused, but service is there to stay. In the above code, the "callback" variable (reference to the Activity) may fail if activity is not running. It will lead to NPE. Hope my point is clear. – amsurana Jul 07 '16 at 04:27
  • 5
    I wolud recommend in unBind method of service set Callbacks activity to null, so no memory leaks would be – Anton Kizema Sep 18 '16 at 19:46
  • 1
    Yes, well I believe that there could be some improvements to this solution and this is the basic pattern. – Moti Bartov Sep 19 '16 at 10:22
  • How about using aidl in android? This is another way to send back object from service. – neilQ5 Oct 06 '17 at 18:43
  • what to do if I need timer to be continued, even if app is destroyed and restarted – Balraj Allam May 18 '18 at 18:40
  • 1
    @MotiBartov you deserve a novel prize, thanks for the solution! :) – Carlos.V Nov 01 '18 at 21:52
63

Update: July 10 2016

IMO I think using BroadcastReceiver for custom events is better way as the Messengers mentioned don't handle activity recreation on device rotation as well as possible memory leaks.

You may create custom BroadCast Receiver for events in the activity, Then you may also use Messengers.

  1. In your Activity

    create a MessageHandler class as

    public static class MessageHandler extends Handler {
        @Override
        public void handleMessage(Message message) {
            int state = message.arg1;
            switch (state) {
            case HIDE:
                progressBar.setVisibility(View.GONE);
                break;
            case SHOW:
                progressBar.setVisibility(View.VISIBLE);
                break;
            }
        }
    }
    

    Now you can have it's instance as

    public static Handler messageHandler = new MessageHandler();
    

    Start your Service with this Handler object as an extra data as

    Intent startService = new Intent(context, SERVICE.class)
    startService.putExtra("MESSENGER", new Messenger(messageHandler));
    context.startService(startService);
    
  2. In your Service you receive this object from the intent and initialize the Messenger variable in Service as

    private Messenger messageHandler;
    Bundle extras = intent.getExtras();
    messageHandler = (Messenger) extras.get("MESSENGER");
    sendMessage(ProgressBarState.SHOW);
    

    And then write a method sendMessage to send messages to activity.

    public void sendMessage(ProgressBarState state) {
    Message message = Message.obtain();
    switch (state) {
        case SHOW :
            message.arg1 = Home.SHOW;
            break;
        case HIDE :
            message.arg1 = Home.HIDE;
            break;
    }
    try {
        messageHandler.send(message);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
    }
    

The sample code above shows and hides a ProgressBar in Activity as messages are received from Service.

Rachit Mishra
  • 6,101
  • 4
  • 30
  • 51
  • @DjDexter5GHz thankyou, but I will suggest you to the whole thing, In my case I have to hide/show a Layout and hide/show a ProgressBar and the whole concept works fine. You have many objects, I hope it works. – Rachit Mishra Dec 15 '13 at 13:57
  • i am trying to work it out with this code, will reply results... EDIT: It works for ints and i have to implement Parceable for objects but it works. i can take it from here. Thanks again! – Dejan Dec 15 '13 at 14:04
  • How can i make communication in reverse connection? Activity to Service? – Dejan Dec 15 '13 at 14:46
  • 1
    Let the service be an IntentService then you can easily send Intent to the Service to do something. – Rachit Mishra Dec 15 '13 at 14:52
  • Be careful about the leaks. As service holds reference to the activity (via the Messenger). So your activity won't be released until the server holds messageHandler. – stevo.mit Nov 14 '15 at 09:38
  • 5
    How is MessageHandler accessing the Activity's progressBar, as the class is static? – cd1 Apr 13 '16 at 20:02
  • 2
    What about rotating the device while the service is still running? I presume a new `MessageHandler` will be created then and the service's `MessageHandler` will no longer be able to communicate to the activity. Is the Handler something you can maybe save and restore in onSavedInstanceState()? – Micro Jul 02 '16 at 00:43
  • I suggest a better solution now will be use broadcast. where you can handle all scenarios. – Rachit Mishra Jul 02 '16 at 12:34
  • Thnaks @RachitMishra for the answer – Jinu Nov 14 '16 at 05:39
  • You **cannot** pass a `object` which is either not implementing `Parcelable` or `Serializable` from a component to the service and vice versa with `IntentService` approach. – Eido95 Sep 18 '17 at 16:28
15

Intents are good solution for communication between Activitiy and Service.

A fast solution for receive intents in your service is subclassing IntentService class. It handles asynchronous requests expressed as Intents using a queue and worker thread.

For communication from service to Activity you can broadcast the intent but instead of using normal sendBroadcast() from Context, a more efficent way is to use LocalBroadcastManager from support library.

Example service.

public class MyIntentService extends IntentService {
    private static final String ACTION_FOO = "com.myapp.action.FOO";
    private static final String EXTRA_PARAM_A = "com.myapp.extra.PARAM_A";

    public static final String BROADCAST_ACTION_BAZ = "com.myapp.broadcast_action.FOO";
    public static final String EXTRA_PARAM_B = "com.myapp.extra.PARAM_B";

    // called by activity to communicate to service
    public static void startActionFoo(Context context, String param1) {
        Intent intent = new Intent(context, MyIntentService.class);
        intent.setAction(ACTION_FOO);
        intent.putExtra(EXTRA_PARAM1, param1);
        context.startService(intent);
    }

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_FOO.equals(action)) {
                final String param1 = intent.getStringExtra(EXTRA_PARAM_A);
                // do something
            }
        }
    }

    // called to send data to Activity
    public static void broadcastActionBaz(String param) {
        Intent intent = new Intent(BROADCAST_ACTION_BAZ);
        intent.putExtra(EXTRA_PARAM_B, param);
        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
        bm.sendBroadcast(intent);
    }
}

Example Activity

public class MainActivity extends ActionBarActivity {

    // handler for received data from service
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(MyIntentService.BROADCAST_ACTION_BAZ)) {
                final String param = intent.getStringExtra(EXTRA_PARAM_B);
                // do something
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        IntentFilter filter = new IntentFilter();
        filter.addAction(MyIntentService.BROADCAST_ACTION_BAZ);
        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
        bm.registerReceiver(mBroadcastReceiver, filter);
    }

    @Override
    protected void onDestroy() {
        LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
        bm.unregisterReceiver(mBroadcastReceiver);
        super.onDestroy();
    }

    // send data to MyService
    protected void communicateToService(String parameter) {
        MyIntentService.startActionFoo(this, parameter);
    }
}
mpolci
  • 2,999
  • 1
  • 23
  • 16
5

I think there is a problem with the correct answer. I have not enough reputation to comment on it.

Right in the answer: Activity call bindService() to get pointer to Service is ok. Because service context is maintained when connection is maintained.

wrong in the answer: service pointer to Activity class to call back is bad way. Activity instance maybe not null during Activity context is being Release => exception here.

solution for the wrong in the answer: service send intent to Activity. and Activity receiver intent via BroadcastReceiver.

Note: in this case, Service and Activity in the same Process, you should use LocalBroadcastManager to send intent. It make performance and security better

HungNM2
  • 3,101
  • 1
  • 30
  • 21
4

This is a simple example of communication between activity and service

Activity

MyReceiver myReceiver; //my global var receiver
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.layourAwesomexD);
    registerReceiver();
}

//When the activity resume, the receiver is going to register...
@Override
protected void onResume() {
    super.onResume();
    checkStatusService(); // verficarStatusServicio(); <- name change
    registerReceiver();
}
//when the activity stop, the receiver is going to unregister...
@Override
protected void onStop() {
    unregisterReceiver(myReceiver); //unregister my receiver...
    super.onStop();
}
//function to register receiver :3
private void registerReceiver(){
    //Register BroadcastReceiver
    //to receive event from our service
    myReceiver = new MyReceiver();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(MyService.SENDMESAGGE);
    registerReceiver(myReceiver, intentFilter);
}

// class of receiver, the magic is here...
private class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context arg0, Intent arg1) {
        //verify if the extra var exist
        System.out.println(arg1.hasExtra("message")); // true or false
        //another example...
        System.out.println(arg1.getExtras().containsKey("message")); // true or false
        //if var exist only print or do some stuff
        if (arg1.hasExtra("message")) {
            //do what you want to
            System.out.println(arg1.getStringExtra("message"));
        }    
    }
}

public void checkStatusService(){
    if(MyService.serviceStatus!=null){
        if(MyService.serviceStatus == true){
            //do something
            //textview.text("Service is running");
        }else{
            //do something
            //textview.text("Service is not running");
        }
    }
}

Service

public class MyService extends Service {

final static String SENDMESAGGE = "passMessage";

public static Boolean serviceStatus = false;

@Override
public void onCreate() {
    super.onCreate();
    serviceStatus=true;
}

@Nullable
@Override
    public IBinder onBind(Intent intent) {return null;}

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //you service etc...             
        passMessageToActivity("hello my friend this an example of send a string...");
        return START_STICKY;
    }

@Override
    public void onDestroy() {
        super.onDestroy();
        passMessageToActivity("The service is finished, This is going to be more cooler than the heart of your ex...");
        System.out.println("onDestroy");
        serviceStatus=false;
   }

private void passMessageToActivity(String message){
        Intent intent = new Intent();
        intent.setAction(SENDMESAGGE);
        intent.putExtra("message",message);
        sendBroadcast(intent);
    }
}
  • if we don't unregister BroadcastReceiver we will have an error, you need to unregister when the activity go onPause, onStop, onDestroy...
  • if you don't register BroadcastReceiver when you back to activity, it will not listen anything from the service... the service will send information to BroadcastReceiver but it will not receive anything because it isn't registered.
  • When you create more than one service, the following services are going to begin in onStartCommand.
  • You can pass information to service with intent and you get it in onStartCommand
  • Difference about return in onStartCommand: Difference between START_STICKY and START_REDELIVER_INTENT? and check the official website of google: Services
Community
  • 1
  • 1
DarckBlezzer
  • 4,578
  • 1
  • 41
  • 51
  • what if i want to listen to a service even after onPause() or onDestroy() ?? – Kaveesh Kanwal Sep 12 '16 at 05:35
  • 1
    What do you mean, save output in a getsharedpreference? and when the activity resume put it... example: the activity is == null -> save in sharedPreference -> activity.resume -> get data from sharedPreference.. – DarckBlezzer Sep 12 '16 at 21:57
  • what you mean verficarStatusServicio() – Mahmoud Alnouno Oct 30 '16 at 10:12
  • Sorry i didn't put the function, it is a verificator, like check status service, if it is running or stopped, or something that you check in service... example: i want to show running or stopped in a `TextView`... i create a function that check the status, (check a static var in service.class) if it has "running" or "stopped" or if exist.... – DarckBlezzer Oct 31 '16 at 17:13
3

The best way in this case is to communicate by doing broadcasting from your service for different actions and receiving it in your activity. You can create a custom broadcast and send some codes defining specific events like complete, change, prepare etc...

Vikram Singh
  • 1,420
  • 14
  • 19
  • Do you know of any tutorial? I tried BrodacastReceivers and ended up with problems with instantiating Service. That's part that makes headaches. – Dejan Dec 15 '13 at 13:21
  • Any sample code showing initialization and bidirectional object oommunication is appreciated. – Dejan Dec 15 '13 at 13:29
  • umm...i did that myself and i don't know if there is complete tutorial for this..but you can search it in parts..like for custom broadcast receivers etc. and assemble it together...:) – Vikram Singh Dec 15 '13 at 13:37
2

Most easy and efficient way will be using EventBus from GreenRobot.

Use simple 3 steps:

1 Define events

public static class MessageEvent { /* Additional fields if needed */ }

2 Prepare subscribers: Declare and annotate your subscribing method, optionally specify a thread mode:

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {/* Do something */};

Register and unregister your subscriber. For example on Android, activities and fragments should usually register according to their life cycle:

@Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }

3 Post events:

EventBus.getDefault().post(new MessageEvent());
Khemraj Sharma
  • 57,232
  • 27
  • 203
  • 212
  • 2
    I actually used this solution. If you have big project code gets easily cluttered. I found out that using Dagger and dependency injection in combination with Repository pattern and MVP gives best results. – Dejan May 09 '18 at 07:43
0

Very easy yet powerful way is to use EventBus you can add it to your gradle build and enjoy the easy publisher/subscriber pattern .

Melad
  • 1,184
  • 14
  • 18
  • 2
    I would not recommend EventBus, since I did try it and I finished in hell, since I ended up debugging whole lot of code without knowing from which part of application is called. – Dejan Nov 19 '17 at 08:49