Android applications are built from 4 types of components:
Activity – Screen (code and GUI)
Service – independent code supplying something
Content Provider – Data service
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
Comments