Рубрики
Без рубрики

Как создать шину событий с помощью RxJava и RxAndroid

Объяснение того, как создать шину событий с помощью RxJava и RxAndroid.

Автор оригинала: Pierce Zaifman.

Первоначально опубликовано на Первоначально опубликовано на

Если вам когда-либо приходилось общаться между различными частями вашего приложения, это может быть болезненно. Чтобы облегчить это, вы можете использовать шину событий, такую как Otto . Но с добавлением RxJava и RxAndroid в экосистему Android вам больше не нужно полагаться на Отто. Отто на самом деле устарел в пользу этих новых библиотек, так как создать с их помощью собственную шину событий на самом деле довольно просто.

Я придумал свое собственное решение, которое хорошо подходит для моих целей. Вы можете использовать его как есть, если хотите, или настроить его в соответствии с вашими потребностями.

Первая попытка

Если вы просто хотите передавать произвольные данные в свое приложение, это все, что вам нужно:

public final class RxBus {

    private static PublishSubject sSubject = PublishSubject.create();

    private RxBus() {
        // hidden constructor
    }


    public static Subscription subscribe(@NonNull Action1 action) {
        return sSubject.subscribe(action);
    }

    public static void publish(@NonNull Object message) {
        sSubject.onNext(message);
    }
}


//Example usage below:

public class MyActivity extends Activity {
  
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity);
        
        //Using Retrolambda
        RxBus.subscribe((message) -> {
            if (message instanceof Data) {
                Data data = (Data) cityObject;
                Log.v("Testing", data.getInfo());
            }
        });
    }
}

public class MyFragment extends Fragment {
    
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment, container, false);
        
        //Using Retrolambda
        v.findViewById(R.id.view).setOnClickListener(view -> {
            RxBus.publish(new Data("Hello World"));
        });
        
        return v;
    }

}

Использование PublishSubject означает, что новый подписчик будет получать только события, отправленные после их подписки. Он не будет повторять старые события для новых подписчиков.

Однако у этого простого решения есть несколько проблем. Вы не можете выбрать, какие события будет получать ваш подписчик, он будет видеть все. Единственный способ убедиться, что вы получите нужные данные, – это определить новый класс для каждого события, например Событие загрузки данных XYZ или PurpleButtonClickedEvent . Затем вам нужно выполнить проверку instanceof и привести ее. Лично мне не нравится создавать новый класс для каждого типа событий, которые я хочу транслировать.

Кроме того, это решение может привести к утечке памяти, если вы не откажетесь от каждой подписки. В идеале я хочу иметь возможность публиковать событие и подписываться на обновления для этого события. Я не хочу управлять подписками в каждом месте, где я подписываюсь.

Вторая попытка

Чтобы решить эти проблемы, моя следующая итерация выглядела так:

public final class RxBus {

    private static Map> sSubjectMap = new HashMap<>();
    private static Map sSubscriptionsMap = new HashMap<>();

    private RxBus() {
        // hidden constructor
    }

    /**
     * Get the subject or create it if it's not already in memory.
     */
    @NonNull
    private static PublishSubject getSubject(String subjectKey) {
        PublishSubject subject = sSubjectMap.get(subjectKey);
        if (subject == null) {
            subject = PublishSubject.create();
            subject.subscribeOn(AndroidSchedulers.mainThread());
            sSubjectMap.put(subjectKey, subject);
        }

        return subject;
    }

    /**
     * Get the CompositeSubscription or create it if it's not already in memory.
     */
    @NonNull
    private static CompositeSubscription getCompositeSubscription(@NonNull Object object) {
        CompositeSubscription compositeSubscription = sSubscriptionsMap.get(object);
        if (compositeSubscription == null) {
            compositeSubscription = new CompositeSubscription();
            sSubscriptionsMap.put(object, compositeSubscription);
        }

        return compositeSubscription;
    }

