Замена RxBus и EventBus на LiveDataBus — эволюция шины сообщений Android

Архитектура Android RxJava EventBus

задний план

Для системы Android передача сообщений является самым основным компонентом.Передачу сообщений выполняют разные страницы и компоненты в каждом приложении. Передача сообщений может использоваться не только для связи между четырьмя основными компонентами Android, но и для связи между асинхронными потоками и основным потоком. Для Android-разработчиков существует множество способов передачи сообщений, от самых первых использованных Handler, BroadcastReceiver, обратного вызова интерфейса до популярных в последние годы фреймворков класса коммуникационной шины EventBus и RxBus. Платформа обмена сообщениями Android постоянно развивается.

Начните с EventBus

EventBus — это платформа публикации/подписки на события Android, которая упрощает доставку событий Android за счет разделения издателей и подписчиков. EventBus может заменить традиционный обратный вызов Intent, Handler, Broadcast или интерфейса Android, передавать данные между потоками Fragment, Activity и Service и выполнять методы.

Самая большая особенность EventBus: простота и разделение. До того, как не было EventBus, мы обычно использовали широковещание для реализации мониторинга или обратных вызовов функций пользовательского интерфейса.В некоторых сценариях мы также могли напрямую использовать Intent для передачи простых данных или обработки передачи сообщений между потоками через Handler. Но ни трансляция, ни механизм Handler далеко не удовлетворяют нашу эффективную разработку. EventBus упрощает связь между компонентами внутри приложения, а также между компонентами и фоновыми потоками. После запуска EventBus получил высокую оценку разработчиков.

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

eventbus

Изображение взято с домашней страницы EventBus GitHub

опубликовать/подписать модель

Модель подписки-публикации определяет зависимость «один ко многим», позволяя нескольким объектам-подписчикам одновременно прослушивать объект темы. Когда этот тематический объект изменяет свое состояние, он уведомляет все объекты-подписчики, чтобы они могли автоматически обновлять свое состояние.

发布/订阅模式

Появление RxBus

RxBus — это не библиотека, а файл, и реализация — всего 30 строк кода. Сам по себе RxBus не требует слишком большого анализа, его мощь полностью зависит от технологии RxJava, на которой он основан. В последние годы особенно популярна технология реактивного программирования, и RxJava является ее реализацией на языке Java. RxJava по своей сути является моделью публикации/подписки, и в ней легко обрабатывать переключение потоков. Таким образом, RxBus, написав всего 30 строк кода, осмеливается бросить вызов статусу лидера EventBus.

Принцип RxBus

Существует класс Subject RxJava, который наследует класс Observable, достигая при этом интерфейса Observer, поэтому Subject может играть роль подписчиков и подписчиков одновременно, мы используем подкласс PublishSubject Subject для создания объекта Subject (PublishSubject может быть подписан только после получения событие будет сразу отправлено подписчикам), где нужно для получения событий, подписаться на предметный объект после предметного объекта, если событие получено, оно будет передавать подписчику, затем действовать как предметный объект подписан роль лиц.

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

Последним шагом является отмена подписки. В RxJava операция подписки возвращает объект Subscription, чтобы отменить подписку в нужное время, чтобы предотвратить утечку памяти. Если класс генерирует несколько объектов Subscription, мы можем использовать CompositeSubscription для их хранения. Массовая отписка.

Существует множество реализаций RxBus, таких как:

