язык описания интерфейсов Android (AIDL)

Язык описания интерфейсов Android (AIDL) похож на другие языки описания интерфейсов. Он позволяет определить программный интерфейс, который клиент и сервис используют для межпроцессного взаимодействия.

В Android один процесс обычно не может получить доступ к памяти другого. Чтобы общаться, им нужно разложить свои объекты на примитивы, которые операционная система может понять и маршалировать объекты через эту границу для вас. Писать код для маршалинга вручную – утомительная задача, поэтому Android выполняет его за вас с помощью AIDL.

Примечание. AIDL необходим, только если вы разрешаете клиентам из разных приложений получать доступ к вашему сервису для IPC и хотите обрабатывать многопоточность в своем сервисе. Если вам не нужно выполнять параллельные IPC в разных приложениях, создайте интерфейс, реализовав Binder. Если вам нужно реализовать IPC, но не требуется многопоточность, реализуйте интерфейс с помощью Messenger. В любом случае, прежде чем реализовывать AIDL, убедитесь, что вы понимаете, как работают привязанные службы.

Прежде чем приступить к разработке интерфейса AIDL, учтите, что вызовы интерфейса AIDL являются прямыми вызовами функций. Не делайте предположений о потоке, в котором происходит вызов. Результат зависит от того, откуда поступил вызов – из потока в локальном или удаленном процессе.

  • Вызовы, сделанные из локального процесса, выполняются в том же потоке, который делает вызов. Если это основной поток UI, этот поток продолжит выполняться в интерфейсе AIDL. Если это другой поток, то именно он выполняет ваш код в сервисе. Таким образом, если к сервису обращаются только локальные потоки, вы можете полностью контролировать, какие потоки в нем выполняются. В этом случае не используйте AIDL, а создайте интерфейс, реализовав Binder.
  • Вызовы из удаленного процесса отправляются из пула потоков, который платформа поддерживает внутри вашего собственного процесса. Будьте готовы к входящим звонкам от неизвестных абонентов, в том числе к нескольким одновременным звонкам. Другими словами, реализация интерфейса AIDL должна быть полностью потокобезопасной. Вызовы, сделанные из одного потока на одном и том же удаленном объекте, поступают на принимающую сторону по порядку.
  • Ключевое слово oneway изменяет поведение удаленных вызовов. При его использовании удаленный вызов не блокируется. Он отправляет данные о транзакции и сразу же возвращает результат. Реализация интерфейса в конечном итоге получает это как обычный вызов из пула потоков Binder в качестве обычного удаленного вызова. Если oneway используется с локальным вызовом, это ни на что не влияет, и вызов остается синхронным.

Определение интерфейса AIDL

Определите интерфейс AIDL в файле .aidl, используя синтаксис языка программирования Java, а затем сохраните его в исходном коде в каталоге src/ как приложения, в котором размещен сервис, так и любого другого приложения, которое связывается с сервисом.

При сборке каждого приложения, содержащего файл .aidl, инструменты Android SDK генерируют интерфейс IBinder на основе файла .aidl и сохраняют его в каталоге проекта gen/. Сервис должен реализовывать интерфейс IBinder. Затем клиентские приложения могут привязаться к сервису и вызвать методы из IBinder, чтобы выполнить IPC.

Чтобы создать привязанную службу с помощью AIDL, выполните следующие действия, описанные в следующих разделах:

  1. Создайте файл .aidl

    Этот файл определяет программный интерфейс с сигнатурами методов.

  2. Реализуйте интерфейс

    Инструменты Android SDK генерируют интерфейс на языке программирования Java на основе вашего файла .aidl. Этот интерфейс содержит внутренний абстрактный класс Stub, который расширяет Binder и реализует методы из интерфейса AIDL. Вам нужно расширить класс Stub и реализовать методы.

  3. Предоставление клиентам доступа к интерфейсу

    Реализуйте Service и переопределите onBind(), чтобы вернуть реализацию класса Stub.

Внимание! Любые изменения, которые вы вносите в интерфейс AIDL после первого выпуска, должны быть обратно совместимыми, чтобы не нарушить работу других приложений, использующих ваш сервис. Поскольку файл .aidl необходимо скопировать в другие приложения, чтобы они могли получить доступ к интерфейсу вашего сервиса, вы должны поддерживать исходный интерфейс.

Создайте файл AIDL

