lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

Привязанные службы

Привязанная служба предоставляет интерфейс типа клиент-сервер. Привязанная служба позволяет компонентам приложения (например, операциям) взаимодействовать со службой, отправлять запросы, получать результаты и даже делать то же самое с другими процессами через IPC. Привязанная служба обычно работает, пока другой компонент приложения привязан к ней. Она не работает постоянно в фоновом режиме.

В этом документе рассказывается, как создать привязанную службу, включая привязку службы к другим компонентам приложения. Также рекомендуем обратиться к статье Службы, чтобы узнать подробнее о службах, например, об организации отправки уведомлений от службы, настройке службы на работу на переднем плане и т. д.

Основы

Привязанная служба представляет собой реализацию класса Service, которая позволяет другим приложениям привязываться к нему и взаимодействовать с ним. Чтобы обеспечить привязку службы , сначала необходимо реализовать метод обратного вызова onBind(). Этот метод возвращает объект IBinder. Он определяет программный интерфейс, с помощью которого клиенты могут взаимодействовать со службой.

Для привязки к службе клиент может вызвать метод bindService(). После привязки он должен предоставить реализацию метода ServiceConnection, который служит для отслеживания подключения к службе. Метод bindService() возвращается незамедлительно без значения, однако , когда система Android устанавливает подключение клиент-служба, она вызывает метод onServiceConnected() для ServiceConnection, чтобы выдать объект IBinder, который клиент может использовать для взаимодействия со службой.

Одновременно к службе могут подключиться сразу несколько клиентов. Однако система вызывает метод onBind() вашей службы для получения объекта IBinder только при первой привязке клиента. После чего система выдает такой же объект IBinder для любых дополнительных клиентов, которые выполняют привязку, без повторного вызова метода onBind().

Когда отменяется привязка последнего клиента от службы, система уничтожает службу (если только служба не была так же запущена методом startService()).

Самую важную роль в реализации привязанной службы играет определение интерфейса, который возвращает ваш метод обратного вызова onBind(). Существует несколько различных способов определения интерфейса IBinder службы. Каждый из них рассматривается в следующем разделе.

Создание привязанной службы

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

Расширение класса Binder
Если служба является частной и предоставляется в рамках вашего собственного приложения, а также выполняется в том же процессе, что и клиент (общий процесс), создавать интерфейс следует путем расширения класса Binder и возврата его экземпляра из метода onBind(). Клиент получает объект Binder, после чего он может использовать его для получения прямого доступа к общедоступным методам, имеющимся либо в реализации Binder, либо даже в Service.

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

Использование объекта Messenger
Если необходимо, чтобы интерфейс службы был доступен для разных процессов, его можно создать с помощью объекта Messenger. Таким образом, служба определяет объект Handler, соответствующий различным типам объектов Message. Этот объект Handler является основой для объекта Messenger, который, в свою очередь, предоставляет клиенту объект IBinder, благодаря чему последний может отправлять команды в службу с помощью объектов Message. Кроме того, клиент может определить объект Messenger для самого себя, что позволяет службе возвращать сообщения клиенту.

Это самый простой способ организовать взаимодействие процессов, поскольку Messenger организует очередь всех запросов в рамках одного потока, поэтому вам не нужно делать свою службу потокобезопасной.

Использование языка AIDL
AIDL (Android Interface Definition Language) выполняет всю работу по разделению объектов на примитивы, которые операционная система может распознать и распределить по процессам для организации взаимодействия между ними (IPC). Предыдущий способ с использованием объекта Messenger фактически основан на AIDL, поскольку это его базовая структура. Как уже упоминалось выше, объект Messenger создает очередь из всех запросов клиентов в рамках одного потока, поэтому служба одновременно получает только один запрос. Однако, если необходимо, чтобы служба обрабатывала одновременно сразу несколько запросов, можно использовать AIDL напрямую. В таком случае ваша служба должна поддерживать многопоточность и должна быть потокобезопасной.

