Язык определения интерфейса 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 поддерживает следующие типы данных:
- Все примитивные типы языка программирования Java (например,
int
,long
,char
,boolean
и т. д.). - Массивы примитивных типов, например
int[]
-
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
, выполните следующие действия:
- Сделайте так, чтобы ваш класс реализовал интерфейс
Parcelable
. - Реализуйте
writeToParcel
, который принимает текущее состояние объекта и записывает его вParcel
. - Добавьте в свой класс статическое поле
CREATOR
, которое является объектом, реализующим интерфейсParcelable.Creator
. - Наконец, создайте файл
.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, выполните следующие шаги в вызывающем классе:
- Включите файл
.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); } } } }