AIDL использует простой синтаксис, который позволяет объявить интерфейс с одним или несколькими методами, которые могут принимать параметры и возвращать значения. Параметры и возвращаемые значения могут быть любого типа, даже другими интерфейсами, сгенерированными AIDL.

Файл .aidl должен быть написан на языке программирования Java. Каждый файл .aidl должен определять один интерфейс и требует только объявления интерфейса и сигнатур методов.

По умолчанию AIDL поддерживает следующие типы данных:

  • Все примитивные типы, за исключением short, в языке программирования Java (например, int,long, char, boolean и т. д.).
  • Массивы любых типов, например int[] или MyParcelable[].
  • String
  • CharSequence
  • List

    Все элементы в List должны относиться к одному из поддерживаемых типов данных в этом списке или к одному из других сгенерированных AIDL интерфейсов или объектов Parcelable, которые вы объявляете. List можно использовать как параметризованный класс типа, например List<String>. Фактический конкретный класс, который получает другая сторона, всегда является ArrayList, хотя метод генерируется для использования интерфейса List.

  • Map

    Все элементы в Map должны относиться к одному из поддерживаемых типов данных в этом списке или к одному из других сгенерированных AIDL интерфейсов или объявленных вами объектов Parcelable. Параметризованные карты типов, например в формате Map<String,Integer>, не поддерживаются. Фактический конкретный класс, который получает другая сторона, всегда является HashMap, хотя метод генерируется для использования интерфейса Map. Вместо Map можно использовать Bundle.

Для каждого дополнительного типа, не указанного ранее, необходимо включить оператор import, даже если они определены в том же пакете, что и ваш интерфейс.

При определении интерфейса сервиса учитывайте следующее:

  • Методы могут принимать ноль или более параметров и могут возвращать значение или void.
  • Для всех непримитивных параметров требуется тег направления, указывающий, куда передаются данные: in, out или inout (см. пример ниже).

    Примитивы, String, IBinder и интерфейсы, созданные с помощью AIDL, по умолчанию имеют тип in. Изменить его нельзя.

    Внимание! Ограничьте направление только тем, что действительно необходимо, поскольку маршалинг параметров – ресурсоемкая операция.

  • Все комментарии к коду, включенные в файл .aidl, включаются в сгенерированный интерфейс IBinder, за исключением комментариев перед инструкциями импорта и пакета.
  • В интерфейсе AIDL можно определить строковые и целочисленные константы, например const int VERSION = 1;.
  • Вызовы методов отправляются с помощью кода transact(), который обычно основан на индексе метода в интерфейсе. Поскольку это усложняет управление версиями, вы можете вручную назначить код транзакции методу: void method() = 10;.
  • Аргументы и типы возвращаемых значений, допускающие значение NULL, должны быть аннотированы с помощью @nullable.

Пример файла .aidl:

// IRemoteService.aidl
package com.example.android;

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

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

Сохраните файл .aidl в каталоге src/ проекта. При сборке приложения инструменты SDK создают файл интерфейса IBinder в каталоге gen/ проекта. Название созданного файла совпадает с названием файла .aidl, но имеет расширение .java. Например, IRemoteService.aidl преобразуется в IRemoteService.java.

Если вы используете Android Studio, инкрементная сборка генерирует класс связывателя почти сразу. Если вы не используете Android Studio, инструмент Gradle сгенерирует класс связывания при следующей сборке приложения. Соберите проект с помощью gradle assembleDebug или gradle assembleRelease сразу после того, как закончите писать файл .aidl, чтобы ваш код мог ссылаться на сгенерированный класс.

Реализуйте интерфейс

При сборке приложения инструменты Android SDK генерируют файл интерфейса .java, название которого совпадает с названием файла .aidl. Сгенерированный интерфейс включает подкласс Stub, который является абстрактной реализацией родительского интерфейса, например YourInterface.Stub, и объявляет все методы из файла .aidl.

Примечание. В Stub также определены вспомогательные методы, в частности asInterface(), который принимает IBinder (обычно передаваемый в метод обратного вызова onServiceConnected() клиента) и возвращает экземпляр интерфейса-заглушки. Подробнее о том, как это сделать, рассказывается в разделе Вызов метода IPC.

Чтобы реализовать интерфейс, созданный из файла .aidl, расширьте созданный интерфейс Binder, например YourInterface.Stub, и реализуйте методы, унаследованные из файла .aidl.

Ниже приведен пример реализации интерфейса IRemoteService, определенного в предыдущем примере IRemoteService.aidl, с использованием анонимного экземпляра.

Kotlin

