Бывают такие задачи в которых приходится распределять работу функционала, что бы каждая задача выполнялась после окончания предыдущей. В таких случаях обычно пихают внутри колбека об окончании процесса что бы стартовал новый процесс, это выглядит не очень красиво, сильно грубо и нагромождено.
В таких случаях обычно стараются использовать такую штуку как очередь. Она помогает поставить процесс понятно и легкоизменяемо, каждый просесс выполняется в отдельном потоке, отдельном классе, и никак не задевает работу следующего или предыдущего процесса, все предельно понятно и логично.
В моем примере будет пример очереди в которой будет три этапа:
— CREATE — это будет какой-нибудь этап на пример создания видео файла или чего-то.
— WORK — далее мы образно говоря делаем какие-то действия над ним, режем файл, меняем кодек или еще что-то и отправляем на сервер.
— CLEAN — стираем все файлы которые были созданы с памяти девайса.
Это очень абстрактно говоря тот флоу который я буду сегодня тут описывать. В реальности у нас не будет никаких действий над файлами, я просто добавлю задержку между процессами что бы показать что там что-то происходит.
Визуально выглядеть это все будет на экране как два текстовых поля которые показывают задержку между процессами и текущий процесс который выполняется. По нажатию на кноку у нас стартует очередь.
Давайте для начала решим какие статусы у нас будут, я их уже описал выше, теперь нам надо их перенести в енам.
Statuses.kt
Как я и говорил выше, три статуса, они у нас будут описывать какие процессы у нас будут в очереди.
Далее нам нужно создать модель с которой у нас будет происходить сбор и получение данных, и в которой у нас будет хранится статусы и стейты процессов. Обычно это нужно делать где нибудь в БД, для того что бы эта информация хранилась постоянно и к ней можно было обратиться в случае ошибки или сбоя в очереди. Но я для упрощения примера решил это все хранить только во время жизни приложения, по этому все будем хранить в модельке.
MetaDataModel.kt
Данный класс у нас хранит в себе всю самую важную информацию касательно процесса, но так же в нем может быть информация касательно какого-то на пример файла который мы режем, меняем или отправляем на сервер, или что-то подобное. У меня все минимально, только то что потребуется в дальнейшем. state — стоит по дефолту CREATE, возможно в другом случае по дефолту статуса быть не может, по этому его придется убрать и он будет ставится или через конструктор или через сеттер.
Потом мы создаем несколько базовых классов и интерфейсов, которые будут у нас иметь основной функционал для работы с очередью.
BaseWorkItem.kt
Этот work item у нас является базовой точкой в которой у нас идет переопределение метода equals, для того что-бы мы могли понять являются ли work item's которые мы хотим сравнить — одинаковые или нет. Это мы будем понимать по ID которые у нас будут уникальные и которые мы сравниваем в методе equals. Так же у нас каждый work item с которым мы будем дальше работать будет наследником BaseWorkItem, по этому любой из имеющихся work item's мы сможем сравнить и использовать в базовых классах.
Далее создаем несколько лисенеров которые мы будем использовать для кидания колбеков из базовых классов в наши процессы и наоборот из процессов в базовый класс.
BaseWorkListener.kt
Этот листенер мы будем использовать для сигнализации в класс менеджер о том что данный процесс или закончил свою работу или зафейлился и упал и в классе менеджере мы будем уже дальше решать что делать, или двигаться на следующий процесс или выводить сообщение об ошибке.
BaseProcessCallback.kt
Так же у нас будет вот такой еще один лисенер, который будет кидать колбеки в базовый класс процессов, и в зависимости от прилетевшего колбека наш базовый класс будет уже проверять есть ли у нас в очереди еще какие-то задачи, и если есть то будет двигать их дальше, иначе если они остуствуют то просто выходить и останавливать работу.
BaseTaskProcessor.kt
Вот собственно наш базовый класс который будет следить за очередью, данный класс наследуются от BaseProcessCallback для получения колбеков из процессов которые подпишутся на изменения в данном классе. Так же у нас есть какая-то «Т» которая у нас обозначена как BaseWorkItem, а это значит что мы будем передавать WorkItem который будет наследником BaseWorkItem и в котором будут находится какие-то дополнительные поля которые понадобятся в ходе работы процессов.
— queueWorkItem — добавляет все процессы в очередь и проверяет если список не пуст и в нем находятся какие-то процессы.
— checkWorkQueue — берем первый айтем из списка и выполняем его с помощью метода processWorkItem который является абстрактным методом, он будет описываться в дочерних классах этого базового класса.
— onSuccess / onError — у нас проверяет есть ли что-то еще в очереди и запускает его если еще что-то есть.
С базовой частью мы закончили. Это по сути список всех классов которые у нас на данный момент будут использоваться в дочерних классах, кроме MetaDataModel. Далее мы будем создавать сами процессы которые будут дальше запускаться в очереди в менеджере процессов.
Как я говорил раньше, у нас будет три процесса: CREATE, WORK, CLEAN. Для каждого из этих процессов нам нужно создать отдельный WorkItem и Process в которые мы будем передавать нужные данные и получать колбеки с результатами работы.
CreateWorkItem.kt
В данном айтеме у нас в конструкторе передаем MetaDataModel в которой задаем уникальный ID процесса и статус. Так же мы унаследовали для данного класса BaseWorkItem и передали metadata который мы передали в конструкторе.
CreateProcess.kt
Что же мы тут видим? Мы подписались в конструкторе на лисенера который будет кидать коллбеки в BaseTaskProcessor, так же мы унаследовались от BaseTaskProcessor для определения метода processWorkItem и описали работу данного класса в этом методе. В данном случае у нас просто хендлер который 5 секунду будет ничего не делать, а дальше кинет колбеки в BaseTaskProcessor и в менеджера процессов, который мы опишем далее. Таких классов у нас еще будет два, я сюда их добавлять не буду, их можно будет найти по этим двум ссылкам: WORK — WorkWorkItem.kt, WorkProcess.kt, CLEAN — CleanWorkItem.kt, CleanProcess.kt
Далее нам нужно создать менеджера который будет управлять этими всеми процессами и запускать их по колбекам которые будут прилетать из этих же процессов. Для начала создадим интерфейс для визуализации работы очереди.
QueueFlowListener.kt
Данный интерфейс нам нужен для отправки колбека о статусе текущего процесса в активити для отображения в текстовых полях, какой на данный момент процесс в очереди, и сколько до окончания работы онного.
QueueFlowManager.kt
В init мы прописываем все процессы которые у нас будут и подписываемся на их коллбеки, что бы по результату получать какой-то фидбек и стартовать новый процесс.
— makeSequentialStatusMap — мы создаем список в который задаем все процессы которые мы будем запускать.
— onStateChanged — изменяет текущий стейт в модели которая у нас является текущей для работы с очередью. В нем же по порядку, в зависимости от полученного стейта запускается нужный процесс. Далее вызывается onWorkItemStateChange для отправки колбека о текущем стейте в активити.
— onJobComplete — мы получаем текущий стейт, увеличиваем его индекс и запускаем следующий стейт в работу.
— onJobFailed — записываем ошибки с которой упала очередь и стейт на котором оно упало.
— startFlow — запускает флоу с последнего который ему задали в onStateChange.
activity_main.xml
Вот так будет выглядеть наш леяут, на нем будет три элемента, два текстовых поля и одна кнопка, по нажатию на которую мы будем стартовать нашу очередь.
MainActivity.kt
Из самого важного что я могу отметить в данной активити — это onCreate в котором по нажатию на кнопку мы стартуем очередь. В onWorkItemStateChange мы отображаем стейт и рестартуем таймер каждый раз когда меняется стейт процесса. countDownTimer — нужен чисто для визуального понимания через сколько запустится новый процесс в очереди. Ну и в onTick в countDownTimer'e мы обновляем текстовое поле которое касается таймера.
Вот такая штука эта очередь, надеюсь кому-то это будет полезно и когда нибудь пригодится, я лично с этим столкнулся и долго мучался что бы правильно это реализовать, как практика показала такой способ очень удобный и стабильный, очередь работает как часы, добавление нового элемента в очередь не составит никакого труда так как архитектура построена на принципе SOLID — каждый класс ответственнен за выполнение одного действия.
Исходники:
GitHub
В моем примере будет пример очереди в которой будет три этапа:
— CREATE — это будет какой-нибудь этап на пример создания видео файла или чего-то.
— WORK — далее мы образно говоря делаем какие-то действия над ним, режем файл, меняем кодек или еще что-то и отправляем на сервер.
— CLEAN — стираем все файлы которые были созданы с памяти девайса.
Это очень абстрактно говоря тот флоу который я буду сегодня тут описывать. В реальности у нас не будет никаких действий над файлами, я просто добавлю задержку между процессами что бы показать что там что-то происходит.
Визуально выглядеть это все будет на экране как два текстовых поля которые показывают задержку между процессами и текущий процесс который выполняется. По нажатию на кноку у нас стартует очередь.
Давайте для начала решим какие статусы у нас будут, я их уже описал выше, теперь нам надо их перенести в енам.
Statuses.kt
Как я и говорил выше, три статуса, они у нас будут описывать какие процессы у нас будут в очереди.
Далее нам нужно создать модель с которой у нас будет происходить сбор и получение данных, и в которой у нас будет хранится статусы и стейты процессов. Обычно это нужно делать где нибудь в БД, для того что бы эта информация хранилась постоянно и к ней можно было обратиться в случае ошибки или сбоя в очереди. Но я для упрощения примера решил это все хранить только во время жизни приложения, по этому все будем хранить в модельке.
MetaDataModel.kt
Данный класс у нас хранит в себе всю самую важную информацию касательно процесса, но так же в нем может быть информация касательно какого-то на пример файла который мы режем, меняем или отправляем на сервер, или что-то подобное. У меня все минимально, только то что потребуется в дальнейшем. state — стоит по дефолту CREATE, возможно в другом случае по дефолту статуса быть не может, по этому его придется убрать и он будет ставится или через конструктор или через сеттер.
Потом мы создаем несколько базовых классов и интерфейсов, которые будут у нас иметь основной функционал для работы с очередью.
BaseWorkItem.kt
Этот work item у нас является базовой точкой в которой у нас идет переопределение метода equals, для того что-бы мы могли понять являются ли work item's которые мы хотим сравнить — одинаковые или нет. Это мы будем понимать по ID которые у нас будут уникальные и которые мы сравниваем в методе equals. Так же у нас каждый work item с которым мы будем дальше работать будет наследником BaseWorkItem, по этому любой из имеющихся work item's мы сможем сравнить и использовать в базовых классах.
Далее создаем несколько лисенеров которые мы будем использовать для кидания колбеков из базовых классов в наши процессы и наоборот из процессов в базовый класс.
BaseWorkListener.kt
Этот листенер мы будем использовать для сигнализации в класс менеджер о том что данный процесс или закончил свою работу или зафейлился и упал и в классе менеджере мы будем уже дальше решать что делать, или двигаться на следующий процесс или выводить сообщение об ошибке.
BaseProcessCallback.kt
Так же у нас будет вот такой еще один лисенер, который будет кидать колбеки в базовый класс процессов, и в зависимости от прилетевшего колбека наш базовый класс будет уже проверять есть ли у нас в очереди еще какие-то задачи, и если есть то будет двигать их дальше, иначе если они остуствуют то просто выходить и останавливать работу.
BaseTaskProcessor.kt
Вот собственно наш базовый класс который будет следить за очередью, данный класс наследуются от BaseProcessCallback для получения колбеков из процессов которые подпишутся на изменения в данном классе. Так же у нас есть какая-то «Т» которая у нас обозначена как BaseWorkItem, а это значит что мы будем передавать WorkItem который будет наследником BaseWorkItem и в котором будут находится какие-то дополнительные поля которые понадобятся в ходе работы процессов.
— queueWorkItem — добавляет все процессы в очередь и проверяет если список не пуст и в нем находятся какие-то процессы.
— checkWorkQueue — берем первый айтем из списка и выполняем его с помощью метода processWorkItem который является абстрактным методом, он будет описываться в дочерних классах этого базового класса.
— onSuccess / onError — у нас проверяет есть ли что-то еще в очереди и запускает его если еще что-то есть.
С базовой частью мы закончили. Это по сути список всех классов которые у нас на данный момент будут использоваться в дочерних классах, кроме MetaDataModel. Далее мы будем создавать сами процессы которые будут дальше запускаться в очереди в менеджере процессов.
Как я говорил раньше, у нас будет три процесса: CREATE, WORK, CLEAN. Для каждого из этих процессов нам нужно создать отдельный WorkItem и Process в которые мы будем передавать нужные данные и получать колбеки с результатами работы.
CreateWorkItem.kt
В данном айтеме у нас в конструкторе передаем MetaDataModel в которой задаем уникальный ID процесса и статус. Так же мы унаследовали для данного класса BaseWorkItem и передали metadata который мы передали в конструкторе.
CreateProcess.kt
Что же мы тут видим? Мы подписались в конструкторе на лисенера который будет кидать коллбеки в BaseTaskProcessor, так же мы унаследовались от BaseTaskProcessor для определения метода processWorkItem и описали работу данного класса в этом методе. В данном случае у нас просто хендлер который 5 секунду будет ничего не делать, а дальше кинет колбеки в BaseTaskProcessor и в менеджера процессов, который мы опишем далее. Таких классов у нас еще будет два, я сюда их добавлять не буду, их можно будет найти по этим двум ссылкам: WORK — WorkWorkItem.kt, WorkProcess.kt, CLEAN — CleanWorkItem.kt, CleanProcess.kt
Далее нам нужно создать менеджера который будет управлять этими всеми процессами и запускать их по колбекам которые будут прилетать из этих же процессов. Для начала создадим интерфейс для визуализации работы очереди.
QueueFlowListener.kt
Данный интерфейс нам нужен для отправки колбека о статусе текущего процесса в активити для отображения в текстовых полях, какой на данный момент процесс в очереди, и сколько до окончания работы онного.
QueueFlowManager.kt
В init мы прописываем все процессы которые у нас будут и подписываемся на их коллбеки, что бы по результату получать какой-то фидбек и стартовать новый процесс.
— makeSequentialStatusMap — мы создаем список в который задаем все процессы которые мы будем запускать.
— onStateChanged — изменяет текущий стейт в модели которая у нас является текущей для работы с очередью. В нем же по порядку, в зависимости от полученного стейта запускается нужный процесс. Далее вызывается onWorkItemStateChange для отправки колбека о текущем стейте в активити.
— onJobComplete — мы получаем текущий стейт, увеличиваем его индекс и запускаем следующий стейт в работу.
— onJobFailed — записываем ошибки с которой упала очередь и стейт на котором оно упало.
— startFlow — запускает флоу с последнего который ему задали в onStateChange.
activity_main.xml
Вот так будет выглядеть наш леяут, на нем будет три элемента, два текстовых поля и одна кнопка, по нажатию на которую мы будем стартовать нашу очередь.
MainActivity.kt
Из самого важного что я могу отметить в данной активити — это onCreate в котором по нажатию на кнопку мы стартуем очередь. В onWorkItemStateChange мы отображаем стейт и рестартуем таймер каждый раз когда меняется стейт процесса. countDownTimer — нужен чисто для визуального понимания через сколько запустится новый процесс в очереди. Ну и в onTick в countDownTimer'e мы обновляем текстовое поле которое касается таймера.
Вот такая штука эта очередь, надеюсь кому-то это будет полезно и когда нибудь пригодится, я лично с этим столкнулся и долго мучался что бы правильно это реализовать, как практика показала такой способ очень удобный и стабильный, очередь работает как часы, добавление нового элемента в очередь не составит никакого труда так как архитектура построена на принципе SOLID — каждый класс ответственнен за выполнение одного действия.
Исходники:
GitHub
Комментариев нет:
Отправить комментарий