Главная » Разработка для Android » Переопределения и обратные вызовы – Android

0

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

Чтобы создать точку расширения для добавления обратного вызова, в классе определяются две вещи. Во-первых, определяется интерфейс Java (обычно его название заканчивается на Handler, Callback или Listener). Этот интерфейс описывает, но не реализует действие обратного вызова. Кроме того, класс определяет метод-установщик, принимающий в качестве аргумента тот объект, который реализует интерфейс.

Рассмотрим приложение, в котором должен обрабатываться текстовый ввод, получаемый от пользователя. Ввод, редактирование и отображение текста, разумеется, требуют большого и сложного набора классов пользовательского интерфейса. Но приложение в то же время не должно решать большинство связанных с ними задач. Вместо этого в компоновку приложения добавляется библиотечный ви-джет – скажем, EditText. Фреймворк обеспечивает инстанцирование виджета, отображение его на экране, обновление содержимого по мере того, как пользователь вводит текст, и т. д. Здесь происходит все, за исключением задач, для решения которых и предназначено ваше приложение (то есть за исключением обработки в коде вводимого текста). Здесь и применяется обратный вызов.

В документации по системе Android указано, что объект Edi tText определяет метод addTextChangedListener, принимающий в качестве аргумента объект Textwatcher. Данный объект определяет методы, активируемые, если изменяется текст, который содержится в виджете. Образец кода приложения может выглядеть так:

MyModel – это центральная часть вашего приложения. Здесь программа принимает текст, вводимый пользователем, и совершает над этим текстом определенные полезные операции. При создании модель MyModel получает TextBox, то есть получает поле, в которое пользователь будет вводить текст. На данный момент вы, наверное, уже успели набить руку в синтаксическом разборе такого кода. В своем конструкторе MyModel создает новую анонимную реализацию интерфейса TextWatcher. Здесь же реализуются три метода, необходимые для этого интерфейса. Два из них, onTextChanged и beforeTextChanged, ничего не делают. А вот третий метод, afterTextChanged, вызывает метод handleTextChange, относящийся к MyModel.

Вся эта система отлично работает. Возможно, два метода, beforeTextChanged и onTextChanged, которые не используются в данном конкретном приложении, немного захламляют код. Однако за исключением этого момента в коде очень красиво реализуется разделение функций. Модель MyModel не представляет, как TextView отображает текст, где он выводится на экране и как в поле попадает тот или иной текст. Маленький промежуточный класс (relay class), анонимный экземпляр TextWatcher, просто передает измененный текст между видом и MyModel. Реализация модели MyModel занята лишь теми событиями, которые происходят при изменении текста.

Этот процесс, в ходе которого скрепляются пользовательский интерфейс и его поведения, часто называется подключением (wiring up). Хотя данный процесс достаточно мощный, с ним также связано множество ограничений. Клиентский код – то есть код, который регистрируется на получение обратного вызова, – не может влиять на поведение вызывающего элемента. К тому же клиент не получает никакой информации о состоянии, кроме параметров, передаваемых в вызове. Тип интерфейса (в данном случае TextWatcher) представляет собой явный контракт между отправителем обратного вызова и клиентом.

Существует действие, при помощи которого клиент обратного вызова может влиять на вызывающий элемент: клиент может отказать в отклике. Клиентский код должен воспринимать обратный вызов как обычное уведомление, а не как попытку запустить какую-либо длительную встроенную обработку (inline processing). Если требуется выполнить какой-либо значительный кусок работы (то есть более нескольких сотен команд или любые вызовы, которые могут замедлить функционирование служб, в частности служб файловой системы или сетевых служб), то эти команды нужно поставить в очередь и выполнить позже, возможно, в другом потоке. Мы подробно поговорим о том, как это делается, в подразделе «AsyncTask и поток пользовательского интерфейса» раздела «Параллелизм в Android».

Аналогично служба, которая пытается поддерживать несколько клиентов обратных вызовов, может испытывать нехватку ресурсов процессора, даже если все клиенты работают относительно хорошо. В то время как addTextChangedListener поддерживает возможность подписки для нескольких клиентов, многие обратные вызовы, входящие в состав библиотеки Android, поддерживают только один обратный вызов. При работе с такими обратными вызовами (например, setOnKeyListener) назначение нового клиента для определенного обратного вызова, направляемого к конкретному объекту, приводит к замене предыдущего клиента, стоявшего на этом месте. Зарегистрированный ранее клиент больше не будет получать уведомлений об обратных вызовах. На самом деле он не получит уведомления даже о том, что больше не является клиентом. С этого момента все уведомления будет получать клиент, зарегистрированный последним. Такое ограничение, существующее в коде, помогает решить очень насущную проблему – таким образом исключается ситуация, в которой обратный вызов поддерживал бы неограниченное количество клиентов. Если в вашем коде приходится распределять уведомления сразу между множеством получателей, вам придется придумать для этого такой способ, который будет безопасен в контексте вашего приложения.

Паттерн обратного вызова повсеместно встречается в библиотеках Android. Поскольку эта идиома известна всем разработчикам Android, вам тоже следует писать свой код по такому принципу. Когда какому-либо классу требуются уведомления об изменениях, происходящих в других классах, – особенно при динамическом изменении ассоциаций во время исполнения – попробуйте реализовать такое отношение в виде обратного вызова. Если отношение не является динамическим, воспользуйтесь внедрением зависимости (dependency injection), то есть примените параметр конструктора и финальное поле, чтобы сделать требуемое отношение постоянным.

Источник: Android. Программирование на Java для нового поколения мобильных устройств

По теме:

  • Комментарии