ZOFTINO.COM android and web dev tutorials

Android Bound Services & Inter Process Communication

Android bound service is a service component which allows clients-server communication. Depending on how bound service is implemented, inter process communication is possible between clients and bound service. Meaning other apps running in different process than the service process can communicate with service. Bound services run as long as there is a client connected to it. Multiple clients can simultaneously connect to bound service and perform operations.

In this document I am going to show how to implement bound services. If you need information about services and view examples, you can read android services post.

Bound service is created like started service by extending Service object. Important callback method that need to be implemented in bound service is onBind(). Method onBind() should return IBinder which is used by clients to communicate with service. Here IBinder implementation is important, we will see how IBinder can be implemented.

Bound service is started by calling bindService() method which doesn’t return any value. This method takes ServiceConnection as argument. ServiceConnection interface has two methods which are called by the system. Method onServiceConnected() is called after system receives IBinder object from onBind() method call on the service. Clients preserve IBinder object and uses it for future communications needs with service.

IBinder Implementation

IBinder provides interface for client service communication. For communication within the same process, meaning client and service run in the same app or process, IBinder implementation is simple. You need to extend Binder class to provide public methods and return instance of it from onBind() method. You can find this example in my previous post android services.

For inter process communication, IBinder can be created using Messenger object. Messenger holds reference to handler. Service needs to define handler for different types of Message objects. Messenger maintains work queue so that one request is handled at a time.

To implement bound service with inter process communication capability, first you need to define handlers in the service, then create Messenger object passing the handler and implement onBind() method returning IBinder using Messenger.

 final private Messenger messenger = new Messenger(new IpcHandler());

@Override
public IBinder onBind(Intent intent) {
    return messenger.getBinder();
}

class IpcHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
       String couponType =  msg.getData().getString("CouponType");
        switch (couponType) {
            case "latest":
                Toast.makeText(getApplicationContext(), "Latest Coupon: Mobiles 50% Off", Toast.LENGTH_SHORT).show();
                break;
            case "popular":
                Toast.makeText(getApplicationContext(), "Popular Coupon: Fashion 90% Off", Toast.LENGTH_SHORT).show();
                break;
            case "custom":
                sendResponseToClient(msg);
                break;
            default:
                super.handleMessage(msg);
        }
    }
}
 

Clients can start bound service by calling bindService() passing ServiceConnection object. Clients can use IBinder received in onServiceConnected() callback method of ServiceConnection object to send messages to service.

 private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName className,
                                   IBinder iBinder) {
        messenger = new Messenger(iBinder);
    }
    @Override
    public void onServiceDisconnected(ComponentName arg0) {
        messenger = null;
    }
};
 

To send data in the request to service, Bundle object needs to be created and added to message object. Messenger’s send() method is used to send request to service passing message object.

 Message msg = Message.obtain();
try {
    Bundle bundle = new Bundle();
    bundle.putString("CouponType", "latest");
    msg.setData(bundle);

    messenger.send(msg);
} catch (RemoteException e) {
    Toast.makeText(getApplicationContext(), "Latest Coupon: Error", Toast.LENGTH_SHORT).show();
}
 

Bound Service Response Message to Client

Service can send results back to client using reply to messenger. For that, clients need to define a message handler to handle service response. Then create Messenger object using the handler and assign the messenger object to the request message object to be sent to service by setting replyTo.

class ClientHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        String coupon =  msg.getData().getString("Coupon");
        tv.setText(coupon);
    }
}
Message msg = Message.obtain();
msg.replyTo =  new Messenger(new ClientHandler());
try {
    Bundle bundle = new Bundle();
    bundle.putString("CouponType", "custom");
    msg.setData(bundle);

    messenger.send(msg);
} catch (RemoteException e) {
    Toast.makeText(getApplicationContext(), "Popular Coupon: Error", Toast.LENGTH_SHORT).show();
}

Bound Service Inter Process Communication Code

Bound Service

 package com.zoftino.content;


import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.Toast;