private val binder = object : IRemoteService.Stub() {

    override fun getPid(): Int =
            Process.myPid()

    override fun basicTypes(
            anInt: Int,
            aLong: Long,
            aBoolean: Boolean,
            aFloat: Float,
            aDouble: Double,
            aString: String
    ) {
        // Does nothing.
    }
}

Java

private final IRemoteService.Stub binder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing.
    }
};

Теперь binder – это экземпляр класса Stub (Binder), который определяет интерфейс IPC для сервиса. На следующем шаге этот экземпляр будет предоставлен клиентам, чтобы они могли взаимодействовать с сервисом.

При реализации интерфейса AIDL учитывайте следующие правила:

  • Входящие вызовы не обязательно выполняются в основном потоке, поэтому вам нужно с самого начала подумать о многопоточности и правильно создать сервис, чтобы он был потокобезопасным.
  • По умолчанию вызовы IPC являются синхронными. Если вы знаете, что на выполнение запроса сервису требуется больше нескольких миллисекунд, не вызывайте его из основного потока действия. Это может привести к зависанию приложения и появлению диалогового окна "Приложение не отвечает". Вызовите его из отдельного потока в клиенте.
  • Вызывающей стороне возвращаются только типы исключений, перечисленные в справочной документации по Parcel.writeException().

Предоставьте клиентам доступ к интерфейсу

После реализации интерфейса для сервиса его нужно предоставить клиентам, чтобы они могли к нему привязаться. Чтобы предоставить интерфейс для сервиса, расширьте Service и реализуйте onBind(), чтобы вернуть экземпляр класса, который реализует сгенерированный Stub, как описано в предыдущем разделе. Ниже приведен пример сервиса, который предоставляет клиентам интерфейс IRemoteService.

Kotlin

class RemoteService : Service() {

    override fun onCreate() {
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder {
        // Return the interface.
        return binder
    }


    private val binder = object : IRemoteService.Stub() {
        override fun getPid(): Int {
            return Process.myPid()
        }

        override fun basicTypes(
                anInt: Int,
                aLong: Long,
                aBoolean: Boolean,
                aFloat: Float,
                aDouble: Double,
                aString: String
        ) {
            // Does nothing.
        }
    }
}

Java

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface.
        return binder;
    }

    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing.
        }
    };
}

Теперь, когда клиент, например объект activity, вызывает bindService() для подключения к этому сервису, обратный вызов onServiceConnected() клиента получает экземпляр binder, возвращенный методом onBind() сервиса.

У клиента также должен быть доступ к классу интерфейса. Поэтому, если клиент и сервис находятся в разных приложениях, у приложения клиента должна быть копия файла .aidl в каталоге src/, который генерирует интерфейс android.os.Binder, предоставляя клиенту доступ к методам AIDL.

Когда клиент получает IBinder в обратном вызове onServiceConnected(), он должен вызвать YourServiceInterface.Stub.asInterface(service), чтобы привести возвращенный параметр к типу YourServiceInterface:

Kotlin

var iRemoteService: IRemoteService? = null

val mConnection = object : ServiceConnection {

    // Called when the connection with the service is established.
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service)
    }

    // Called when the connection with the service disconnects unexpectedly.
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "Service has unexpectedly disconnected")
        iRemoteService = null
    }
}

Java

IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established.
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly.
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        iRemoteService = null;
    }
};

Дополнительные образцы кода можно найти в классе RemoteService.java в ApiDemos.

Передача объектов через IPC

В Android 10 (уровень API 29 или выше) вы можете определять объекты Parcelable непосредственно в AIDL. Типы, которые поддерживаются в качестве аргументов интерфейса AIDL и других объектов Parcelable, также поддерживаются здесь. Это позволяет избежать дополнительной работы по написанию кода маршалинга и пользовательского класса вручную. Однако это также создает пустую структуру. Если вам нужны специальные методы доступа или другие функции, используйте Parcelable.

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect {
    int left;
    int top;
    int right;
    int bottom;
}

Приведенный выше пример кода автоматически генерирует класс Java с целочисленными полями left, top, right и bottom. Весь необходимый код маршалинга реализуется автоматически, и объект можно использовать напрямую без добавления каких-либо реализаций.

Вы также можете передавать пользовательский класс из одного процесса в другой через интерфейс IPC. Однако убедитесь, что код вашего класса доступен на другой стороне канала IPC и ваш класс поддерживает интерфейс Parcelable. Поддержка Parcelable важна, поскольку позволяет системе Android разбивать объекты на примитивы, которые можно передавать между процессами.

