'How can a remote Service send messages to a bound Activity?

I've read the documentation about Bound Services, where it is shown that you can easily communicate through Messages from an Activity to a remote (i.e. not in the same context) Service but is there any way to send messages from the Service to the bound Activity? For example, my activity bounds to a running background service of the same application, sends a message to it and upon the reception of this message the service replies with a message to the activity.. how do I implement this? Can you point me to some documentation that explains this topic?



Solution 1:[1]

NOTE: This is only for in-process services and activities, not remote like question asked.

Using a service to communicate with an activity involves making a listener that you can pass to the service from the activity.

You need to make a service that is bound to an activity.

The first step is making a service. In the service make sure you have a Binder object and the method to return a binder object. Below is an example that I used in my service to retrieve my binder. Also notice this binder has a method to set a listener, which will be saved in the service as a BoundServiceListener type field.

/**
 * Class used for the client Binder.  Because we know this service always
 * runs in the same process as its clients, we don't need to deal with IPC.
 */
public class DownloadBgBinder extends Binder {

    public DownloadBgService getService() {
        // Return this instance of LocalService so clients can call public methods
        return DownloadBgService.this;
    }

    public void setListener(BoundServiceListener listener) {
        mListener = listener;
    }
}

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

Now you need to create some kind of interface that you can pass to the binder object that your service can use to send updates to. Below is my BoundServiceListener.

public interface BoundServiceListener {

    public void sendProgress(double progress);
    public void finishedDownloading();
}

Now in your activity you need to create a ServiceConnection object that is used for binding to a service. SO add somethinglike this.

/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {

    @Override
    public void onServiceDisconnected(ComponentName arg0) {
        mBound = false;
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // We've bound to LocalService, cast the IBinder and get LocalService instance
        DownloadBgBinder binder = (DownloadBgBinder) service;
        mService = binder.getService();
        binder.setListener(new BoundServiceListener() {

            @Override
            public void sendProgress(double progress) {
                // Use this method to update our download progress
            }

            @Override
            public void finishedDownloading() {

            }   
        });

        mBound = true;
    }

Now the key line to notice here is

binder.setListener(new BoundServiceListener() {

    @Override
    public void sendProgress(double progress) {
        // Use this method to update our download progress
    }

    @Override
    public void finishedDownloading() {

    }
});

This part is where I am actually sending my BoundServiceListener interface to the service class. The service class then uses that listener object here

    if (mListener!=null)
        mListener.finishedDownloading();
    if (mListener!=null)
        mListener.sendProgress(percent);

Now you can put this anywhere you need to in your service class, and your activity will receive your progress update.

Also be sure to include following in your activity to actually bind and start the service.

Intent intent = new Intent(this, DownloadBgService.class);
startService(intent);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

Keep in mind that even though you bind to a service, it it not actually started until you call start service. Binding to the service just connects the service to an activity. the startService() method calls the services

onStartCommand(Intent intent, int flags, int startId)

Also declare your service in your manifest

<service android:name=".services.DownloadBgService" />

Also unbind the service when the activity leaves by

@Override
protected void onStop() {
    super.onStop();
    // Unbind from the service
    if (mBound) {
        unbindService(mConnection);
        mBound = false;
    }
}

Hope this helps.

Solution 2:[2]

1) implement transact/onTransact methods in own Binder.class and binder proxy implementing IInterface.class objects (anon or by extending a class direct) by use of passed in those methods Parcel.class object
2) attach local interface to own Binder object 3) create service and return a binder proxy implementation from onBind method 4) create bond with bindService(ServiceConnection) 5) this will result in returning proxy binder via created bound in interfece implementation

this is an android implementation of IPC with usage of kernel binder space

simplifying in code example :

class ServiceIPC extends Service {

    @Override
    public Binder onBind()  {
        return new IInterface() {

            IInterface _local = this;         

            @Override 
            public IBinder asBinder() {
               return new Binder() 

                           {   
                               //
                               // allow distinguish local/remote impl 
                               // avoid overhead by ipc call 
                               // see Binder.queryLocalInterface("descriptor");
                               //
                               attachLocalInterface(_local,"descriptor");
                           }

                           @Override
                           public boolean onTransact(int code,
                                                     Parcel in,
                                                     Parcel out,
                                                     int flags) 
                                   throws RemoteException {
                               //
                               //  your talk between client & service goes here 
                               //
                               return whatsoever // se super.onTransact(); 
                           }
                      }
            }    

        }.asBinder();
    }

}

*then you could use the IBinder on client and service side the transact method to talk with each other (4 example using odd/eve codes to disgusting local remote side as we use same onTransact method for booth sides)

Solution 3:[3]

should be able to do this using . a AIDL file like android billing api does. its a way to do RPC calls (communicate across remote processes). but you have to declare each method you want to use. sort of like the interface above already mentioned.

Solution 4:[4]

In Short, the Answer is by assigning a Messenger with ResponseHandler to msg.replyTo(). Let's see in the below example how we do it.

Brief about what this Example Does: In this Example, We have a button in MainActivity whose onClick() is linked to sendMessage(View view). Once the Button is Clicked, We Send a Custom Message to RemoteService. Once Custom Message is received in Remote Service, We append the CurrentTime to Custom Message and send it back to MainActivity.

MainActivity.java

public class MainActivity extends AppCompatActivity {

    ServiceConnector serviceConnector;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.serviceConnector = new ServiceConnector();
        Intent intent = new Intent(this,RemoteService.class);
        bindService(intent,serviceConnector, Context.BIND_AUTO_CREATE);
    }

    public void sendMessage(View view) {
        Message msg = Message.obtain();
        msg.replyTo = new Messenger(new ResponseHandler(this));
        Bundle bundle = new Bundle();
        bundle.putString("MyString", "Time");
        msg.setData(bundle);
        try {
            this.serviceConnector.getMessenger().send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

ResponseHandler.java

public class ResponseHandler extends Handler {

    MainActivity mainActivity;
    public ResponseHandler(Context context){
        this.mainActivity = (MainActivity) context;
    }
    @Override
    public void handleMessage(@NonNull Message msg) {
        Bundle data = msg.getData();
        String dataString = data.getString("respData");
        Toast.makeText(this.mainActivity,dataString,Toast.LENGTH_SHORT).show();
    }
}

ServiceConnector.java

public class ServiceConnector implements ServiceConnection {

    private Messenger messenger;

    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) 
   {
          this.messenger = new Messenger(iBinder);
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        this.messenger = null;
    }

    public Messenger getMessenger(){
        return this.messenger;
    }
}

RemoteService.java

public class RemoteService extends Service {

    private final IBinder iBinder = new Messenger(new IncomingHandler(this)).getBinder();

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return iBinder;
    }

}

IncomingHandler.java

public class IncomingHandler extends Handler {

    private RemoteService remoteService;

    public IncomingHandler(Context context)
    {
        this.remoteService = (RemoteService)context;
    }

    public RemoteService getService()
    {
        return this.remoteService;
    }

    @Override
    public void handleMessage(@NonNull Message msg) {
        try {
            msg.replyTo.send(getCurrentTime(msg));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public Message getCurrentTime(Message msg){
        SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss MM/dd/yyyy", Locale.US);
        Message resp = Message.obtain();
        Bundle bResp = new Bundle();
        bResp.putString("respData", msg.getData().getString("MyString") + " : " +(dateFormat.format(new Date())).toString());
        resp.setData(bResp);
        return resp;

    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 j2emanue
Solution 4 Anandaraja_Srinivasan