Pull to refresh

Comments 15

Использовал подобный подход на android — он там имеет дополнительные плюсы, так с UI можно работать только из основного потока, и реализован функциона передачи ивентов между UI-потоком и тред-пулом. Используется Roboguice для dependency injection.
Реализация:
GlobalEventManager
https://gist.github.com/naphaso/c9dbb8a4d4035d1f0584
умеет регистрировать/разрегистрировать обработчики сообщений с определённым типом TP — обрабатывать в тредпуле, UI — обабатывать в UI-потоке, IN — обрабатывать синхронно в потоке, пославшем сообщения (значительно быстрее, если обработчик сообщения отрабатывает очень быстро и не взаимодействует с UI).
Отнаследованы и подкручены тред-пулы, чтобы назначали красивые имена тредам, которые обрабатывают сообщения, чтобы было видно в логах, к обработке какого сообщения отновится каждая запись там.
EventTask
https://gist.github.com/naphaso/69de1a51ad383035dd04
Собственно, он и назначает красивые имена тредам.
EventListener
https://gist.github.com/naphaso/cc6d7b5da4de4c301f8d
Хранит WeakReference не объект и метод для обработки сообщения. WeakReference — чтобы, в случае, если объект забыл или физически не может (не знает, когда) отписться от приёма сообщений, это не приводило к трагическим последствиям в виде удерживания этого объекта в памяти.

Используется примерно так:
В любом объекте при его старте подписываемся на сообщения:
globalEventListener.registerListeners(this);

Когда уже не нужно принимать сообщения, отписываемся:
globalEventManager.unregisterListeners(this);


Как уже сказал, отписываться не обязательно, но желательно.

Обрабатываем сообщения в методах, аннотиованных следующим образом:
@Event(ThreadType.UI)
public void onSomeEvent(SomeEvent event) {
    // some UI work
}


Ну, и полный пример:
@Singleton
public class SomeBackground {
    @Inject
    private GlobalEventManager globalEventManager;

    @Inject
    public void init() {
        globalEventManager.registerListeners(this);
    }

    @Event(ThreadType.TP)
    public void onStartBackgroundWork(BackgroundTaskEvent event) {
        // some work
        globalEventManager.fire(new PublishResultsEvent(results));
    }
}

public class SomeActivity extends RoboActivity {
    @Inject
    private GlobalEventManager globalEventManager;
    
    @Override
    public void onCreate(Context context) {
        super.onCreate(context);
        globalEventManager.registerListeners(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        globalEventManager.unregisterListeners(this);
    }

    public void onButtonClickListener() {
        globalEventManager.fire(new BackgroundTaskEvent(...));
    }

    @Event(ThreadType.UI)
    public void onShowResults(PublishResultsEvent event) {
        // show results in GUI
    }
}


За ошибки не ручаюсь, писал код прямо в комментарии. Потом, правда, этот подход пришлось немного переработать, но было весьма удобно.
Поддерживаю! Событийно-ориентированная парадигма в андроиде сглаживает многие шероховатости (например, коммуникацию фрагментов с активити или с сервисом). Лично я решил велосипедов не изобретать, а использовать простенький и крохотный EventBus — github.com/greenrobot/EventBus
Мне кажется, что это именно тот случай, когда коментарий информативней поста :)
два вопроса:

1. а при при использовании WeakReference как гарантируется сохранность слушателей?
2. кем обрабатываются аннотации?
1. А никак не гарантируется, если объект никто не держал и его собрал GC — значит ему не нужно получать ивенты, и он при первом же ивенте убирается из listener'ов.
2. Аннотации обрабатываются GlobalEventManager'ом в метода registerListeners и unregisterListeners.
Чем дольше я программирую, тем больше мне нравятся слабосвязанные системы, которые состоят из большого числа разрозненных единиц (модулей), ничего не знающих друг о друге, но предполагающих существование других.


Двумя руками за. Сам стараюсь проектировать системы подобным образом.
Давно достали многомегабайтные монстры, загружающиеся минуту и больше, забивающие ОЗУ кодом, который не будет использован в текущей сессии, а может, и вообще никогда.
При вызове у вас перебираются обработчики и вызываются. Если один из вызываемых обработчиков подпишется на то же событие еще раз или отпишется, то произойдет модификация коллекции, код упадёт и остальные обработчики не вызовутся. В этом месте нужно либо копировать список обработчиков в локальный, либо использовать одну из CopyOnWrite-коллекций для хранения обработчиков. А ссылка на гитхаб будет?
Как уже ответили выше, Вы изобрели велосипед, под названием EventBus. Есть много имплементаций, например, из Guava. Событийное программирование — это огромный антипаттерн, который использовать нужно с особой осторожностью, а по возможности избегать вообще.
Спасибо за подсказку, обязательно посмотрю. Но велосипеды — не так уж плохо, особенно если поставленную задачу выполняют неплохо — особенно столь несложные.

> Событийное программирование — это огромный антипаттерн
Простите, а почему так категорично? По-моему, я подробно описал как недостатки, так и достоинства событий (впрочем, это одно и то же). Но именно такое поведение мне и нужно, а следовательно — я предупрежден.

Например, это напоминает подавление NPE исключения при вызове метода на несуществующем объекте — но так же просто логгировать или добавить его во всех местах.
Я не говорю, что надо вообще все им заменить. Но в определенных случаев — более чем красиво и удобно.
Событийное программирование — это огромный антипаттерн

Эм. А можно поподробнее?
— разобщает логику и делает запутанным код системы
— сильно затрудняет нахождение ошибок и отладку
— не гарантирует и не контролирует последовательность обработки событий и соответственно делает непредсказуемым поведение системы

Я раньше много писал на свинге и знаю что такое. И в самой библиотеке были баги и практически все приложения подглючивали.
Поэтому если можно обойтись без событийной модели — оно всегда предпочтительней.
> разобщает логику и делает запутанным код системы
Наоборот, заставляет систему разбивать на независимые легкотестируемые модули

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

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

Посему, с одной стороны, так легче, с другой стороны — к черту старый подход отладки.
Идеальная система имеет древовидную структуру компонентов. Если есть возможность сделать ее такой, такой ее и нужно делать. Событийная модель может быть полезна:
— когда компоненты достаточно независимы и слабо связаны функционально
— при асинхронном взаимодейтвии компонентов
— когда нужно связать две несовместимые модели, например модель данных приложения и GUI
— в качестве локальных callback-ов от компонентов низкого уровня к более высоким, если нельзя обойтись без них

Потенциальные проблемы, за которыми нужно будет следить, если испльзовать события:
— регистрация/дерегистрация хендлеров. Как правило 80% всех ошибок в свинге, связанных с memory-leaking. Спасает EventBus с weak хендлерами.
— когда в процессе обработки события одини из хендлеров генерирует новое. Логика подсказывает, что оно должно обработаться после текущего, однако реально оно обрабатывается синхронно прямо в момент выкидывания. Частично спасает EventBus с асинхронной очередью, ибо в очереди уже могут стоять другие события, которые к моменту обработки данного делают состояние системы несовместимым. Например, кнопка окна выкинула, что она нажалась, а следующим в очереди уже стояло закрытие окна. В итоге обработчик получил нажатие кнопки, которая уже не существует, что могло бы спровоцировать трудноотлавливаемую ошибку.
— запутанные зависимости и дилема: в какой последовательности правильно останавливать компоненты?
— ну и классика при использовании в multithreaded environment: дедлоки всех возможных видов.
Sign up to leave a comment.

Articles