Чтобы создать специальный класс, поддерживающий Parcelable, выполните следующие действия:

  1. Реализуйте в классе интерфейс Parcelable.
  2. Реализуйте метод writeToParcel, который принимает текущее состояние объекта и записывает его в Parcel.
  3. Добавьте в класс статическое поле CREATOR, которое является объектом, реализующим интерфейс Parcelable.Creator.
  4. Наконец, создайте файл .aidl, в котором объявляется класс Parcelable, как показано в следующем файле Rect.aidl.

    Если вы используете собственный процесс сборки, не добавляйте в него файл .aidl. Как и заголовочный файл на языке C, этот файл .aidl не компилируется.

AIDL использует эти методы и поля в генерируемом коде для маршалинга и демаршалинга объектов.

Ниже приведен пример файла Rect.aidl, в котором создается класс Rect, поддерживающий сериализацию:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

Ниже приведен пример того, как класс Rect реализует протокол Parcelable.

Kotlin

import android.os.Parcel
import android.os.Parcelable

class Rect() : Parcelable {
    var left: Int = 0
    var top: Int = 0
    var right: Int = 0
    var bottom: Int = 0

    companion object CREATOR : Parcelable.Creator<Rect> {
        override fun createFromParcel(parcel: Parcel): Rect {
            return Rect(parcel)
        }

        override fun newArray(size: Int): Array<Rect?> {
            return Array(size) { null }
        }
    }

    private constructor(inParcel: Parcel) : this() {
        readFromParcel(inParcel)
    }

    override fun writeToParcel(outParcel: Parcel, flags: Int) {
        outParcel.writeInt(left)
        outParcel.writeInt(top)
        outParcel.writeInt(right)
        outParcel.writeInt(bottom)
    }

    private fun readFromParcel(inParcel: Parcel) {
        left = inParcel.readInt()
        top = inParcel.readInt()
        right = inParcel.readInt()
        bottom = inParcel.readInt()
    }

    override fun describeContents(): Int {
        return 0
    }
}

Java

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }

    public int describeContents() {
        return 0;
    }
}

Маршалинг в классе Rect выполняется просто. Ознакомьтесь с другими методами Parcel, чтобы узнать, какие ещё значения можно записывать в Parcel.

Предупреждение. Помните о последствиях для безопасности при получении данных из других процессов. В этом случае Rect считывает четыре числа из Parcel, но вы должны убедиться, что они находятся в допустимом диапазоне значений для того, что пытается сделать вызывающий объект. Дополнительную информацию о том, как защитить приложение от вредоносного ПО, можно найти в советах по безопасности.

Методы с аргументами Bundle, содержащими Parcelable

Если метод принимает объект Bundle, который должен содержать объекты Parcelable, убедитесь, что вы задали загрузчик классов для Bundle, вызвав Bundle.setClassLoader(ClassLoader), прежде чем пытаться считать данные из Bundle. В противном случае вы столкнетесь с ошибкой ClassNotFoundException, даже если Parcelable правильно определен в вашем приложении.

Рассмотрим следующий пример файла .aidl:

// IRectInsideBundle.aidl
package com.example.android;

/** Example service interface */
interface IRectInsideBundle {
    /** Rect parcelable is stored in the bundle with key "rect". */
    void saveRect(in Bundle bundle);
}
Как показано в примере ниже, ClassLoader явно задается в Bundle перед чтением Rect:

Kotlin

private val binder = object : IRectInsideBundle.Stub() {
    override fun saveRect(bundle: Bundle) {
      bundle.classLoader = classLoader
      val rect = bundle.getParcelable<Rect>("rect")
      process(rect) // Do more with the parcelable.
    }
}

Java

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
    public void saveRect(Bundle bundle){
        bundle.setClassLoader(getClass().getClassLoader());
        Rect rect = bundle.getParcelable("rect");
        process(rect); // Do more with the parcelable.
    }
};

Вызов метода IPC

