Android AIDL Example with Kotlin Sep 13th 2020 Words: 929

Background

I am working on an Android app that I want part of the function being delivered in an additional plugin. This plugin does not have any activity, it only provide service for my main app. Therefor, I need a method to enable my main app communicating with the plugin. AIDL is exactly what I am looking for.

Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.

Other choices of IPC are:

  • File
  • AIDL
  • Binder
  • Messenger
  • Content Provider
  • Socket

Server Code

The server only has a service with one function, to echo the argument to the caller.

IMyServiceInterface.aidl
1
2
3
4
5
6
7
8
package com.example.myplugin;

// Declare any non-default types here with import statements

interface IMyServiceInterface {

int test(int echo);
}
AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myplugin">

<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=".MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.myplugin.MyService" />
</intent-filter>
</service>
</application>

</manifest>
MyService.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.example.plugin

import android.app.Service
import android.content.Intent
import android.os.IBinder

class InputService : Service() {
private val binder = object : IMyServiceInterface.Stub() {
// Implement your aidl interface here
override fun test(echo: Int): Int {
return echo
}
}

override fun onBind(intent: Intent): IBinder {
return binder;
}
}

Client Code

The client has a button which will call Myservice.test with a random number on click.

IMyServiceInterface.aidl
1
2
3
4
5
6
7
8
9
10
// Just copy and paste, do not touch anything

package com.example.myplugin;

// Declare any non-default types here with import statements

interface IMyServiceInterface {

int test(int echo);
}
AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">

<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">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
activity_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click to test AIDL"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="MAKE IT RAIN"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.example.myapp

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.IBinder
import android.widget.Button
import android.widget.Toast
import com.example.myplugin.IMyServiceInterface

class MainActivity : AppCompatActivity() {
var iInputService: IMyServiceInterface? = null
val mConnection = object : ServiceConnection {
// Called when the connection with the service is established
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// Following the example above for an AIDL interface,
// this gets an instance of the IRemoteInterface, which we can use to call on the service
iInputService = IMyServiceInterface.Stub.asInterface(service)
Toast.makeText(applicationContext, "Service bind success", Toast.LENGTH_SHORT).show()
}

// Called when the connection with the service disconnects unexpectedly
override fun onServiceDisconnected(className: ComponentName) {
iInputService = null
Toast.makeText(applicationContext, "Service disconnected", Toast.LENGTH_SHORT).show()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// bind service on activity create
val intent = Intent()
intent.setClassName("com.example.myplugin", "com.example.myplugin.InputService"); // Must use explicit intent
bindService(intent, mConnection, Context.BIND_AUTO_CREATE)

// Call remote function on button click
val button: Button = findViewById(R.id.button)
button.setOnClickListener {

val result = iInputService?.test((1..100).random())
Toast.makeText(applicationContext, """Call result: $result""", Toast.LENGTH_SHORT)
.show()
}
}

override fun onDestroy() {
super.onDestroy()
unbindService(mConnection); // Unbind service on activity destroy
}
}

Test

👍👍👍