Android services are designed for many permutations and combinations. You can use them for long running operations, you can use them for long running network operations, you can use them for never ending operations in a separate process. One of these many permutations and combinations is Android service interprocess communication through the usage of AIDL. The Android Interface Definition Language. In this tutorial I would show you how to create an interface through AIDL and use it for interprocess communication between a service an activity.
Before starting with the tutorial let me put down some facts for beginners.
- Android services are not background tasks, they run on your app’s main thread only.
- An Android service can be started in a different process, which would free your app’s main thread.
- If an Android service is started in a different process, you cannot communicate with that service through a normal
IBinder interface.
To do so we need to establish an inter process communication between the android service (in process A) and the activity (in process B). One of the two ways of establishing interprocess communication (IPC) is through, the use of AIDL. Although it sounds a little complex, but believe me its not that difficult. All you need to do is, create an interface, like a normal java interface. Then place it in your file structure with an extension .aidl. Just like the image shown below.
AIDL : Android Interface Definition Language
To perform IPC (Inter Process Communication) in Android, one needs to define a set of methods through which a process can be accessed from a remote process. Through AIDL we can define these methods. AIDL is just like any other interface in Java, where abstract methods are defined. To do so we need to create a file with .aidl extension and define all the abstract methods in it, which are to be accessed outside this process.
One of the main features of AIDL is that, by using this we can communicate between two applications. Although if you don’t need to communicate with a different app (in a different process of course), this approach should not be used. As this feature also imposes a direct restriction on the implementation. That only a primitive data type, and some other basic data types like, string, lists, maps, etc. can be used as parameters for methods. If you want to use a custom class object as a parameter, then you need to implement the
Parcelable interface in that object’s class and import it in the AIDL too. This can be a separate tutorial in itself, therefore for the sake of simplicity I have not included it in this Android Service Interprocess Communication With AIDL tutorial.
While designing the Android service which will implement this AIDL interface, keep in mind that this service can be accessed by more than one application at a time. Therefore you need to implement this service as a multi-threaded service, assuming any method defined in this service can be executed simultaneously by more than one application.
Android Inter Process Communication In Same App
In this Android Service: IPC With AIDL tutorial, my aim is to get the running time of an Android service in an activity. To do this I will start an Android service in a process, with a timer running inside it. Then I will bind it to an activity in a separate process. I will be doing all of this, in the same app. As in normal scenarios, mostly when a service is started, it is accessed in the same app first. Although if it needs to be accessed in a different app, it will be accessed in the same way. Moving on lets have a look at the code:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.truiton.boundservice" android:versionCode="1" android:versionName="1.0" > <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="com.truiton.boundservice.BoundService" android:process=":truiton_process" > </service> </application> </manifest>
In the above manifest, you can see highlighted, the service is started in a separate process. Reason being we have to perform Android Service Interprocess Communication With AIDL. Next lets define the Android AIDL file.
package com.truiton.boundservice.aidl; interface IBoundService{ int getPid(); String getTimestamp(); }
After creating this file with .aidl
extension, you need to put this file in the /src
folder of your project. After this, Android SDK tools will automatically generate an IBinder
interface in your /gen
folder. You can access this interface through a Stub
subclass, which will also be created automatically. Have a look at the service code below:
package com.truiton.boundservice; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.widget.Chronometer; import com.truiton.boundservice.aidl.IBoundService; public class BoundService extends Service { private static String LOG_TAG = "BoundService"; private Chronometer mChronometer; private IBoundService.Stub mBinder = new IBoundService.Stub() { @Override public int getPid() throws RemoteException { return this.getPid(); } @Override public String getTimestamp() throws RemoteException { long elapsedMillis = SystemClock.elapsedRealtime() - mChronometer.getBase(); int hours = (int) (elapsedMillis / 3600000); int minutes = (int) (elapsedMillis - hours * 3600000) / 60000; int seconds = (int) (elapsedMillis - hours * 3600000 - minutes * 60000) / 1000; int millis = (int) (elapsedMillis - hours * 3600000 - minutes * 60000 - seconds * 1000); return hours + ":" + minutes + ":" + seconds + ":" + millis; } }; @Override public void onCreate() { super.onCreate(); Log.v(LOG_TAG, "in onCreate"); mChronometer = new Chronometer(this); mChronometer.setBase(SystemClock.elapsedRealtime()); mChronometer.start(); } @Override public IBinder onBind(Intent intent) { Log.v(LOG_TAG, "in onBind"); return mBinder; } @Override public void onRebind(Intent intent) { Log.v(LOG_TAG, "in onRebind"); super.onRebind(intent); } @Override public boolean onUnbind(Intent intent) { Log.v(LOG_TAG, "in onUnbind"); return true; } @Override public void onDestroy() { super.onDestroy(); Log.v(LOG_TAG, "in onDestroy"); mChronometer.stop(); } }
As you can see the above implementation of Android service is very basic, its running a Chronometer
to keep track of the service start time. In this Android service inter process communication with AIDL example my aim is to get the running time of this service in an activity. Now if you remember this service is running in a separate process, therefore to get the running time of this service out of this process, we need to perform inter process communication. Hence getTimestamp()
method defined in the AIDL, is implemented here.
Next lets have a look at the layout file of the activity, where this method would be accessed:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFFFFF" 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.truiton.boundservice.MainActivity" > <ImageView android:id="@+id/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="-80dp" android:src="@drawable/truiton_sq" /> <Button android:id="@+id/print_timestamp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="130dp" android:text="Print Timestamp" /> <TextView android:id="@+id/timestamp_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/print_timestamp" android:layout_centerHorizontal="true" android:layout_marginTop="120dp" android:text="" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/stop_service" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/print_timestamp" android:layout_centerHorizontal="true" android:text="Stop Service" /> </RelativeLayout>
After rendering this layout, it would look something like this:
This layout would be inflated in MainActivity.java, lets have a look at the code for this class:
package com.truiton.boundservice; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import com.truiton.boundservice.aidl.IBoundService; public class MainActivity extends AppCompatActivity { IBoundService mBoundServiceInterface; boolean mServiceConnected = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final TextView timestampText = (TextView) findViewById(R.id.timestamp_text); Button printTimestampButton = (Button) findViewById(R.id.print_timestamp); Button stopServiceButon = (Button) findViewById(R.id.stop_service); printTimestampButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mServiceConnected) { try { timestampText.setText((mBoundServiceInterface) .getTimestamp()); } catch (RemoteException e) { e.printStackTrace(); } } } }); stopServiceButon.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mServiceConnected) { unbindService(mServiceConnection); mServiceConnected = false; } Intent intent = new Intent(MainActivity.this, BoundService.class); stopService(intent); } }); } @Override protected void onStart() { super.onStart(); Intent intent = new Intent(this, BoundService.class); startService(intent); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); if (mServiceConnected) { unbindService(mServiceConnection); mServiceConnected = false; } } private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { mServiceConnected = false; } @Override public void onServiceConnected(ComponentName name, IBinder service) { mBoundServiceInterface = IBoundService.Stub.asInterface(service); mServiceConnected = true; } }; }
As you can see in the above class, an android service by the name of BoundService is started by calling startService()
method. Further the same service is bound by calling the bindService()
method. But here the binding process is a little different from standard Android bound service. Here if you see closely IBoundService.Stub.asInterface(service)
is used to get the interface in this activity, through which the getTimestamp()
method is called, which prints the time stamp. To get a better understanding, have a look at the full source code below:
For the sake of simplicity in this Android Service Interprocess Communication With AIDL tutorial I have used the same app to access the service which was started in a different process. But if you are trying to access the service in a different app, don’t forget to add the .aidl
file in that app’s /src
folder structure. By doing this you would be able to access the remote service’s methods. Hope this helps.
Born in New Delhi, India. A software engineer by profession, an android enthusiast and an evangelist. My motive here is to create a group of skilled developers, who can develop something new and good. Reason being programming is my passion, and also it feels good to make a device do something you want. In a very short span of time professionally I have worked with many tech firms. As of now too, I am employed as a senior engineer in a leading tech company. In total I may have worked on more than 20 projects professionally, and whenever I get spare time I share my thoughts here at Truiton.
Good one
Thanks.