Чтобы вызвать удаленный интерфейс, определенный с помощью AIDL, выполните следующие действия в вызывающем классе:

  1. Добавьте файл .aidl в каталог проекта src/.
  2. Объявите экземпляр интерфейса IBinder, который генерируется на основе AIDL.
  3. Реализуйте ServiceConnection.
  4. Вызовите Context.bindService(), передав реализацию ServiceConnection.
  5. В реализации onServiceConnected() вы получаете экземпляр IBinder, который называется service. Вызовите YourInterfaceName.Stub.asInterface((IBinder)service), чтобы привести возвращенный параметр к типу YourInterface.
  6. Вызовите методы, определенные в интерфейсе. Всегда перехватывайте исключения DeadObjectException, которые возникают при разрыве соединения. Также перехватывайте исключения SecurityException, которые возникают, когда у двух процессов, участвующих в вызове метода IPC, есть конфликтующие определения AIDL.
  7. Чтобы отключиться, вызовите Context.unbindService() с экземпляром интерфейса.

При вызове сервиса IPC учитывайте следующее:

  • Объекты подсчитываются по ссылкам в разных процессах.
  • В качестве аргументов метода можно отправлять анонимные объекты.

Дополнительную информацию о привязке к сервису можно найти в обзоре привязанных сервисов.

Ниже приведен образец кода, демонстрирующий вызов сервиса, созданного с помощью AIDL, взятый из примера Remote Service в проекте ApiDemos.

Kotlin

private const val BUMP_MSG = 1

class Binding : Activity() {

    /** The primary interface you call on the service.  */
    private var mService: IRemoteService? = null

    /** Another interface you use on the service.  */
    internal var secondaryService: ISecondary? = null

    private lateinit var killButton: Button
    private lateinit var callbackText: TextView
    private lateinit var handler: InternalHandler

    private var isBound: Boolean = false

    /**
     * Class for interacting with the main interface of the service.
     */
    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // This is called when the connection with the service is
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service)
            killButton.isEnabled = true
            callbackText.text = "Attached."

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService?.registerCallback(mCallback)
            } catch (e: RemoteException) {
                // In this case, the service crashes before we can
                // do anything with it. We can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_connected,
                    Toast.LENGTH_SHORT
            ).show()
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // This is called when the connection with the service is
            // unexpectedly disconnected&mdash;that is, its process crashed.
            mService = null
            killButton.isEnabled = false
            callbackText.text = "Disconnected."

            // As part of the sample, tell the user what happened.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT
            ).show()
        }
    }

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private val secondaryConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            secondaryService = ISecondary.Stub.asInterface(service)
            killButton.isEnabled = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
            secondaryService = null
            killButton.isEnabled = false
        }
    }

    private val mBindListener = View.OnClickListener {
        // Establish a couple connections with the service, binding
        // by interface names. This lets other applications be
        // installed that replace the remote service by implementing
        // the same interface.
        val intent = Intent(this@Binding, RemoteService::class.java)
        intent.action = IRemoteService::class.java.name
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        intent.action = ISecondary::class.java.name
        bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE)
        isBound = true
        callbackText.text = "Binding."
    }

    private val unbindListener = View.OnClickListener {
        if (isBound) {
            // If we have received the service, and hence registered with
            // it, then now is the time to unregister.
            try {
                mService?.unregisterCallback(mCallback)
            } catch (e: RemoteException) {
                // There is nothing special we need to do if the service
                // crashes.
            }

            // Detach our existing connection.
            unbindService(mConnection)
            unbindService(secondaryConnection)
            killButton.isEnabled = false
            isBound = false
            callbackText.text = "Unbinding."
        }
    }

    private val killListener = View.OnClickListener {
        // To kill the process hosting the service, we need to know its
        // PID.  Conveniently, the service has a call that returns
        // that information.
        try {
            secondaryService?.pid?.also { pid ->
                // Note that, though this API lets us request to
                // kill any process based on its PID, the kernel
                // still imposes standard restrictions on which PIDs you
                // can actually kill. Typically this means only
                // the process running your application and any additional
                // processes created by that app, as shown here. Packages
                // sharing a common UID are also able to kill each
                // other's processes.
                Process.killProcess(pid)
                callbackText.text = "Killed service process."
            }
        } catch (ex: RemoteException) {
            // Recover gracefully from the process hosting the
            // server dying.
            // For purposes of this sample, put up a notification.
            Toast.makeText(this@Binding, R.string.remote_call_failed, Toast.LENGTH_SHORT).show()
        }
    }

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private val mCallback = object : IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here is
         * NOT running in our main thread like most other things. So,
         * to update the UI, we need to use a Handler to hop over there.
         */
        override fun valueChanged(value: Int) {
            handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0))
        }
    }

    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to interact with it before doing anything.
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.remote_service_binding)

        // Watch for button taps.
        var button: Button = findViewById(R.id.bind)
        button.setOnClickListener(mBindListener)
        button = findViewById(R.id.unbind)
        button.setOnClickListener(unbindListener)
        killButton = findViewById(R.id.kill)
        killButton.setOnClickListener(killListener)
        killButton.isEnabled = false

        callbackText = findViewById(R.id.callback)
        callbackText.text = "Not attached."
        handler = InternalHandler(callbackText)
    }

    private class InternalHandler(
            textView: TextView,
            private val weakTextView: WeakReference<TextView> = WeakReference(textView)
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                BUMP_MSG -> weakTextView.get()?.text = "Received from service: ${msg.arg1}"
                else -> super.handleMessage(msg)
            }
        }
    }
}

