Язык определения интерфейса 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, выполните следующие действия, описанные в следующих разделах:
- Создайте файл
.aidl
Этот файл определяет программный интерфейс с сигнатурами методов.
- Реализовать интерфейс
Инструменты Android SDK генерируют интерфейс на языке программирования Java на основе вашего файла
.aidl
. Этот интерфейс имеет внутренний абстрактный классStub
, который расширяетBinder
и реализует методы из вашего интерфейса AIDL. Вам необходимо расширить классStub
и реализовать методы. - Предоставьте интерфейс клиентам
Реализуйте
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, или объявленных вами parcelables.List
может быть опционально использован как параметризованный класс типа, напримерList<String>
. Фактический конкретный класс, который получает другая сторона, всегда являетсяArrayList
, хотя метод генерируется для использования интерфейсаList
. -
Map
Все элементы в
Map
должны быть одним из поддерживаемых типов данных в этом списке или одним из других интерфейсов, сгенерированных AIDL, или объявленных вами parcelables. Параметризованные карты типов, такие какMap<String,Integer>
, не поддерживаются. Фактический конкретный класс, который получает другая сторона, всегда являетсяHashMap
, хотя метод генерируется для использования интерфейсаMap
. Рассмотрите возможность использованияBundle
в качестве альтернативыMap
.
Вам необходимо включить оператор import
для каждого дополнительного типа, не перечисленного ранее, даже если они определены в том же пакете, что и ваш интерфейс.
При определении интерфейса вашего сервиса имейте в виду, что:
- Методы могут принимать ноль или более параметров и могут возвращать значение или void.
- Все непримитивные параметры требуют тега направления, указывающего, в каком направлении передаются данные:
in
,out
илиinout
(см. пример ниже).Примитивы,
String
,IBinder
и интерфейсы, сгенерированные AIDL,in
по умолчанию и не могут быть другими.Внимание: Ограничьте направление тем, что действительно необходимо, поскольку упорядочивание параметров обходится дорого.
- Все комментарии к коду, включенные в файл
.aidl
, включаются в сгенерированный интерфейсIBinder
, за исключением комментариев перед операторами импорта и пакета. - Константы String и int могут быть определены в интерфейсе 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, инкрементальная сборка генерирует класс binder практически сразу. Если вы не используете Android Studio, инструмент Gradle генерирует класс binder в следующий раз, когда вы собираете свое приложение. Соберите свой проект с помощью gradle assembleDebug
или gradle assembleRelease
как только вы закончите писать файл .aidl
, чтобы ваш код мог быть связан с сгенерированным классом.
Реализовать интерфейс
При создании приложения инструменты Android SDK генерируют файл интерфейса .java
, названный в честь вашего файла .aidl
. Сгенерированный интерфейс включает подкласс с именем Stub
, который является абстрактной реализацией его родительского интерфейса, например YourInterface.Stub
, и объявляет все методы из файла .aidl
.
Примечание: Stub
также определяет несколько вспомогательных методов, наиболее заметный из которых asInterface()
, который принимает IBinder
, обычно тот, который передается в метод обратного вызова onServiceConnected()
клиента, и возвращает экземпляр интерфейса stub. Для получения более подробной информации о том, как сделать это приведение, см. раздел Вызов метода 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 и другие parcelables, также поддерживаются здесь. Это позволяет избежать дополнительной работы по ручному написанию кода маршалинга и пользовательского класса. Однако это также создает голую структуру. Если требуются пользовательские методы доступа или другая функциональность, реализуйте 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
, выполните следующие действия:
- Сделайте так, чтобы ваш класс реализовал интерфейс
Parcelable
. - Реализуйте
writeToParcel
, который берет текущее состояние объекта и записывает его вParcel
. - Добавьте в свой класс статическое поле
CREATOR
, которое является объектом, реализующим интерфейсParcelable.Creator
. - Наконец, создайте файл
.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
.
Котлин
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
, который, как ожидается, будет содержать parcelables, убедитесь, что вы установили загрузчик классов 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
: Котлин
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, выполните следующие действия в вызывающем классе:
- Включите файл
.aidl
в каталогsrc/
проекта. - Объявите экземпляр интерфейса
IBinder
, который генерируется на основе AIDL. - Реализовать
ServiceConnection
. - Вызовите
Context.bindService()
, передав реализациюServiceConnection
. - В вашей реализации
onServiceConnected()
вы получаете экземплярIBinder
, называемыйservice
. ВызовитеYourInterfaceName .Stub.asInterface((IBinder) service )
чтобы привести возвращаемый параметр к типуYourInterface
. - Вызывайте методы, которые вы определили в своем интерфейсе. Всегда перехватывайте исключения
DeadObjectException
, которые выбрасываются при разрыве соединения. Также перехватывайте исключенияSecurityException
, которые выбрасываются, когда два процесса, участвующие в вызове метода IPC, имеют конфликтующие определения AIDL. - Чтобы отключиться, вызовите
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—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—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); } } } }