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

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

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

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

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

  • Вызовы, сделанные из локального процесса, выполняются в том же потоке, который выполняет вызов. Если это ваш основной поток пользовательского интерфейса, этот поток продолжает выполняться в интерфейсе 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 поддерживает следующие типы данных:

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

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

  • Map

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

Вы должны включить оператор 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 , с использованием анонимного экземпляра:

Котлин

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.
    }
}

Ява

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 синхронны. Если вы знаете, что службе требуется более нескольких миллисекунд для выполнения запроса, не вызывайте ее из основного потока действия. Это может привести к зависанию приложения, в результате чего Android отобразит диалоговое окно «Приложение не отвечает». Вызовите его из отдельного потока в клиенте.
  • Только типы исключений, перечисленные в справочной документации для Parcel.writeException() отправляются обратно вызывающей стороне.

Откройте интерфейс для клиентов

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

Котлин

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.
        }
    }
}

Ява

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.
        }
    };
}

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

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

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

Котлин

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
    }
}

Ява

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 .

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 , в котором объявляется ваш разделяемый класс, как показано в следующем файле 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 .

Котлин

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
    }
}

Ява

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, содержащими Parcelables

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

Например, рассмотрим следующий пример файла .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 :

Котлин

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.
    }
}

Ява

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, взятый из примера удаленной службы в проекте ApiDemos.

Котлин

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)
            }
        }
    }
}

Ява

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);
            }
        }
    }
}