Java

public static class Binding extends Activity {
    /** The primary interface we are calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary secondaryService = null;

    Button killButton;
    TextView callbackText;

    private InternalHandler handler;
    private boolean isBound;

    /**
     * Standard initialization of this activity. Set up the UI, then wait
     * for the user to interact with it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button taps.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(unbindListener);
        killButton = (Button)findViewById(R.id.kill);
        killButton.setOnClickListener(killListener);
        killButton.setEnabled(false);

        callbackText = (TextView)findViewById(R.id.callback);
        callbackText.setText("Not attached.");
        handler = new InternalHandler(callbackText);
    }

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // This is called when the connection with the service is
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service);
            killButton.setEnabled(true);
            callbackText.setText("Attached.");

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
                // In this case the service crashes before we can even
                // do anything with it. We can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                    Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service is
            // unexpectedly disconnected&mdash;that is, its process crashed.
            mService = null;
            killButton.setEnabled(false);
            callbackText.setText("Disconnected.");

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private ServiceConnection secondaryConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            secondaryService = ISecondary.Stub.asInterface(service);
            killButton.setEnabled(true);
        }

        public void onServiceDisconnected(ComponentName className) {
            secondaryService = null;
            killButton.setEnabled(false);
        }
    };

    private OnClickListener mBindListener = new OnClickListener() {
        public void onClick(View v) {
            // Establish a couple connections with the service, binding
            // by interface names. This lets other applications be
            // installed that replace the remote service by implementing
            // the same interface.
            Intent intent = new Intent(Binding.this, RemoteService.class);
            intent.setAction(IRemoteService.class.getName());
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            intent.setAction(ISecondary.class.getName());
            bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE);
            isBound = true;
            callbackText.setText("Binding.");
        }
    };

    private OnClickListener unbindListener = new OnClickListener() {
        public void onClick(View v) {
            if (isBound) {
                // If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                    try {
                        mService.unregisterCallback(mCallback);
                    } catch (RemoteException e) {
                        // There is nothing special we need to do if the service
                        // crashes.
                    }
                }

                // Detach our existing connection.
                unbindService(mConnection);
                unbindService(secondaryConnection);
                killButton.setEnabled(false);
                isBound = false;
                callbackText.setText("Unbinding.");
            }
        }
    };

    private OnClickListener killListener = new OnClickListener() {
        public void onClick(View v) {
            // To kill the process hosting our service, we need to know its
            // PID.  Conveniently, our service has a call that returns
            // that information.
            if (secondaryService != null) {
                try {
                    int pid = secondaryService.getPid();
                    // Note that, though this API lets us request to
                    // kill any process based on its PID, the kernel
                    // still imposes standard restrictions on which PIDs you
                    // can actually kill.  Typically this means only
                    // the process running your application and any additional
                    // processes created by that app as shown here. Packages
                    // sharing a common UID are also able to kill each
                    // other's processes.
                    Process.killProcess(pid);
                    callbackText.setText("Killed service process.");
                } catch (RemoteException ex) {
                    // Recover gracefully from the process hosting the
                    // server dying.
                    // For purposes of this sample, put up a notification.
                    Toast.makeText(Binding.this,
                            R.string.remote_call_failed,
                            Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here is
         * NOT running in our main thread like most other things. So,
         * to update the UI, we need to use a Handler to hop over there.
         */
        public void valueChanged(int value) {
            handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0));
        }
    };

    private static final int BUMP_MSG = 1;

    private static class InternalHandler extends Handler {
        private final WeakReference<TextView> weakTextView;

        InternalHandler(TextView textView) {
            weakTextView = new WeakReference<>(textView);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    TextView textView = weakTextView.get();
                    if (textView != null) {
                        textView.setText("Received from service: " + msg.arg1);
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
}