AndroidKnife/RxBus (https://github.com/AndroidKnife/RxBus) Бланкдж/RxBus (https://github.com/Blankj/RxBus)

На самом деле, как упоминалось ранее, принцип RxBus настолько прост, что мы можем сами написать реализацию RxBus:

Реализация RxBus на основе RxJava1:

public final class RxBus {

    private final Subject<Object, Object> bus;

    private RxBus() {
        bus = new SerializedSubject<>(PublishSubject.create());
    }

    private static class SingletonHolder {
        private static final RxBus defaultRxBus = new RxBus();
    }

    public static RxBus getInstance() {
        return SingletonHolder.defaultRxBus;
    }

    /*
     * 发送
     */
    public void post(Object o) {
        bus.onNext(o);
    }

    /*
     * 是否有Observable订阅
     */
    public boolean hasObservable() {
        return bus.hasObservers();
    }

    /*
     * 转换为特定类型的Obserbale
     */
    public <T> Observable<T> toObservable(Class<T> type) {
        return bus.ofType(type);
    }
}

Реализация RxBus на базе RxJava2:

public final class RxBus2 {

    private final Subject<Object> bus;

    private RxBus2() {
        // toSerialized method made bus thread safe
        bus = PublishSubject.create().toSerialized();
    }

    public static RxBus2 getInstance() {
        return Holder.BUS;
    }

    private static class Holder {
        private static final RxBus2 BUS = new RxBus2();
    }

    public void post(Object obj) {
        bus.onNext(obj);
    }

    public <T> Observable<T> toObservable(Class<T> tClass) {
        return bus.ofType(tClass);
    }

    public Observable<Object> toObservable() {
        return bus;
    }

    public boolean hasObservers() {
        return bus.hasObservers();
    }
}

Представляем идею LiveDataBus

Начните с LiveData

LiveData — это фреймворк, предложенный компонентами архитектуры Android. LiveData — это наблюдаемый класс хранения данных, который может воспринимать и отслеживать жизненный цикл таких компонентов, как Activity, Fragment или Service. Именно потому, что LiveData знает о жизненном цикле компонента, данные пользовательского интерфейса могут обновляться только тогда, когда компонент находится в активном состоянии жизненного цикла.

Для LiveData требуется объект-наблюдатель, который обычно является конкретной реализацией класса Observer. Когда жизненный цикл наблюдателя находится в состоянии STARTED или RESUMED, LiveData будет уведомлять наблюдателя об изменении данных; когда наблюдатель находится в других состояниях, даже если данные LiveData изменились, он не будет уведомлен.

Преимущества LiveData

  • Пользовательский интерфейс и данные в реальном времени согласованыПоскольку LiveData использует шаблон наблюдателя, его можно уведомить об изменении данных и обновить пользовательский интерфейс.
  • Избегайте утечек памятиНаблюдатель привязан к жизненному циклу компонента, и когда связанный компонент уничтожается (destroy), наблюдатель немедленно автоматически очищает свои собственные данные.
  • Больше никаких сбоев из-за остановки ActivityНапример: когда активность находится в фоновом состоянии, она не будет получать никаких событий от LiveData.
  • Нет необходимости решать проблемы, вызванные жизненным цикломLiveData может отслеживать жизненный цикл связанного компонента и уведомлять об изменениях данных только в активном состоянии.
  • Обновление данных в реальном времениВсегда получайте актуальные данные, когда компонент активен или переходит из неактивного в активное.
  • Решение проблем с изменением конфигурацииКогда экран поворачивается или перерабатывается и перезапускается, последние данные могут быть получены немедленно.

Разговор о компонентах архитектуры Android

Core Android Architecture Components is the Lifecycle, LiveData, ViewModel and Room, through which you can make a very elegant interface to interact with the data and do some persistence of operation, highly decoupled, automatic management life cycle, but do not worry about memory leaks вопрос.

  • RoomМощная библиотека сопоставления объектов SQLite.
  • ViewModelОбъект, который предоставляет данные для компонентов пользовательского интерфейса и сохраняет изменения в конфигурации устройства.
  • LiveDataНаблюдаемый контейнер данных с учетом жизненного цикла, который хранит данные и уведомляет вас об изменении данных.
  • LifecycleСодержит LifeCycleOwer и LifecycleObserver, которые являются владельцем и наблюдателем жизненного цикла соответственно.

Особенности компонентов архитектуры Android

  • программирование на основе данныхМеняются всегда данные, и интерфейс менять не нужно.
  • Осведомленность о жизненном цикле для предотвращения утечек памяти.
  • Высокая развязкаДанные, интерфейс сильно разделены.
  • сохранение данныхДанные и ViewModel не связаны с жизненным циклом пользовательского интерфейса и не будут уничтожены из-за реконструкции интерфейса.

Фокус: зачем использовать LiveData для построения шины передачи данных LiveDataBus

Причины использования LiveData

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

Зачем использовать LiveDataBus вместо EventBus и RxBus

  • Реализация LiveDataBus и ее простотаПо сравнению со сложной реализацией EventBus, LiveDataBus можно реализовать только с одним классом.
  • LiveDataBus может уменьшить размер пакета APKПоскольку LiveDataBus зависит только от LiveData официальных компонентов архитектуры Android Android, других зависимостей нет, и существует только один класс для собственной реализации. Для сравнения, размер JAR-пакета EventBus составляет 57 КБ, а RxBus зависит от RxJava и RxAndroid, размер пакета RxJava2 — 2,2 МБ, размер пакета RxJava1 — 1,1 МБ, а размер пакета RxAndroid — 9 КБ. Использование LiveDataBus может значительно уменьшить размер пакета APK.
  • Улучшенная поддержка проверяющей стороны LiveDataBusLiveDataBus зависит только от LiveData официальных компонентов архитектуры Android Android.По сравнению с RxJava и RxAndroid, от которых зависит RxBus, проверяющая сторона имеет лучшую поддержку.
  • LiveDataBus учитывает жизненный циклLiveDataBus имеет представление о жизненном цикле, и вызывающему абоненту не нужно вызывать антирегистрацию в системе Android.По сравнению с EventBus и RxBus, он более удобен в использовании, и нет риска утечки памяти.

Дизайн и архитектура LiveDataBus

Состав LiveDataBus

  • ИнформацияСообщение может быть любым объектом, и могут быть определены различные типы сообщений, такие как логические и строковые. Также возможно определить пользовательские типы сообщений.
  • канал сообщенийLiveData играет роль канала сообщений, разные каналы сообщений отличаются разными именами, имя имеет тип String, а канал сообщений LiveData можно получить по имени.
  • шина сообщенийШина сообщений реализована синглтоном, а разные каналы сообщений хранятся в HashMap.
  • подпискаПодписчик получает канал сообщения через getChannel, а затем вызывает методObserver, чтобы подписаться на сообщение этого канала.
  • выпускатьИздатель получает канал сообщения через getChannel, а затем вызывает setValue или postValue для публикации сообщения.

Схема LiveDataBus

LiveDataBus原理图

Реализация LiveDataBus

Первая реализация:

public final class LiveDataBus {

    private final Map<String, MutableLiveData<Object>> bus;

    private LiveDataBus() {
        bus = new HashMap<>();
    }

    private static class SingletonHolder {
        private static final LiveDataBus DATA_BUS = new LiveDataBus();
    }

    public static LiveDataBus get() {
        return SingletonHolder.DATA_BUS;
    }

    public <T> MutableLiveData<T> getChannel(String target, Class<T> type) {
        if (!bus.containsKey(target)) {
            bus.put(target, new MutableLiveData<>());
        }
        return (MutableLiveData<T>) bus.get(target);
    }

    public MutableLiveData<Object> getChannel(String target) {
        return getChannel(target, Object.class);
    }
}

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

Зарегистрируйтесь, чтобы подписаться:

LiveDataBus.get().getChannel("key_test", Boolean.class)
        .observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(@Nullable Boolean aBoolean) {
            }
        });

