Как известно, RxJava идеально подходит для решения двух задач: обработки потоков событий и работы с асинхронными методами. В одном из предыдущих постов я показал, как можно построить цепочку операторов, обрабатывающую поток событий от сенсора. А сегодня я хочу продемонстрировать, как RxJava применяется для работы с существенно асинхронным API. В качестве такого API я выбрал Camera2 API.  Ниже будет показан пример использования Camera2 API, который пока довольно слабо задокументирован и изучен сообществом. Для его укрощения будет использована RxJava2. Вторая версия этой популярной библиотеки вышла сравнительно недавно, и примеров на ней тоже немного.  Для кого этот пост? Я рассчитываю, что читатель – умудрённый опытом, но всё ещё любознательный Android-разработчик. Очень желательны базовые знания о реактивном программировании (хорошее введение – здесь) и понимание Marble Diagrams. Пост будет полезен тем, кто хочет проникнуться реактивным подходом, а также тем, кто хочет использовать Camera2 API в своих проектах. Предупреждаю, будет много кода!  Исходники проекта можно найти на GitHub. Добавим сторонние зависимости в наш проект. При работе с RxJava совершенно необходима поддержка лямбд – иначе код будет выглядеть просто ужасно. Так что если вы ещё не перешли на Android Studio 3.0, добавим Retrolambda в наш проект.  Теперь можно поднять версию языка до 8, что обеспечит поддержку лямбд. Полные инструкции. Актуальную версию, полные инструкции и документацию ищите тут. Полезная библиотека при использовании RxJava на Android. В основном используется ради AndroidSchedulers. Репозиторий. В своё время я участвовал в code review модуля, написанного с использованием Camera1 API, и был неприятно удивлён неизбежными в силу дизайна API concurrency issues. Видимо, в Google тоже осознали проблему и задепрекейтили первую версию API. Взамен предлагается использовать Camera2 API. Вторая версия доступна на Android Lollipop и новее.  Давайте же на него посмотрим. Google проделал хорошую работу над ошибками в плане организации потоков. Все операции выполняются асинхронно, уведомляя о результатах через колбеки. Причём, передавая соответствующий Handler, можно выбрать поток, в котором будут вызываться методы колбеков.  Google предлагает пример приложения Camera2Basic. Это довольно наивная реализация, но помогает начать разбираться с API. Посмотрим, сможем ли мы сделать более изящное решение, используя реактивный подход. Если кратко, то последовательность действий для получения снимка такова:  Прежде всего нам понадобится CameraManager. Этот класс позволяет получать информацию о существующих в системе камерах и подключаться к ним. Камер может быть несколько, в смартфонах обычно их две: фронтальная и тыловая. Получаем список камер. Вот так сурово – просто список строковых айдишников.  Теперь получим список характеристик для каждой камеры. CameraCharacteristics содержит огромное количество ключей, по которым можно получать информацию о камере. Чаще всего на этапе выбора камеры смотрят на то, куда направлена камера. Для этого необходимо получить значение по ключу CameraCharacteristics.LENS_FACING. Камера может быть фронтальной (CameraCharacteristics.LENS_FACING_FRONT), тыловой (CameraCharacteristics.LENS_FACING_BACK) или подключаемой (CameraCharacteristics.LENS_FACING_EXTERNAL).  Функция выбора камеры с предпочтением по ориентации может выглядеть примерно так: Отлично, теперь у нас есть id камеры нужной ориентации (или любой другой, если нужной не нашлось). Пока всё довольно просто, никаких асинхронных действий. Мы подходим к асинхронным методам API. Каждый из них мы превратим в Observable с помощью метода create. Устройство перед использованием нужно открыть с помощью метода CameraManager.openCamera. В этот метод передаём id выбранной камеры, колбек для получения асинхронного результата и Handler, если мы хотим, чтобы методы колбека вызывались в потоке этого Handler. Вот мы и столкнулись с первым асинхронным методом. Оно и понятно, ведь инициализация устройства – длительный и дорогой процесс. Давайте взглянем на CameraDevice.StateCallback.  В реактивном мире эти методы будут соответствовать событиям. Давайте сделаем Observable, который будет генерировать события, когда API камеры будет вызывать onOpened, onClosed, onDisconnected. Чтобы мы могли различать эти события, создадим enum: А чтобы в реактивном потоке (тут и далее я буду называть реактивным потоком последовательность реактивных операторов – не путать со Thread) была возможность что-то сделать с устройством, мы добавим в генерируемое событие ссылку на CameraDevice. Самый простой способ – генерировать Pair<DeviceStateEvents, CameraDevice>. Для создания Observable воспользуемся методом create (напомню, мы используем RxJava2, так что теперь нам совсем не стыдно это делать). Вот сигнатура метода create: То есть нам нужно передать в него объект, реализующий интерфейс ObservableOnSubscribe<T>. Этот интерфейс содержит всего один метод который вызывается каждый раз, когда Observer подписывается (subscribe) на наш Observable.  Посмотрим, что такое ObservableEmitter. Уже хорошо. С помощью методов setDisposable/setCancellable можно задать действие, которое будет выполнено, когда от нашего Observable отпишутся. Это крайне полезно, если при создании Observable мы открывали ресурс, который надо закрывать. Мы могли бы создать Disposable, в котором закрывать устройство при unsubscribe, но мы хотим реагировать на событие onClosed, поэтому делать этого не будем. Метод isDisposed позволяет проверять, подписан ли кто-то ещё на наш Observable. Заметим, что ObservableEmitter расширяет интерфейс Emitter. Вот эти методы нам и нужны! Мы будем вызывать onNext каждый раз, когда Camera API будет вызывать колбеки интерфейса CameraDevice.StateCallback onOpened / onClosed / onDisconnected; и мы будем вызывать onError, когда Camera API будет вызывать колбек onError. Итак, применим наши знания. Mетод, создающий Observable, может выглядеть так (ради читабельности я убрал проверки на isDisposed(), полный код со скучными проверками смотрите на GitHub): Супер! Мы только что стали чуть более реактивными! Как я уже говорил, все методы Camera2 API принимают Handler как один из параметров. Передавая null, мы будем получать вызовы колбеков в текущем потоке. В нашем случае это поток, в котором был вызван subscribe, то есть Main Thread.  Теперь, когда у нас есть CameraDevice, можно открыть CaptureSession. Не будем медлить! Для этого воспользуемся методом CameraDevice.createCaptureSession. Вот его сигнатура: На вход подаётся список Surface (где его взять, разберёмся чуть позже) и CameraCaptureSession.StateCallback. Давайте посмотрим, какие в нём есть методы.   Богато! Но мы уже знаем, как побеждать колбеки. Создадим Observable, который будет генерировать события, когда Camera API будет вызывать эти методы. Чтобы их различать, создадим enum. А чтобы в реактивном потоке был объект CameraCaptureSession, будем генерировать не просто CaptureSessionStateEvent, а Pair<CaptureSessionStateEvents, CameraCaptureSession>. Вот как может выглядеть код метода, создающего такой Observable (проверки снова убраны для читабельности): Для того чтобы на экране появилась живая картинка с камеры, необходимо постоянно получать новые изображения с устройства и передавать их для отображения. Для этого в API есть удобный метод  CameraCaptureSession.setRepeatingRequest. Применяем уже знакомый нам приём, чтобы сделать эту операцию реактивной. Смотрим на интерфейс CameraCaptureSession.CaptureCallback.  Опять же, мы хотим различать генерируемые события и для этого создадим enum. Мы видим, что в методы передаётся достаточно много информации, которую мы хотим иметь в реактивном потоке, в том числе CameraCaptureSession, CaptureRequest, CaptureResult, поэтому просто Pair<> нам уже не подойдёт – создадим POJO: Создание CameraCaptureSession.CaptureCallback вынесем в отдельный метод. Из всех этих сообщений нам интересны onCaptureCompleted/onCaptureFailed, остальные события игнорируем. Если они понадобятся вам в ваших проектах, их несложно добавить. Теперь всё готово для создания Observable. На самом деле, этот шаг полностью аналогичен предыдущему, только мы делаем не повторяющийся запрос, а единичный. Для этого воспользуемся методом  CameraCaptureSession.capture. Он принимает точно такие же параметры, так что мы сможем использовать функцию, определённую выше, для создания CaptureCallback.  Cameara2 API позволяет в запросе передавать список Surface, которые будут использованы для записи данных с устройства. Нам потребуются два Surface: Для отображения preview на экране мы воспользуемся TextureView. Для того чтобы получить Surface из TextureView, предлагается воспользоваться методом TextureView.setSurfaceTextureListener.
TextureView уведомит listener, когда Surface будет готов к использованию.  Давайте на этот раз создадим PublishSubject, который будет генерировать события, когда TextureView вызывает методы listener. Используя PublishSubject, мы избегаем возможных проблем с множественным subscribe. Мы устанавливаем SurfaceTextureListener один раз в onCreate и дальше живём спокойно. PublishSubject позволяет подписываться на него сколько угодно раз и раздает события всем подписавшимся.  При использовании Camera2 API существует тонкость, связанная с невозможностью явно задать размер изображения, – камера сама выбирает одно из поддерживаемых ею разрешений на основании размеров, переданных ей Surface. Поэтому придётся пойти на такой трюк: выясняем список поддерживаемых камерой размеров изображения, выбираем наиболее приглянувшийся и затем устанавливаем размер буфера в точности таким же.  При этом, если мы хотим видеть изображение с сохранением пропорций, необходимо задать нужные пропорции нашему TextureView. Для этого мы его расширим и переопределим метод onMeasure: Для того чтобы сохранить изображение из Surface в файл, воспользуемся классом ImageReader.
Несколько слов о выборе размера для ImageReader. Во-первых, мы должны выбрать его из поддерживаемых камерой. Во-вторых, соотношение сторон должно совпадать с тем, что мы выбрали для preview. Чтобы мы могли получать уведомления от ImageReader о готовности изображения, воспользуемся методом setOnImageAvailableListener Передаваемый listener реализует всего один метод onImageAvailable.
Каждый раз, когда Camera API будет записывать изображение в Surface, предоставленную нашим ImageReader, он будет вызывать этот колбек. Сделаем эту операцию реактивной: создадим Observable, который будет генерировать сообщение каждый раз, когда ImageReader будет готов предоставить изображение. Обратите внимание, тут мы воспользовались методом ObservableEmitter.setCancellable, чтобы удалять listener, когда от Observable отписываются. Запись в файл – длительная операция, сделаем её реактивной с помощью метода fromCallable. Теперь мы можем задать такую последовательность действий: когда в ImageReader появляется готовое изображение, мы записываем его в файл в рабочем потоке Schedulers.io(), затем переключаемся в UI thread и уведомляем UI о готовности файла. Итак, мы основательно подготовились. Мы уже можем создавать Observable для основных асинхронных действий, которые требуются для работы приложения. Впереди самое интересное – конфигурирование реактивных потоков. Для разминки давайте сделаем так, чтобы камера открывалась после того, как SurfaceTexture готов к использованию. Ключевым оператором здесь выступает flatMap.  В нашем случае при получении события о готовности SurfaceTexture он выполнит функцию openCamera и пустит события от созданного ей Observable дальше в реактивный поток. Важно также понять, зачем в конце цепочки используется оператор share. Он эквивалентен цепочке операторов publish().refCount().  Если долго смотреть на эту Marble Diagram, то можно заметить, что результат весьма похож на результат использования PublishSubject. Действительно, мы решаем похожую проблему: если на наш Observable подпишутся несколько раз, мы не хотим каждый раз открывать камеру заново.  Для удобства давайте введём ещё пару Observable. openCameraObservable будет генерировать события, когда камера будет успешно открыта, а closeCameraObservable – когда она будет закрыта. Сделаем ещё один шаг: после успешного открытия камеры откроем сессию. И по аналогии создадим ещё пару Observable, сигнализирующих об успешном открытии или закрытии сессии. Наконец, мы можем задать повторяющийся запрос для отображения preview. Теперь достаточно выполнить previewObservable.subscribe() — и на экране появится живая картинка с камеры!  Небольшое отступление. Если схлопнуть все промежуточные Observable, то получится вот такая цепочка операторов: И этого достаточно для показа preview. Впечатляет, не так ли? На самом деле, у этого решения есть проблемы с закрытием ресурсов, да и снимки пока делать нельзя. Я привёл его, чтобы была видна цепочка целиком. Все промежуточные Observable понадобятся нам при составлении более сложных сценариев поведения в будущем. Для того чтобы у нас была возможность отписаться, необходимо сохранять возвращаемое методом subscribe Disposable. Удобнее всего пользоваться CompositeDisposable. В реальном коде я везде делаю mCompositeDisposable.add(...subscribe()), но сейчас я эти вызовы опускаю, чтобы вам было легче читать. Внимательный читатель, конечно, уже заметил, что мы использовали метод createPreviewBuilder, который ещё не описывался. Давайте же посмотрим, что у него внутри. Здесь мы пользуемся любезно предоставленным нам шаблоном запроса для preview, добавляем в него нашу Surface и говорим, что хотим Auto Focus, Auto Exposure и Auto White Balance (три A). Чтобы этого добиться, достаточно установить несколько флагов.  Для того чтобы получать события по нажатиям кнопок, можно воспользоваться прекрасной библиотекой RxBinding, но мы сделаем проще. Теперь набросаем план действий. Прежде всего мы хотим делать снимок тогда, когда уже начался preview (это значит, что всё готово для снимка). Для этого воспользуемся оператором combineLatest. Но он будет генерировать события постоянно при получении свежих событий от previewObservable, поэтому ограничимся первым событием. Дождёмся, пока сработают автофокус и автоэкспозиция. И, наконец, сделаем снимок. Цепочка операторов целиком выглядит так: Посмотрим, что внутри captureStillPicture. Здесь нам всё уже довольно знакомо: создаём запрос, запускаем capture – и ждём результат. Запрос конструируется из шаблона STILL_PICTURE, в него добавляется Surface для записи в файл, а также несколько волшебных флагов, сообщающих камере, что это ответственный запрос для сохранения изображения. Также задаётся информация о том, как ориентировать изображение в JPEG. Хорошие приложения всегда закрывают ресурсы, особенно такие дорогие, как камера. Давайте тоже по событию onPause будем всё закрывать. Здесь мы поочерёдно закрываем сессию и устройство, дожидаясь подтверждения от API.  Мы создали приложение, которое умеет показывать живую картинку preview и делать снимки. То есть получили вполне рабочее приложение камеры. Нераскрытыми остались вопросы ожидания срабатывания автофокуса и автовыбора экспозиции. Ответы на них обязательно появятся в следующей части.  [Update: вторая часть тут] С появлением RxJava разработчики получили в свои руки могучий инструмент по обузданию асинхронных API. Используя его грамотно, можно избежать Callback Hell и получить чистый, легко читаемый и расширяемый код. Делитесь вашими мыслями в комментариях! Android Developer