public class BoundServiceIPC extends Service {

    final private Messenger messenger = new Messenger(new IpcHandler());

    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }

    class IpcHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
           String couponType =  msg.getData().getString("CouponType");
            switch (couponType) {
                case "latest":
                    Toast.makeText(getApplicationContext(), "Latest Coupon: Mobiles 50% Off", Toast.LENGTH_SHORT).show();
                    break;
                case "popular":
                    Toast.makeText(getApplicationContext(), "Popular Coupon: Fashion 90% Off", Toast.LENGTH_SHORT).show();
                    break;
                case "custom":
                    sendResponseToClient(msg);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
    private void sendResponseToClient(Message msg){
        Message msgResponse = Message.obtain();
        Bundle bundle = new Bundle();
        bundle.putString("Coupon", "Entire Store Sale Upto 98% Off");
        msgResponse.setData(bundle);

        try {
            msg.replyTo.send(msgResponse);
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }
}
 

Activity - Bound Service Client

 package com.zoftino.content;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

public class BoundServiceIPCActivity extends AppCompatActivity {

    private Messenger messenger = null;
    private TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bsipc);

        tv = (TextView) findViewById(R.id.cpn);

    }

    @Override
    protected void onStop() {
        super.onStop();
        if (messenger != null) {
            unbindService(serviceConnection);
            messenger = null;
        }
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className,
                                       IBinder iBinder) {
            messenger = new Messenger(iBinder);
        }
        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            messenger = null;
        }
    };
    public void bindService(View view){
        Intent i =  new Intent();
        i.setClass(this, BoundServiceIPC.class);
        bindService(i, serviceConnection, Context.BIND_AUTO_CREATE);
    }
    public void getLatestCoupon(View view){
        if (messenger == null) return;

         Message msg = Message.obtain();
        try {
            Bundle bundle = new Bundle();
            bundle.putString("CouponType", "latest");
            msg.setData(bundle);

            messenger.send(msg);
        } catch (RemoteException e) {
            Toast.makeText(getApplicationContext(), "Latest Coupon: Error", Toast.LENGTH_SHORT).show();
        }
    }
    public void getPopularCoupon(View view){
        if (messenger == null) return;

        Message msg = Message.obtain();
        try {
            Bundle bundle = new Bundle();
            bundle.putString("CouponType", "popular");
            msg.setData(bundle);

            messenger.send(msg);
        } catch (RemoteException e) {
            Toast.makeText(getApplicationContext(), "Popular Coupon: Error", Toast.LENGTH_SHORT).show();
        }
    }
    public void stopService(View view){
        if (messenger != null) {
            unbindService(serviceConnection);
            messenger = null;
        }
    }
    public void getResponseFromBoundService(View view){
        if (messenger == null) return;

        Message msg = Message.obtain();
        msg.replyTo =  new Messenger(new ClientHandler());
        try {
            Bundle bundle = new Bundle();
            bundle.putString("CouponType", "custom");
            msg.setData(bundle);

            messenger.send(msg);
        } catch (RemoteException e) {
            Toast.makeText(getApplicationContext(), "Custom Coupon: Error", Toast.LENGTH_SHORT).show();
        }
    }
    class ClientHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            String coupon =  msg.getData().getString("Coupon");
            tv.setText(coupon);
        }
    }
}
 

Activity Layout

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_bsipc"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.zoftino.content.BoundServiceIPCActivity">
    <TextView
        android:id="@+id/cpn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"></TextView>

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="bindService"
        android:text="Bind Service"></Button>

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="stopService"
        android:text="Stop Service"></Button>

    <Button
        android:id="@+id/button3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="getLatestCoupon"
        android:text="Get Latest Coupon"></Button>

    <Button
        android:id="@+id/button6"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="getPopularCoupon"
        android:text="Get Popuar Coupon"></Button>
    <Button
        android:id="@+id/button16"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="getResponseFromBoundService"
        android:text="Get Response From BoundService"></Button>
</LinearLayout>