top of page
Writer's pictureThe Tech Platform

ANDROID SERVICES AND AIDL

Android applications are built from 4 types of components:

  1. Activity – Screen (code and GUI)

  2. Service – independent code supplying something

  3. Content Provider – Data service

  4. Broadcast Receiver – component for global events handling

The developer can create multiple components from each type and each one can be an entry point.

In this tutorial i will go over the steps to create and use an android service

To use a service from a client application (2 separate APKs) we are using binder IPC. The Binder is an IPC mechanism built into the kernel (as character device). In the native layer google wrote the libbinder library and with help of AIDL language and tool it make the binder very easy to use


AIDL – Android Interface Definition Language

To create a service we need to define an interface between the client and the server. AIDL is the way we do that , simply add a file with .aidl extension and the AIDL tool supplied with Android SDK will generate a code for using in the server (stub) and the client(proxy).


The best way to build client and server applications is to create the common code in a library

  • Create an Android Studio project – select “phone or tablet” template no activity – this will be the server application for hosting our services

  • From file menu select new -> New module and select Android Library. Name the library mylib

  • Add a new AIDL file to the library – right click the library and select new -> AIDL -> AIDL file. Name the file ISimpl.aidl

  • Declare a simple interface:



package app.mabel.com.mylib;
 
 
interface ISimp {
 int add(int a,int b);
 int sub(int a,int b);
}
  • Build the module – from the build menu select make module mylib

The generated code

If you change the view project files and go to mylib/build/generated/source , you will found a generated java file ISimp.java with the generated code from your AIDL


The generated code looks like this: (I removed the inner methods to simplify the explanation)


package app.mabel.com.mylib;
 
public interface ISimp extends android.os.IInterface {
 public static abstract class Stub extends android.os.Binder implements app.mabel.com.mylib.ISimp {
 
 private static class Proxy implements app.mabel.com.mylib.ISimp {
 }
 }
 
 public int add(int a, int b) throws android.os.RemoteException;
 public int sub(int a, int b) throws android.os.RemoteException;
 
}

The interface in the AIDL file converted into java interface. The Stub class is used by the service implementation In the server app. The proxy class is used by the client application.


Writing the server application

First thing we need to do is adding dependency between the server application and the android library module:

  • Right click on the server application -> Open Module Settings -> Dependencies -> click on the green plus icon on the upper right corner -> Select Module dependency -> select mylib

Add a new java class to the project – SimpServiceImp.java. The class should derived from ISimp.Stub class. The generated Stub class is abstract so we need to implement the missing methods – the members from the interface ISimp:


package app.mabel.com.testservice;
 
import android.os.RemoteException;
import app.mabel.com.mylib.ISimp;
 
 
public class SimpServiceImp extends ISimp.Stub {
 @Override
 public int add(int a, int b) throws RemoteException {
 return a+b;
 }
 
 @Override
 public int sub(int a, int b) throws RemoteException {
 return a-b;
 }
}

Now to create an Android Service we need to add another class derived from Service class and implement onBind and return the service implementation (Java does not support multiple inheritance so there is no way to derived from both the Stub and Service)

Create a new class: MySimpService.java


package app.mabel.com.testservice;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
 
public class MySimpService extends Service {
 @Nullable
 @Override
 public IBinder onBind(Intent intent) {
 return new SimpServiceImp();
 }
}

Last thing to do is declare the service in AndroidManifest.xml file:


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="app.mabel.com.testservice">
 
 <application
 android:allowBackup="true"
 android:icon="@mipmap/ic_launcher"
 android:label="@string/app_name"
 android:roundIcon="@mipmap/ic_launcher_round"
 android:supportsRtl="true"
 android:theme="@style/AppTheme" >
 <service android:name=".MySimpService">
 <intent-filter>
 <action android:name="app.mabel.com.testservice.MySimpService"/>
 </intent-filter>
 </service>
 </application>
</manifest>