Чтобы использовать AIDL напрямую, необходимо создать файл .aidl, который определяет программный интерфейс. Этот файл используется инструментами SDK Android для создания абстрактного класса, который реализует интерфейс и обеспечивает взаимодействие процессов, и который в дальнейшем можно расширить в службе.

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

Расширение класса Binder

Если ваша служба используется только локальным приложением и не взаимодействует с разными процессами, можно реализовать собственный класс Binder, с помощью которого клиент получает прямой доступ к общедоступным методам в службе.

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

Вот как это сделать:

  1. Создайте в вашей службе экземпляр класса Binder со следующими характеристиками:
    • экземпляр содержит общедоступные методы, которые может вызывать клиент; либо
    • экземпляр возвращает текущий экземпляр класса Service, содержащий общедоступные методы, которые может вызывать клиент; или
    • экземпляр возвращает экземпляр другого класса, размещенного в службе, содержащий общедоступные методы, которые может вызывать клиент.
  2. Верните этот экземпляр класса Binder из метода обратного вызова onBind().
  3. В клиенте получите класс Binder от метода обратного вызова onServiceConnected() и выполните вызовы к привязанной службе с помощью предоставленных методов.

Примечание. Служба и клиент должны выполняться в одном и том же приложении , поскольку в этом случае клиент может транслировать возвращенный объект и надлежащим образом вызывать его API-интерфейсы. Кроме того, служба и клиент должны выполняться в рамках одного и того же процесса, поскольку этот способ не подразумевает какого-либо распределения по процессам.

Ниже представлен пример службы, которая предоставляет клиентам доступ к методам посредством реализации класса Binder:

public class LocalService extends Service {
    // Binder given to clients
    private final IBinder mBinder = new LocalBinder();
    // Random number generator
    private final Random mGenerator = new Random();

    /**
     * Class used for the client Binder.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /** method for clients */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}

Объект LocalBinder предоставляет для клиентов метод getService(), чтобы они могли получить текущий экземпляр класса LocalService. Благодаря этому клиенты могут вызывать общедоступные методы в службе. Например, клиенты могут вызвать метод getRandomNumber() из службы.

Ниже представлен пример операции, которая выполняет привязку к классу LocalService и вызывает метод getRandomNumber() при нажатии кнопки:

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /** Called when a button is clicked (the button in the layout file attaches to
      * this method with the android:onClick attribute) */
    public void onButtonClick(View v) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call were something that might hang, then this request should
            // occur in a separate thread to avoid slowing down the activity performance.
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** Defines callbacks for service binding, passed to bindService() */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}

В примере выше показано, как клиент привязывается к службе с помощью реализации ServiceConnection и обратного вызова onServiceConnected(). В следующем разделе представлена более подробная информация об этом процессе привязки к службе.

Примечание. В примере выше не выполняется явная отмена привязки к службе, однако всем клиентам следует отменять привязку в соответствующие сроки (например, когда операция приостанавливается).

Примеры кода представлены в статьях, посвященных классам LocalService.java и LocalServiceActivities.java, в ApiDemos.

Использование объекта Messenger

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

Вот краткий обзор того, как следует использовать объект Messenger:

  • Служба реализует объект Handler, который получает обратный вызов для каждого вызова от клиента.
  • Объект Handler используется для создания объекта Messenger (который является ссылкой на объект Handler).
  • Объект Messenger создает объект IBinder, который служба возвращает клиентам из метода onBind().
  • Клиенты используют полученный объект IBinder для создания экземпляра объекта Messenger (который ссылается на объект Handler службы), используемого клиентом для отправки объектов Message в службу.
  • Служба получает каждый объект Message в своем объекте Handler — в частности, в методе handleMessage().

Таким образом, для клиента отсутствуют «методы» для отправки вызова службе. Вместо этого клиент отправляет «сообщения» (объекты Message), которые служба получает в своем объекте Handler.