отправлять сообщения:

LiveDataBus.get().getChannel("key_test").setValue(true);

Мы отправляем событие под названием «key_test» со значением true. В это время подписчик получит сообщение и обработает его соответствующим образом, что очень просто.

Была проблема

Для первой версии реализации LiveDataBus мы обнаружили, что в процессе использования этой LiveDataBus подписчики будут получать сообщения, опубликованные до подписки. Для шины сообщений это неприемлемо. Независимо от EventBus или RxBus, подписчик не получит сообщение, отправленное до подписки. Для шины сообщений LiveDataBus должен решить эту проблему.

анализ проблемы

Как решить эту проблему? Сначала проанализируйте причины:

Когда состояние LifeCircleOwner изменится, будет вызвана функция activeStateChanged LiveData.ObserverWrapper.Если состояние ObserverWrapper в это время активно, будет вызвано dispatchingValue LiveData.

code

В диспетчерском значении LiveData снова будет вызываться метод рассматривать-уведомления LiveData.

code

В методе рассмотрите уведомление LiveData логика в красном поле является ключевой.Если mLastVersion ObserverWrapper меньше, чем mVersion LiveData, он вызовет метод onChanged mObserver. И версия каждого нового подписчика равна -1.Как только LiveData установлен, его версия больше -1 (каждый раз, когда LiveData устанавливается, его версия будет увеличиваться на 1), что заставит LiveDataBus каждый раз регистрировать новый. , этот подписчик получит обратный вызов немедленно, даже если заданное действие произойдет до подписки.