To install the server app select Run -> Edit Configuration and select Nothing in the Launch list (this will only install the APK without trying to run it). then run the application



Writing the Client application

Add a new module to the project – select Phone & Tablet module , select a simple activity. This will create a new APK module so now our project built from 2 APKs and one AAR


Add a dependency to mylib module


To bind to the service you first need to implement ServiceConnection Interface. You can do it using a separate class or anonymous class but in this simple example we use the MainActivity class:


public class MainActivity extends AppCompatActivity implements ServiceConnection{
 @Override
 public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
 
 
 }
 
 @Override
 public void onServiceDisconnected(ComponentName componentName) {
 
 }
...
}

Before we implement the ServiceConnection interface we need to bind the service – we will do it in onStart event:

 protected void onStart() {
 super.onStart();
 bindService(new Intent("app.mabel.com.testservice.MySimpService").setPackage("app.mabel.com.testservice")
 ,this,BIND_AUTO_CREATE);
 
 }

We use the service action name as declared in the AndroidManifest.xml file and we need to specify the server package to create an explicit intent


Now add a ISimp member to the class and implement the onServiceConnected method:

private ISimp service;
 @Override
 public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
 service = ISimp.Stub.asInterface(iBinder);
 
 }

The method asInterface returns a Proxy object to use by the client. Now we can add a button with event and call the service using the proxy:

 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 Button btn=(Button)findViewById(R.id.btn1);
 btn.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View view) {
 try {
 Log.d("tag","res:"+service.add(10,20));
 } catch (RemoteException e) {
 e.printStackTrace();
 }
 
 }
 });
}



How does it work

We can see the flow from the generated code, when we call add , the add method from the Proxy class is invoked , It serialise the parameters and send it to the binder:


public int add(int a, int b) throws android.os.RemoteException {
 android.os.Parcel _data = android.os.Parcel.obtain();
 android.os.Parcel _reply = android.os.Parcel.obtain();
 int _result;
 try {
 _data.writeInterfaceToken(DESCRIPTOR);
 _data.writeInt(a);
 _data.writeInt(b);
 mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
 _reply.readException();
 _result = _reply.readInt();
 } finally {
 _reply.recycle();
 _data.recycle();
 }
 return _result;
}


using mRemote.transact the proxy send the request to the binder, then to the service Stub member onTransact:


public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
 switch (code) {
 case INTERFACE_TRANSACTION: {
 reply.writeString(DESCRIPTOR);
 return true;
 }
 case TRANSACTION_add: {
 data.enforceInterface(DESCRIPTOR);
 int _arg0;
 _arg0 = data.readInt();
 int _arg1;
 _arg1 = data.readInt();
 int _result = this.add(_arg0, _arg1);
 reply.writeNoException();
 reply.writeInt(_result);
 return true;
 }
 ...
}


The function deserialize the parameters, call the service implementation add method (what we implemented above) and serialize the return value

Extending the interface

Now we can add more methods to the interface , implement on the service and use from the client

in, out, inout keywords

When using a primitive types , they passed and return by value but if we send an object to a java method, it is passed by reference. To make it work on another process we need to serialize the parameters from the client to the service and serialize it back because maybe the function changed it. To make it more effective we need to specify if the object should be input (from the client to the service only) , output (from the service to the client only) or input and output. We do that with one of the above keywords

For example we want to add a method to calculate array items sum. we need to pass the array to the service but we don’t care about changes done by the service so we add it to the AIDL file with in keyword:

interface ISimp {
 int add(int a,int b);
 int sub(int a,int b);
 int addrr(in int[] arr);
}


implement on the service implementation class and use it from the client


 @Override
 public int addrr(int[] arr) throws RemoteException {
 int sum=0;
 for(int i=0;i<arr.length;i++)
 sum+=arr[i];
 return sum;
 }


If we want a function to fill the array with values we need to specify it as out and if we read and write values on the service (get an image, convert it to grey scale) we use inout:

interface ISimp {
 int add(int a,int b);
 int sub(int a,int b);
 int addrr(in int[] arr);
 int fillArray(out int[] arr)
 int convertToGray(inout int[] rgb)
}



Creating custom types

You can pass a very limited number of types using the binder :

  • All primitives

  • Arrays of primitives

  • String and CharSequence

  • Some data structures (map, list, etc)

  • File Descriptor (very useful)

You can add a new type by implementing Parcelable interface: (do it in the library)

package app.mabel.com.mylib;
 
import android.os.Parcel;
import android.os.Parcelable;
 
 
public class CustomType implements Parcelable {
 int num1;
 String s1;
 
 public int getNum1() {
 return num1;
 }
 
 public void setNum1(int num1) {
 this.num1 = num1;
 }
 
 public String getS1() {
 return s1;
 }
 
 public void setS1(String s1) {
 this.s1 = s1;
 }
 
 
 @Override
 public int describeContents() {
 return 0;
 }
 
 @Override
 public void writeToParcel(Parcel parcel, int i) {
 parcel.writeInt(num1);
 parcel.writeString(s1);
 }
 
 public static final Parcelable.Creator<CustomType> CREATOR = new Parcelable.Creator<CustomType>(){
 
 @Override
 public CustomType createFromParcel(Parcel parcel) {
 CustomType res=new CustomType();
 res.num1 = parcel.readInt();
 res.s1 = parcel.readString();
 return res;
 }
 
 @Override
 public CustomType[] newArray(int size) {
 return new CustomType[size];
 }
 };
}

Serialize the object in writeToParcel method and deserialize it on createFromParcel. Note that the order is important (in this example first is integer and second is srting)

We also need to add another aidl file CustomType.aidl with the parcelable definition:

1 2 3package app.mabel.com.mylib; parcelable CustomType;


Now we can add the custom type as a parameter in our interface:



package app.mabel.com.mylib;
 
import app.mabel.com.mylib.CustomType;
// Declare any non-default types here with import statements
 
interface ISimp {
 int add(int a,int b);
 int sub(int a,int b);
 int addrr(in int[] arr);
 String getCustomName(in CustomType ct);
}


Note that we need to import the custom type even if its on the same package


Asynchronous Service

Another useful option is declaring an async method in the service. We do it using oneway keyword and we need to split the operation into 2 interfaces – one for the request and one for the response.

For example we want to sum the array elements asynchronously. We need to create 2 interfaces:

one for the request: IAsync.aidl


package app.mabel.com.mylib;
 
import app.mabel.com.mylib.IAsyncListener;
 
oneway interface IAsync {
 void calcSum(in int []arr, in IAsyncListener lis);
}


And one for the response: IAsyncListener.aidl



package app.mabel.com.mylib;
 
// Declare any non-default types here with import statements
 
oneway interface IAsyncListener {
 void OnResponse(int res);
}


Now we implement the service:


class IAsyncImp extends IAsync.Stub {
 
 @Override
 public void calcSum(int[] arr, IAsyncListener lis) throws RemoteException {
 int res=0;
 for(int i=0;i<arr.length;i++)
 res+=arr[i];
 lis.OnResponse(res);
 }
}


And use it from the client


Button btn2=(Button)findViewById(R.id.btn2);
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
 try {
 asyncService.calcSum(new int[]{2, 3, 4, 5}, new IAsyncListener.Stub() {
 @Override
 public void OnResponse(int res) throws RemoteException {
 Log.d("tag","resp:"+res);
 }
 });
 } catch (RemoteException e) {
 e.printStackTrace();
 }
 }
});


Note that the client derived from the response interface Stub (i.e. the client act as a server for the result)

Thats it. You can download the source example here


This is the basic for any type of service in android , next post will be on native services integrated into AOSP


Source: Devarea


The Tech Platform

0 comments

Comments


bottom of page