    /**
     * Subscribe to the specified subject and listen for updates on that subject. Pass in an object to associate
     * your registration with, so that you can unsubscribe later.
     * 

* Note: Make sure to call {@link RxBus#unregister(Object)} to avoid memory leaks. */ public static void subscribe(String subject, @NonNull Object lifecycle, @NonNull Action1 action) { Subscription subscription = getSubject(subject).subscribe(action); getCompositeSubscription(lifecycle).add(subscription); } /** * Unregisters this object from the bus, removing all subscriptions. * This should be called when the object is going to go out of memory. */ public static void unregister(@NonNull Object lifecycle) { //We have to remove the composition from the map, because once you unsubscribe it can't be used anymore CompositeSubscription compositeSubscription = sSubscriptionsMap.remove(lifecycle); if (compositeSubscription != null) { compositeSubscription.unsubscribe(); } } /** * Publish an object to the specified subject for all subscribers of that subject. */ public static void publish(String subject, @NonNull Object message) { getSubject(subject).onNext(message); } }

Я добавил Составная подписка для управления всеми подписками для данного объекта (обычно действия или Фрагмента). Я также добавил карту, чтобы отслеживать различные типы предметов. Таким образом, я могу легко отслеживать все подписки и темы.

Кроме того, чтобы упростить управление отменой подписки и сохранением ссылок на эти подписки, я создал базовую активность и базовую фрагментацию.

public abstract class BaseActivity extends AppCompatActivity {

    @Override
    protected void onDestroy() {
        super.onDestroy();
        RxBus.unregister(this);
    }
}
public abstract class BaseFragment extends Fragment {

    @Override
    public void onDestroy() {
        super.onDestroy();
        RxBus.unregister(this);
    }
}

Вызов Шины Rx.отменить регистрацию(это) в onDestroy () – это все, что требуется для очистки. Если с этим объектом связаны подписки, они будут отменены и удалены. Я также написал комментарии, чтобы дать понять, что если вы подписываетесь, вам нужно позвонить и отменить регистрацию. В случае, если вы не используете базовый класс, который обрабатывает его, или не подписываетесь на автобус откуда-то еще.

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

Окончательное осуществление

Последнее изменение, которое я внес, касается того, как вы определяете, на какую тему вы подписаны. Изначально вы могли бы просто передать строковый ключ, определенный там, где вам нравится. Однако мне нравится, когда все эти ключи организованы в одном месте. Кроме того, я хотел ограничить количество событий, на которые вы могли бы подписаться и публиковать. Итак, я изменил строковый параметр на int и создал набор целочисленных констант с аннотацией IntDef . Вы можете увидеть мое заполненное RxBus.java класс ниже:

/**
 * Used for subscribing to and publishing to subjects. Allowing you to send data between activities, fragments, etc.
 * 

* Created by Pierce Zaifman on 2017-01-02. */ public final class RxBus { private static SparseArray> sSubjectMap = new SparseArray<>(); private static Map sSubscriptionsMap = new HashMap<>(); public static final int SUBJECT_MY_SUBJECT = 0; public static final int SUBJECT_ANOTHER_SUBJECT = 1; @Retention(SOURCE) @IntDef({SUBJECT_MY_SUBJECT, SUBJECT_ANOTHER_SUBJECT}) @interface Subject { } private RxBus() { // hidden constructor } /** * Get the subject or create it if it's not already in memory. */ @NonNull private static PublishSubject getSubject(@Subject int subjectCode) { PublishSubject subject = sSubjectMap.get(subjectCode); if (subject == null) { subject = PublishSubject.create(); subject.subscribeOn(AndroidSchedulers.mainThread()); sSubjectMap.put(subjectCode, subject); } return subject; } /** * Get the CompositeSubscription or create it if it's not already in memory. */ @NonNull private static CompositeSubscription getCompositeSubscription(@NonNull Object object) { CompositeSubscription compositeSubscription = sSubscriptionsMap.get(object); if (compositeSubscription == null) { compositeSubscription = new CompositeSubscription(); sSubscriptionsMap.put(object, compositeSubscription); } return compositeSubscription; } /** * Subscribe to the specified subject and listen for updates on that subject. Pass in an object to associate * your registration with, so that you can unsubscribe later. *

* Note: Make sure to call {@link RxBus#unregister(Object)} to avoid memory leaks. */ public static void subscribe(@Subject int subject, @NonNull Object lifecycle, @NonNull Action1 action) { Subscription subscription = getSubject(subject).subscribe(action); getCompositeSubscription(lifecycle).add(subscription); } /** * Unregisters this object from the bus, removing all subscriptions. * This should be called when the object is going to go out of memory. */ public static void unregister(@NonNull Object lifecycle) { //We have to remove the composition from the map, because once you unsubscribe it can't be used anymore CompositeSubscription compositeSubscription = sSubscriptionsMap.remove(lifecycle); if (compositeSubscription != null) { compositeSubscription.unsubscribe(); } } /** * Publish an object to the specified subject for all subscribers of that subject. */ public static void publish(@Subject int subject, @NonNull Object message) { getSubject(subject).onNext(message); } }

Потенциальные проблемы

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

Я все еще передаю объекты, которые должны быть приведены к правильному типу. Я не уверен, что есть способ обойти это, потому что субъект публикует Объекты . Таким образом, подписчик будет получать только Объекты .

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

Базовая активность и базовый фрагмент отменяют регистрацию с шины в onDestroy() . Это означает, что если вы начнете новое действие, старое действие все равно будет подписано. Поэтому, если вы опубликуете событие, на которое подписано предыдущее действие, это может привести к сбою вашего приложения с помощью java.lang. Исключение IllegalStateException: Не удается выполнить это действие после onSaveInstanceState . Я не хотел вызывать отмену регистрации в onStop () , потому что, если вы вернетесь к предыдущему действию, оно больше не будет подписано. Если вы будете осторожны с тем, как вы управляете своими предметами, это не будет проблемой. В идеале подписки должны приостанавливаться и возобновляться в течение жизненного цикла и, наконец, уничтожаться вместе с жизненным циклом.

Наконец, я использую статические элементы вместо одноэлементного шаблона. Технически я считаю, что использование одноэлементного шаблона более эффективно для памяти. Так как он будет создавать класс только тогда, когда это необходимо. Но в моем случае, поскольку я использую злоупотребление Rx в onCreate() для большинства своих действий, это на самом деле ничего не спасет. Кроме того, объем используемой памяти ничтожно мал. Некоторые люди также думают, что статические переменные-это зло .

Последние мысли

Это решение не идеально, но я чувствовал, что это хороший компромисс между сложностью и простотой использования.

Для получения дополнительных статей, подобных этой, ознакомьтесь с моим блогом по адресу Для получения дополнительных статей, подобных этой, ознакомьтесь с моим блогом по адресу

Оригинал: “https://www.codementor.io/@piercezaifman/how-to-make-an-event-bus-with-rxjava-and-rxandroid-83olid3tf”