code

Краткое изложение причины проблемы

Для этой проблемы обобщите основные причины возникновения. Для LiveData его начальная версия равна -1.При вызове его setValue или postValue его версия будет +1, для каждого пакета обозревателя ObserverWrapper его начальная версия тоже -1, то есть каждый новый Версия зарегистрированного обозревателя равно -1; когда LiveData устанавливает этот ObserverWrapper, если версия LiveData больше, чем версия ObserverWrapper, LiveData заставит текущее значение быть переданным в Observer.

Как решить эту проблему

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

Итак, как это реализовать, смотрите в методе наблюдать LiveData, он создаст LifecycleBoundObserver на шаге 1, а LifecycleBoundObserver является производным классом от ObserverWrapper. Затем на шаге 2 этот LifecycleBoundObserver будет помещен в частный контейнер карты mObservers. И ObserverWrapper, и LifecycleBoundObserver являются частными или видимыми для пакета, поэтому версию LifecycleBoundObserver нельзя изменить путем наследования.

Итак, можете ли вы получить LifecycleBoundObserver из контейнера карты mObservers, а затем изменить версию? Ответ положительный, изучив исходный код SafeIterableMap, мы обнаружили, что существует защищенный метод get. Следовательно, при вызове наблюдаемого мы можем получить LifecycleBoundObserver через отражение, а затем установить версию LifecycleBoundObserver, чтобы она соответствовала LiveData.

code

Для метода наблюдать за жизненным циклом, не учитывающего жизненный цикл, идеи реализации такие же, но конкретная реализация немного отличается. ПриObservForever генерируется обертка не LifecycleBoundObserver, а AlwaysActiveObserver (шаг 1), и у нас нет возможности изменить версию AlwaysActiveObserver после завершения вызоваObserver, т.к. происходит .

code

Итак, дляObserverForever, как решить эту проблему? Поскольку обратный вызов вызывается внутри вызова, мы можем написать ObserverWrapper для переноса реального обратного вызова. Передаем ObserverWrapper вObservForever, тогда мы будем проверять стек вызовов при обратном вызове.Если обратный вызов вызван методомObservForever, то реальный абонент не будет перезванивать.

LiveDataBus наконец реализован

public final class LiveDataBus {

    private final Map<String, BusMutableLiveData<Object>> bus;

    private LiveDataBus() {
        bus = new HashMap<>();
    }

    private static class SingletonHolder {
        private static final LiveDataBus DEFAULT_BUS = new LiveDataBus();
    }

    public static LiveDataBus get() {
        return SingletonHolder.DEFAULT_BUS;
    }