Ниже представлен пример службы, которая использует интерфейс Messenger:

public class MessengerService extends Service {
    /** Command to the service to display a message */
    static final int MSG_SAY_HELLO = 1;

    /**
     * Handler of incoming messages from clients.
     */
    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        return mMessenger.getBinder();
    }
}

Обратите внимание, что метод handleMessage() в объекте Handler — это место, где служба получает входящие объекты Message и решает, что делать дальше, руководствуясь элементом what.

Клиенту требуется лишь создать объект Messenger на основе объекта IBinder, возвращенного службой, и отправить сообщение с помощью метода send(). Ниже представлен пример того, как простая операция выполняет привязку к службе и отправляет ей сообщение MSG_SAY_HELLO:

public class ActivityMessenger extends Activity {
    /** Messenger for communicating with the service. */
    Messenger mService = null;

    /** Flag indicating whether we have called bind on the service. */
    boolean mBound;

    /**
     * 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 has been
            // established, giving us the object we can use to
            // interact with the service.  We are communicating with the
            // service using a Messenger, so here we get a client-side
            // representation of that from the raw IBinder object.
            mService = new Messenger(service);
            mBound = true;
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            mBound = false;
        }
    };

    public void sayHello(View v) {
        if (!mBound) return;
        // Create and send a message to the service, using a supported 'what' value
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        try {
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to the service
        bindService(new Intent(this, MessengerService.class), mConnection,
            Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
}

Обратите внимание, что в этом примере не показано, как служба отвечает клиенту. Если требуется, чтобы служба реагировала, необходимо создать объект Messenger и в клиенте. Затем, когда клиент получает обратный вызов onServiceConnected(), она отправляет в службу объект Message, который включает объект Messenger клиента в качестве значения параметра replyTo метода send().

Пример организации двустороннего обмена сообщениями приведен в примерах кода MessengerService.java (служба) и MessengerServiceActivities.java (клиент).

Привязка к службе

Для привязки к службе компоненты приложения (клиенты) могут использовать метод bindService(). После этого система Android вызывает метод onBind() службы, который возвращает объект IBinder для взаимодействия со службой.

Привязка выполняется асинхронно. bindService() возвращается сразу же и не возвращает клиенту объект IBinder. Для получения объекта IBinder клиенту необходимо создать экземпляр ServiceConnection и передать его в метод bindService(). Интерфейс ServiceConnection включает метод обратного вызова, который система использует для того, чтобы выдать объект IBinder.

Примечание. Выполнить привязку к службе могут только операции, другие службы и поставщики контента — вы не можете самостоятельно выполнить привязку к службе из ресивера.

Поэтому для привязки к службе из клиента необходимо выполнить указанные ниже действия.

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

    Ваша реализация должна переопределять два метода обратного вызова:

    onServiceConnected()
    Система вызывает этот метод, чтобы выдать объект IBinder, возвращенный методом onBind()службы.
    onServiceDisconnected()
    Система Android вызывает этот метод в случае непредвиденной потери подключения к службе, например при сбое в работе службы или в случае ее завершения. Этот метод не вызывается, когда клиент отменяет привязку.
  2. Вызовите метод bindService(), передав в него реализацию интерфейса ServiceConnection.
  3. Когда система вызывает ваш метод обратного вызова onServiceConnected(), вы можете приступить к выполнению вызовов к службе с помощью методов, определенных интерфейсом.
  4. Чтобы отключиться от службы, вызовите метод unbindService().

    В случае уничтожения клиента выполняется отмена его привязки к службе, однако вам всегда следует отменять привязку по завершении взаимодействия со службой или в случае приостановки операции, чтобы служба могла завершить свою работу, когда она не используется. (Более подробно подходящее время для привязки и ее отмены рассматриваются далее в этом документе).

Ниже представлен пример фрагмента кода для подключения клиента к созданной выше службе путем расширения класса Binder — клиенту нужно лишь передать возвращенный объект IBinder в класс LocalService и запросить экземпляр LocalService:

LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Because we have bound to an explicit
        // service that is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false;
    }
};

С помощью этого интерфейса ServiceConnection клиент может выполнить привязку к службе, передав ее в метод bindService(). Например:

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  • Первый параметр в методе bindService() представляет собой объект Intent, который явным образом именует службу для привязки (хотя переход может быть и неявным).
  • Второй параметр — это объект ServiceConnection.
  • Третий параметр представляет собой флаг, указывающий параметры привязки. Обычно им является BIND_AUTO_CREATE, создающий службу, если она уже не выполняется. Другие возможные значения: BIND_DEBUG_UNBIND и BIND_NOT_FOREGROUND или 0, если значение отсутствует.

Дополнительные примечания

Ниже представлены некоторые важные замечания о привязке к службе.

  • Всегда отлавливайте исключения DeadObjectException, которые выдаются при возникновении сбоя подключения. Это единственное исключение, которое выдается удаленными методами.
  • Для объектов всегда учитываются ссылки на них в процессах.
  • Обычно необходимо связать привязку и ее отмену во время сопоставления моментов подключения и отключения в жизненном цикле клиента. Например:
    • Если взаимодействие со службой требуется лишь в то время, когда операция отображается, привязку необходимо выполнить во время метода onStart(), а отмену привязки — во время выполнения метода onStop().
    • Если необходимо, чтобы операция получала ответы даже в случае ее остановки во время работы в фоновом режиме, то привязку можно выполнить во время onCreate(), а отмену привязки — во время выполнения onDestroy(). Однако следует помнить, что такой способ подразумевает, что вашей операции необходимо использовать службу все время, пока она выполняется (даже если она выполняется в фоновом режиме). Поэтому, если служба находится в другом процессе, вы тем самым утяжеляете процесс, а это повышает вероятность того, что система завершит его.

    Примечание. Обычно не следует выполнять привязку или отменять ее во время выполнения методов onResume() и onPause() вашей операции, поскольку такие обратные вызовы происходят при каждом переходе из одного состояния в другое, а обработка данных, выполняемая при таких переходах, должна быть минимальной. Кроме того, если к одной и той же службе привязано несколько операций в вашем приложении, и имеется переход между двумя этими операциями, служба может быть уничтожена и создана повторно, поскольку текущая операция выполняет отмену привязки (во время приостановки) до того, как следующая служба выполнит привязку (во время возобновления). (Подробные сведения о согласовании жизненных циклов операций при таких переходах представлены в статье Операции.)

Пример кода, в котором показан порядок привязки к службе, см. в статье, посвященной классу RemoteService.java, в ApiDemos.

Управление жизненным циклом привязанной службы

Когда выполняется отмена привязки службы ко всем клиентам, система Android уничтожает такую службу (если она не была запущена вместе с onStartCommand()). В таком случае вам не нужно управлять жизненным циклом своей службы, если она исключительно привязанная служба — система Android управляет ей за вас на основании привязки службы к любым другим клиентам.

Однако, если вы решите реализовать метод обратного вызова onStartCommand(), вам необходимо явным образом остановить службу, поскольку в этом случае она считается запущенной. В таком случае служба выполняется до тех пор, пока сама не остановит свою работу с помощью метода stopSelf() или до тех пор, пока другой компонент не вызовет метод stopService(), независимо от привязки службы к каким-либо клиентам.

Кроме того, если ваша служба запущена и принимает привязку, то при вызове системой вашего метода onUnbind() вы также можете вернуть true, если желаете получить вызов к onRebind() при следующей привязке к службе (вместо получения вызова к методу onBind()). Метод onRebind() возвращает значение void, однако клиент по-прежнему получает объект IBinder в своем методе обратного вызова onServiceConnected(). На рисунке 1 ниже иллюстрируется логика жизненного цикла такого рода.

Рисунок 1. Жизненный цикл запущенной службы, для которой выполняется привязка.

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