    public <T> MutableLiveData<T> with(String key, Class<T> type) {
        if (!bus.containsKey(key)) {
            bus.put(key, new BusMutableLiveData<>());
        }
        return (MutableLiveData<T>) bus.get(key);
    }

    public MutableLiveData<Object> with(String key) {
        return with(key, Object.class);
    }

    private static class ObserverWrapper<T> implements Observer<T> {

        private Observer<T> observer;

        public ObserverWrapper(Observer<T> observer) {
            this.observer = observer;
        }

        @Override
        public void onChanged(@Nullable T t) {
            if (observer != null) {
                if (isCallOnObserve()) {
                    return;
                }
                observer.onChanged(t);
            }
        }

        private boolean isCallOnObserve() {
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            if (stackTrace != null && stackTrace.length > 0) {
                for (StackTraceElement element : stackTrace) {
                    if ("android.arch.lifecycle.LiveData".equals(element.getClassName()) &&
                            "observeForever".equals(element.getMethodName())) {
                        return true;
                    }
                }
            }
            return false;
        }
    }

    private static class BusMutableLiveData<T> extends MutableLiveData<T> {

        private Map<Observer, Observer> observerMap = new HashMap<>();

        @Override
        public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
            super.observe(owner, observer);
            try {
                hook(observer);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void observeForever(@NonNull Observer<T> observer) {
            if (!observerMap.containsKey(observer)) {
                observerMap.put(observer, new ObserverWrapper(observer));
            }
            super.observeForever(observerMap.get(observer));
        }

        @Override
        public void removeObserver(@NonNull Observer<T> observer) {
            Observer realObserver = null;
            if (observerMap.containsKey(observer)) {
                realObserver = observerMap.remove(observer);
            } else {
                realObserver = observer;
            }
            super.removeObserver(realObserver);
        }

        private void hook(@NonNull Observer<T> observer) throws Exception {
            //get wrapper's version
            Class<LiveData> classLiveData = LiveData.class;
            Field fieldObservers = classLiveData.getDeclaredField("mObservers");
            fieldObservers.setAccessible(true);
            Object objectObservers = fieldObservers.get(this);
            Class<?> classObservers = objectObservers.getClass();
            Method methodGet = classObservers.getDeclaredMethod("get", Object.class);
            methodGet.setAccessible(true);
            Object objectWrapperEntry = methodGet.invoke(objectObservers, observer);
            Object objectWrapper = null;
            if (objectWrapperEntry instanceof Map.Entry) {
                objectWrapper = ((Map.Entry) objectWrapperEntry).getValue();
            }
            if (objectWrapper == null) {
                throw new NullPointerException("Wrapper can not be bull!");
            }
            Class<?> classObserverWrapper = objectWrapper.getClass().getSuperclass();
            Field fieldLastVersion = classObserverWrapper.getDeclaredField("mLastVersion");
            fieldLastVersion.setAccessible(true);
            //get livedata's version
            Field fieldVersion = classLiveData.getDeclaredField("mVersion");
            fieldVersion.setAccessible(true);
            Object objectVersion = fieldVersion.get(this);
            //set wrapper's version
            fieldLastVersion.set(objectWrapper, objectVersion);
        }
    }
}

Зарегистрируйтесь, чтобы подписаться

LiveDataBus.get()
        .with("key_test", String.class)
        .observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
            }
        });

отправлять сообщения:

LiveDataBus.get().with("key_test").setValue(s);

Описание исходного кода

Исходный код LiveDataBus можно напрямую скопировать и использовать, или вы можете перейти в репозиторий автора на GitHub для просмотра и загрузки: https://github.com/JeremyLiao/LiveDataBus

Суммировать

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

об авторе

Хайлян, старший инженер Meituan, присоединился к Meituan в 2017 году. В настоящее время он в основном отвечает за соответствующий бизнес и разработку модулей приложений, таких как Meituan Light Cashier и Meituan Cashier Retail Edition.