Этой статьи Rxjava достаточно, рекомендуется трещина в стене

API Android RxJava Retrofit

Недавно в проекте использовался фреймворк Rxjava, и он показался мне очень мощным.Поэтому я просмотрел много связанных статей в Интернете и нашел очень хорошую статью.Сегодня я поделюсь этой статьей с вами, спасибо оригинальному автору за его преданность, так что давайте все проверим эту статью:

предисловие

Я использую RxJava с прошлого года, и уже больше года. Присоединившись к Flipboard в этом году, я увидел, что Android-проект Flipboard также использует RxJava, и он используется во все большем количестве сценариев. В последние месяцы я также обнаружил, что все больше и больше людей в Китае начали упоминать RxJava. Некоторые люди говорят, что «RxJava действительно проста в использовании», некоторые люди говорят, что «RxJava действительно сложна в использовании», а другие люди говорят: «У меня действительно есть Baidu и Google, но я все еще хочу спросить: что такое RxJava?»

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

У этой статьи две цели: 1. Дать людям, интересующимся RxJava, некоторые рекомендации для начала работы. 2. Дать более глубокий анализ тем, кто использует RxJava, но все еще сомневается.

содержание:

В конце перед началом текста поставитьGitHubсвязывание и импорт зависимостейgradleКод: Гитхаб:
https://github.com/ReactiveX/RxJava
https://github.com/ReactiveX/RxAndroid
Импорт зависимостей:
compile 'io.reactivex:rxjava:1.0.14'
compile 'io.reactivex:rxandroid:1.0.1'
(Номер версии — это последняя стабильная версия на момент публикации статьи)

Кроме того, благодаря основным членам RxJavaТечет огонь кленовый лестехническая поддержка и внутренние бета-ридерыкодеры,Бао Юнчжан,drakeet,Марлен,иногда снисходительный,Программы не обезьяны,призрак с большой головой,XZoomEye,Си Дэю,TCahead,Tiiime,Ailurus,Жилой старший,злодей,большой большой министр,NicodeLeeпомощь, иЧжоу Ботонг Вербовкаспонсорство.

Что такое RxJava

одно слово:асинхронный.

Собственное представление RxJava на домашней странице GitHub — это «библиотека для составления асинхронных и событийных программ с использованием наблюдаемых последовательностей для Java VM» (библиотека для составления асинхронных событийных программ с использованием наблюдаемых последовательностей для Java VM). Это RxJava, в очень точном выражении.

Однако для новичков это слишком сложно для чтения. Потому что это «резюме», а новичкам нужно «введение».

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

Что хорошего в RxJava

Другими словами, «Это тоже асинхронно, почему люди используют его вместо готового AsyncTask/Handler/XXX/… ? 』

одно слово:лаконичный.

Ключевым моментом асинхронной операции является простота программы, потому что когда процесс планирования усложняется, асинхронный код часто трудно писать и читать. Создано AndroidAsyncTaskиHandlerНа самом деле, чтобы сделать асинхронный код более лаконичным. Преимущество Rxjava также простота, но его простота отличается от этого,По мере того, как логика программы становится все более и более сложной, она может оставаться лаконичной.

举个例子

Допустим есть такое требование: есть кастомный вид на интерфейсimageCollectorView, его роль заключается в отображении нескольких изображений и может использоватьaddImage(Bitmap)способ произвольного увеличения отображаемого изображения. Теперь программа требуется для преобразования заданного массива каталоговFile[] foldersИзображения png в каждом каталоге загружаются и отображаются вimageCollectorViewсередина. Следует отметить, что поскольку процесс чтения изображений занимает много времени, его необходимо выполнять в фоновом режиме, а отображение изображений должно выполняться в UI-потоке. Существует много часто используемых методов реализации, и я опубликую один из них здесь:

new Thread() {
    @Override
    public void run() {
        super.run();
        for (File folder : folders) {
            File[] files = folder.listFiles();
            for (File file : files) {
                if (file.getName().endsWith(".png")) {
                    final Bitmap bitmap = getBitmapFromFile(file);
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            imageCollectorView.addImage(bitmap);
                        }
                    });
                }
            }
        }
    }
}.start();

А если использовать RxJava, то реализация такая:

Observable.from(folders)
    .flatMap(new Func1<File, Observable<File>>() {
        @Override
        public Observable<File> call(File file) {
            return Observable.from(file.listFiles());
        }
    })
    .filter(new Func1<File, Boolean>() {
        @Override
        public Boolean call(File file) {
            return file.getName().endsWith(".png");
        }
    })
    .map(new Func1<File, Bitmap>() {
        @Override
        public Bitmap call(File file) {
            return getBitmapFromFile(file);
        }
    })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Bitmap>() {
        @Override
        public void call(Bitmap bitmap) {
            imageCollectorView.addImage(bitmap);
        }
    });

Один сказал: «Ваш код явно изменился! Будь проще! 』Брат, успокойся, я говорю о простоте логики, а не просто небольшом количестве кода (простота логики — это нирвана для повышения скорости чтения и написания кода, верно?). Если вы понаблюдаете за этим, вы обнаружите, что эта реализация RxJava представляет собой цепной вызов сверху вниз без какой-либо вложенности, что имеет преимущества с точки зрения простоты логики. Когда требования станут более сложными, это преимущество будет более очевидным (Представьте, если требуется выбрать только первые 10 изображений, что нам делать традиционным способом? А если таких требований больше? Подумайте опять же, в этом большом количестве требований После двух месяцев внедрения нужно изменить функцию.Когда вы вернетесь сюда и увидите загадочный отступ, который вы написали в начале, вы можете гарантировать, что вы быстро его поймете, а не пере -думая код?).

Кроме того, если вашей IDE является Android Studio, фактически каждый раз, когда вы открываете файл Java, вы будете видеть предварительный просмотр, который автоматически Lambdaized, что позволит вам более четко увидеть логику программы:

Observable.from(folders)
    .flatMap((Func1) (folder) -> { Observable.from(file.listFiles()) })
    .filter((Func1) (file) -> { file.getName().endsWith(".png") })
    .map((Func1) (file) -> { getBitmapFromFile(file) })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe((Action1) (bitmap) -> { imageCollectorView.addImage(bitmap) });

Если вы привыкли использовать Retrolambda, вы также можете написать код непосредственно в приведенной выше краткой форме. И если вы видите здесь и не знаете, что такое Retrolambda, я не рекомендую изучать это сейчас. Причин две: 1. Lambda — это палка о двух концах, она делает ваш код лаконичным и в то же время снижает его читабельность, поэтому одновременное изучение RxJava и Retrolambda может заставить вас игнорировать некоторые технические детали RxJava. 2. Retrolambda — это неофициальное решение для лямбда-выражений, совместимое с Java 6/7. Его обратная совместимость и стабильность не могут быть гарантированы, поэтому для корпоративных проектов использование Retrolambda рискованно. Так что, в отличие от многих промоутеров RxJava, я не рекомендую изучать Retrolambda вместе с RxJava. На самом деле, я лично ценю Retrolambda, но никогда ею не пользуюсь.

В Android-коде Flipboard присутствует очень сложная логика, включающая множественные операции с памятью, операции с локальными файлами и сетевые операции, объекты делятся и объединяются, потоки взаимодействуют друг с другом и ждут друг друга. Если вы используете обычный метод для реализации, он должен быть написан как фея, но в случае использования RxJava это все еще просто цепочка вызовов для завершения. Это долго, но ясно.

Итак, что же такого хорошего в RxJava? Хорошо быть кратким, и хорошо сводить любую сложную логику к простоте.

Введение в API и принципиальный анализ

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

1. Концепция: шаблон расширенного наблюдателя

Асинхронная реализация RxJava достигается за счет расширенного шаблона наблюдателя.

Шаблон наблюдателя

Сначала кратко опишем паттерн Observer, те, кто с ним уже знаком, могут пропустить этот абзац.

Требования паттерна наблюдатель таковы: объект А (наблюдатель) очень чувствителен к некоторым изменениям в объекте Б (наблюдаемый), и должен реагировать в момент изменения В. Например, полиции, которая любит видеть в новостях ловлю вора, полиции нужно арестовать вора, когда он протянет руку, чтобы совершить преступление. В этом примере полицейский выступает в роли наблюдателя, а вор — в роли наблюдаемого.Полицейский должен следить за каждым движением вора, чтобы не упустить ни одного момента. Режим наблюдателя программы несколько отличается от настоящего «наблюдения»: наблюдателю не нужно все время смотреть на наблюдаемое (например, А не нужно проверять состояние В каждые 2 мс), а используетсярегистр(Register)или позвонилподписка(Subscribe)способ сказать наблюдаемому: мне нужно ваше такое-то состояние, и вы должны уведомить меня, когда оно изменится. Типичным примером в разработке для Android является прослушиватель кликов.OnClickListener. настройки парыOnClickListenerСказать,Viewнаблюдается,OnClickListenerявляется наблюдателем, как черезsetOnClickListener()способ достижения отношения подписки. В тот момент, когда пользователь нажимает кнопку после подписки, Android Framework отправит событие click зарегистрированному пользователю.OnClickListener. Принятие такого пассивного метода наблюдения не только экономит потребление ресурсов при повторном извлечении состояния, но также позволяет получить самую высокую скорость обратной связи. Конечно, это также выигрывает от того, что мы можем настроить наблюдателей и наблюдаемых лиц в наших собственных программах по своему желанию, и полицейский дядя, очевидно, не может попросить вора «вы должны уведомить меня, когда вы совершите преступление».

Шаблон OnClickListener примерно следующий:

OnClickListener 观察者模式

Как показано на рисунке, черезsetOnClickListener()метод,ButtonдержатьOnClickListenerссылка (этот процесс не нарисован на схеме); когда пользователь нажимает,Buttonавтоматический вызовOnClickListenerизonClick()метод. Кроме того, если понятие на этой картинке абстрагировано (Button-> Наблюдается,OnClickListener-> Наблюдатель,setOnClickListener()-> подписаться,onClick()-> события), из режима выделенного наблюдателя (например, только для мониторинга кликов по элементам управления) в режим обычного наблюдателя. Как показано ниже:

通用观察者模式

RxJava, как библиотека инструментов, использует общую форму шаблона наблюдателя.

Шаблон наблюдателя в RxJava

RxJava имеет четыре основных понятия:Observable(наблюдаемый, то есть наблюдаемый),Observer(наблюдатель),subscribe(Подписка), События.ObservableиObserverпройти черезsubscribe()метод реализует отношения подписки, таким образомObservableСобытия могут быть отправлены, чтобы уведомить, когда это необходимоObserver.

В отличие от традиционного режима наблюдателя, метод обратного вызова события RxJava является дополнением к обычным событиям.onNext()(эквивалентноonClick() / onEvent()), определены два особых события:onCompleted()иonError().

  • onCompleted(): очередь событий завершена. RxJava не только обрабатывает каждое событие отдельно, но и рассматривает их как очередь. RxJava указывает, что когда не будет новыхonNext()Когда выпущено, нужно вызватьonCompleted()метод в качестве флага.
  • onError(): Исключение очереди событий. Когда во время обработки события возникает исключение,onError()будет запущен, и очередь будет автоматически завершена, и больше никаких событий не разрешено.
  • В правильно функционирующей последовательности событийonCompleted()иonError()Есть один и только один, и он последний в последовательности событий. должны знать о том,onCompleted()иonError()Эти два также являются взаимоисключающими, то есть, когда один вызывается в очереди, другой не должен вызываться.

Шаблон наблюдателя RxJava примерно следующий:

RxJava 的观察者模式

2. Базовая реализация

Основываясь на вышеизложенных концепциях, базовая реализация RxJava имеет три основных момента:

1) Создать наблюдателя

Наблюдатель — это наблюдатель, он решает, какое поведение он будет иметь при запуске события. в RxJavaObserverРеализация интерфейса:

Observer<String> observer = new Observer<String>() {
    @Override
    public void onNext(String s) {
        Log.d(tag, "Item: " + s);
    }

    @Override
    public void onCompleted() {
        Log.d(tag, "Completed!");
    }

    @Override
    public void onError(Throwable e) {
        Log.d(tag, "Error!");
    }
};

КромеObserverВ дополнение к интерфейсу RxJava также имеет встроенную реализациюObserverАбстрактный класс:Subscriber.SubscriberправильноObserverИнтерфейсы были немного расширены, но их основное использование точно такое же:

Subscriber<String> subscriber = new Subscriber<String>() {
    @Override
    public void onNext(String s) {
        Log.d(tag, "Item: " + s);
    }

    @Override
    public void onCompleted() {
        Log.d(tag, "Completed!");
    }

    @Override
    public void onError(Throwable e) {
        Log.d(tag, "Error!");
    }
};

Не только основное использование одинаково, по сути, в процессе подписки RxJava,Observerвсегда сначала преобразуется вSubscriberповторное использование. Поэтому, если вы просто хотите использовать базовые функции, выберитеObserverиSubscriberточно такой же. Между ними есть два основных отличия для пользователей:

  1. onStart(): ЭтоSubscriberметод увеличения. Он будет вызываться в начале подписки и перед отправкой события и может использоваться для выполнения некоторой подготовительной работы, такой как очистка или сброс данных. Это необязательный метод, и его реализация по умолчанию пуста. Следует отметить, что если потоку требуется подготовить работу (например, всплывающее диалоговое окно, показывающее ход выполнения, это должно быть выполнено в основном потоке),onStart()Не применяется, потому что он всегда вызывается в потоке, в котором происходит подписка, вы не можете указать поток. Чтобы выполнить подготовительную работу на указанном потоке, вы можете использоватьdoOnSubscribe()Подробно метод можно увидеть в следующем тексте.
  2. unsubscribe(): ЭтоSubscriberРеализован еще один интерфейсSubscriptionспособ отписки. После вызова этого методаSubscriberСобытия больше не будут приниматься. Как правило, перед вызовом этого метода вы можете использоватьisUnsubscribed()Сначала оцените статус.unsubscribe()Этот метод важен тем, что вsubscribe()после,Observableбудет держатьSubscriberЕсли эта ссылка не может быть освобождена вовремя, возникает риск утечки памяти. Так что лучше придерживаться эмпирического правила: быть в нужном месте, как только вы его не используете (например,onPause() onStop()и т.д. метод) вызовunsubscribe()чтобы разыменовать отношение, чтобы избежать утечек памяти.
2) Создать наблюдаемый

Observable — это наблюдаемое, оно решает, когда запускать событие и какое событие запускать. Использование RxJavacreate()метод для создания Observable и определения для него правил запуска событий:

Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
    @Override
    public void call(Subscriber<? super String> subscriber) {
        subscriber.onNext("Hello");
        subscriber.onNext("Hi");
        subscriber.onNext("Aloha");
        subscriber.onCompleted();
    }
});

Как видите, аOnSubscribeобъект в качестве параметра.OnSubscribeбудет храниться в возвращаемомObservableобъект, его роль эквивалентна расписанию, когдаObservableПодписавшись,OnSubscribeизcall()Метод будет вызываться автоматически, и последовательность событий будет запускаться по порядку в соответствии с настройками (для приведенного выше кода это наблюдательSubscriberбудет вызываться три разаonNext()и однаждыonCompleted()). Таким образом, callback-метод наблюдателя вызывается наблюдателем, и реализуется доставка события от наблюдателя к наблюдателю, то есть режим наблюдателя.

Этот пример очень прост: содержимое события — это строка, а не какой-то сложный объект; содержимое события уже определено, а не определяется как некоторые шаблоны наблюдателя (например, результат сетевого запроса возвращается в запрос, ранее неизвестный); все события отправляются сразу, а не через какой-то детерминированный или неопределенный интервал времени или запускаются каким-то триггером. В общем, этот пример кажется бесполезным. Но это для иллюстрации, по сути можно сколько угодно писать правила отправки событий. Что касается того, как это сделать, мы поговорим об этом позже, но не сейчас. Только когда сначала объясняются основные принципы, приложение верхнего уровня может быть более легко объяснено.

create()Методы — это самый простой способ создания последовательностей событий в RxJava. На основе этого метода RxJava также предоставляет несколько методов для быстрого создания очередей событий, таких как:

  • just(T...): Отправлять входящие параметры последовательно.
Observable observable = Observable.just("Hello", "Hi", "Aloha");
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();
  • from(T[]) / from(Iterable<? extends T>): переданный массив илиIterableПосле разделения на определенные объекты они отправляются последовательно.
String[] words = {"Hello", "Hi", "Aloha"};
Observable observable = Observable.from(words);
// 将会依次调用:
// onNext("Hello");
// onNext("Hi");
// onNext("Aloha");
// onCompleted();

вышеjust(T...)пример иfrom(T[])пример, оба такие же, как и предыдущийcreate(OnSubscribe)Примеры эквивалентны.

3) Подписаться

созданныйObservableиObserverПосле этого используйтеsubscribe()методы соединяют их, и работает вся цепочка. Форма кода проста:

observable.subscribe(observer);
// 或者:
observable.subscribe(subscriber);

Кто-то может заметить,subscribe()Этот метод немного странный: он выглядит как "observalbeподписалсяobserver / subscriber' вместо 'observer / subscriberподписалсяobservalbe', который выглядит как "журнал подписывается на читателей", чтобы изменить объектное отношение. Это немного неудобно читать, но если API предназначен дляobserver.subscribe(observable) / subscriber.subscribe(observable), хотя это больше соответствует логике мышления, но оказывает влияние на дизайн потокового API, который явно не стоит потерь в сравнении.

Observable.subscribe(Subscriber)Внутренняя реализация такова (только основной код):

// 注意:这不是 subscribe() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public Subscription subscribe(Subscriber subscriber) {
    subscriber.onStart();
    onSubscribe.call(subscriber);
    return subscriber;
}

можно увидеть,subscriber()сделал 3 вещи:

  1. перечислитьSubscriber.onStart(). Этот метод был представлен ранее и является необязательным методом подготовки.
  2. перечислитьObservableсерединаOnSubscribe.call(Subscriber). Здесь начинает работать логика отправки событий. Из этого также видно, что в RxJava,ObservableСобытие отправляется не сразу после создания, а при подписке, т.е.subscribe()когда метод выполняется.
  3. будет передан вSubscriberв видеSubscriptionвозвращение. Это для удобстваunsubscribe().

Отношения между объектами во всем процессе следующие:

关系静图

Или вы можете посмотреть анимацию:

关系静图

Кромеsubscribe(Observer)иsubscribe(Subscriber),subscribe()Также поддерживает не полностью определенные обратные вызовы, RxJava автоматически создаст их в соответствии с определением.Subscriber. Форма выглядит следующим образом:

Action1<String> onNextAction = new Action1<String>() {
    // onNext()
    @Override
    public void call(String s) {
        Log.d(tag, s);
    }
};
Action1<Throwable> onErrorAction = new Action1<Throwable>() {
    // onError()
    @Override
    public void call(Throwable throwable) {
        // Error handling
    }
};
Action0 onCompletedAction = new Action0() {
    // onCompleted()
    @Override
    public void call() {
        Log.d(tag, "completed");
    }
};

// 自动创建 Subscriber ,并使用 onNextAction 来定义 onNext()
observable.subscribe(onNextAction);
// 自动创建 Subscriber ,并使用 onNextAction 和 onErrorAction 来定义 onNext() 和 onError()
observable.subscribe(onNextAction, onErrorAction);
// 自动创建 Subscriber ,并使用 onNextAction、 onErrorAction 和 onCompletedAction 来定义 onNext()、 onError() 和 onCompleted()
observable.subscribe(onNextAction, onErrorAction, onCompletedAction);

Кратко объясните, что появляется в этом кодеAction1иAction0.Action0это интерфейс RxJava, он имеет только один методcall(), этот метод не имеет параметров и возвращаемого значения, посколькуonCompleted()Метод также не имеет параметров и возвращаемого значения, поэтомуAction0можно рассматривать как объект-оболочку, которыйonCompleted()Содержимое пакета упаковывается и передается в качестве параметраsubscribe()для реализации не полностью определенного обратного вызова. Собственно, это тоже можно рассматривать какonCompleted()Метод передается как параметрsubscribe(), что эквивалентно «замыканию» в некоторых других языках.Action1тоже интерфейс, у него тоже только один методcall(T param), этот метод также не имеет возвращаемого значения, но имеет один параметр; иAction0Точно так же, потому чтоonNext(T obj)иonError(Throwable error)Это также один параметр и не возвращаемое значение, поэтомуAction1можетonNext(obj)иonError(error)Соберись и пройдиsubscribe()для реализации не полностью определенного обратного вызова. На самом деле, хотяAction0иAction1Наиболее широко используется в API, но RxJava предоставляет несколькоActionXинтерфейс формы (например,Action2, Action3), их можно использовать для обертывания различных невозвратных методов.

Примечание. Как упоминалось ранее,ObserverиSubscriberодинаковые роли иObserverсуществуетsubscribe()в конечном итоге превратится вSubscriberобъект, поэтому с этого момента я буду использоватьSubscriberзаменитьObserver, что является более строгим.

4) Пример сценария

Вот два примера:

Для того, чтобы понятнее выразить принцип, примеры в этой статье подобраны как можно более простыми, чтобы некоторые коды примеров создавали ощущение "лишнего" и "очевидно проще решить задачу без RxJava" . Когда вы видите это, думайте, что это не потому, что RxJava слишком многословна, а потому, что она не способствует анализу принципа, слишком рано приводя примеры реальных сценариев, поэтому я намеренно выбрал простые сценарии.

а) напечатать массив строк

массив строкnamesВсе строки выводятся последовательно:

String[] names = ...;
Observable.from(names)
    .subscribe(new Action1<String>() {
        @Override
        public void call(String name) {
            Log.d(tag, name);
        }
    });
б) Получить картинку по id и отобразить ее

идентификатор файла для рисования, указанныйdrawableResПолучите изображение и отобразите его вImageView, и вывести ошибку Toast при возникновении исключения:

int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe<Drawable>() {
    @Override
    public void call(Subscriber<? super Drawable> subscriber) {
        Drawable drawable = getTheme().getDrawable(drawableRes));
        subscriber.onNext(drawable);
        subscriber.onCompleted();
    }
}).subscribe(new Observer<Drawable>() {
    @Override
    public void onNext(Drawable drawable) {
        imageView.setImageDrawable(drawable);
    }

    @Override
    public void onCompleted() {
    }

    @Override
    public void onError(Throwable e) {
        Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
    }
});

Как и в двух приведенных выше примерах, создайтеObservableиSubscriber, затем используйтеsubscribe()Соедините их вместе, и базовое использование RxJava готово. очень простой.

Тем не мение,

这并没有什么diao用

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

3. Управление потоками — Планировщик (1)

Без указания потока RxJava следует принципу инвариантности потока, то есть: какой поток вызываетsubscribe(), какой поток создает события, какой поток создает события и какой поток потребляет события. Если вам нужно переключить потоки, вам нужно использоватьScheduler(планировщик).

1) API планировщика (1)

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

  • Schedulers.immediate(): запуск непосредственно в текущем потоке, что эквивалентно отсутствию указания потока. Это значение по умолчаниюScheduler.
  • Schedulers.newThread(): Всегда запускайте новый поток и выполняйте операции в новом потоке.
  • Schedulers.io(): используется для операций ввода-вывода (чтение и запись файлов, чтение и запись баз данных, обмен сетевой информацией и т. д.)Scheduler. модели поведения иnewThread()почти, разницаio()Внутренняя реализация заключается в использовании неограниченного количества пулов потоков, которые могут повторно использовать простаивающие потоки, поэтому в большинстве случаевio()СравниватьnewThread()более эффективным. Не ставьте вычислительную работу наio(), вы можете избежать создания ненужных потоков.
  • Schedulers.computation(): рассчитано с использованиемScheduler. Этот расчет относится к вычислениям с интенсивным использованием ЦП, т. е. к операциям, производительность которых не ограничивается такими операциями, как ввод-вывод, например, графическими вычислениями. этоSchedulerИспользуемый фиксированный пул потоков, размер которого равен количеству ядер ЦП. Не помещайте операции ввода-вывода вcomputation(), в противном случае время ожидания операции ввода-вывода приведет к пустой трате ЦП.
  • Кроме того, в Android есть специальныйAndroidSchedulers.mainThread(), указанная операция будет выполняться в основном потоке Android.

С этимиScheduler, ты можешь использоватьsubscribeOn()иobserveOn()Существует два метода управления потоком. *subscribeOn(): уточнитьsubscribe()возникающий поток, т.е.Observable.OnSubscribeПоток, в котором он был активирован. Или вызвать поток, в котором генерируется событие. *observeOn(): уточнитьSubscriberПоток, который выполняется. Или называется потоком потребления событий.

Текстовое описание всегда сложно понять, код выше:

Observable.just(1, 2, 3, 4)
    .subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
    .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
    .subscribe(new Action1<Integer>() {
        @Override
        public void call(Integer number) {
            Log.d(tag, "number:" + number);
        }
    });

В приведенном выше коде, потому чтоsubscribeOn(Schedulers.io())указывает содержание создаваемого события1,2,3,4будет выдаваться в потоке ввода-вывода; и из-заobserveOn(AndroidScheculers.mainThread()), поэтомуsubscriberПечать чисел будет происходить в основном потоке. На самом деле это вsubscribe()Я написал два предложения раньшеsubscribeOn(Scheduler.io())иobserveOn(AndroidSchedulers.mainThread())Его очень часто используют, и он подходит для большинства программных стратегий «выборки данных из фоновых потоков и отображения их в основном потоке».

И вышеупомянутый пример получения картинки по id картинки и ее отображения, если еще добавить эти два предложения:

int drawableRes = ...;
ImageView imageView = ...;
Observable.create(new OnSubscribe<Drawable>() {
    @Override
    public void call(Subscriber<? super Drawable> subscriber) {
        Drawable drawable = getTheme().getDrawable(drawableRes));
        subscriber.onNext(drawable);
        subscriber.onCompleted();
    }
})
.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
.observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
.subscribe(new Observer<Drawable>() {
    @Override
    public void onNext(Drawable drawable) {
        imageView.setImageDrawable(drawable);
    }

    @Override
    public void onCompleted() {
    }

    @Override
    public void onError(Throwable e) {
        Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
    }
});

Затем загрузка изображения будет происходить в потоке ввода-вывода, а установка изображения будет установлена ​​в основном потоке. Это означает, что даже если загрузка изображения занимает десятки или даже сотни миллисекунд, это не вызовет никаких задержек интерфейса.

2) Принцип планировщика (1)

RxJava Scheduler API очень удобен и волшебен (тред переключается добавлением предложения, как это сделать? Иsubscribe()Разве это не метод, вызываемый непосредственно самым внешним слоем, ему также может быть назначен поток? ). Однако принцип Планировщика необходимо обсудить позже, поскольку его принцип основан на принципе следующего раздела «Трансформация».

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

4. Преобразование

Наконец-то я добираюсь до прекрасного места, взволнованы вы или нет, но я все равно взволнован.

RxJava обеспечивает поддержку преобразования последовательностей событий, что является одной из его основных функций и основной причиной, по которой большинство людей говорят, что «RxJava так удобна в использовании».Так называемое преобразование заключается в обработке объектов или всей последовательности в последовательности событий и преобразовании их в другие события или последовательности событий.Концепции всегда расплывчаты и трудны для понимания, посмотрите на API.

1) API

Первый взгляд на одинmap()пример:

Observable.just("images/logo.png") // 输入类型 String
    .map(new Func1<String, Bitmap>() {
        @Override
        public Bitmap call(String filePath) { // 参数类型 String
            return getBitmapFromPath(filePath); // 返回类型 Bitmap
        }
    })
    .subscribe(new Action1<Bitmap>() {
        @Override
        public void call(Bitmap bitmap) { // 参数类型 Bitmap
            showBitmap(bitmap);
        }
    });

Здесь естьFunc1тип. это иAction1Очень похоже, это тоже интерфейс RxJava, используемый для переноса метода с одним параметром.Func1иActionРазница в том, чтоFunc1Обертывает метод возвращаемым значением. Кроме того, иActionXТакой же,FuncXСуществует также несколько методов для разного количества параметров.FuncXиActionXРазница в том,FuncXОбертывает метод возвращаемым значением.

можно увидеть,map()Метод будет иметь параметры вStringобъект превращается вBitmapобъект возвращается, и послеmap()После метода также указывается тип параметра событияStringповернулся кBitmap. Это прямое преобразование объекта и возврата является наиболее распространенным и простым для понимания преобразованием. Однако преобразование RxJava — это гораздо больше, оно может быть нацелено не только на объект события, но и на всю очередь событий, что делает RxJava очень гибким. Я перечисляю несколько часто используемых преобразований:

  • map(): Прямое преобразование объекта события, специальные функции были представлены выше. Это наиболее часто используемое преобразование для RxJava.map()Схема:map() 示意图

  • flatMap(): Это полезно, ноочень трудно понять, поэтому я решил потратить на него больше места. Сначала предположим такое требование: Предположим, есть структура данных «студенты», и теперь вам нужно вывести имена группы студентов. Реализация проста:

Student[] students = ...;
Subscriber<String> subscriber = new Subscriber<String>() {
    @Override
    public void onNext(String name) {
        Log.d(tag, name);
    }
    ...
};
Observable.from(students)
    .map(new Func1<Student, String>() {
        @Override
        public String call(Student student) {
            return student.getName();
        }
    })
    .subscribe(subscriber);

Простой. Затем снова предположим: что, если вы хотите распечатать названия всех курсов, которые должен пройти каждый студент? (Разница в требованиях в том, что у каждого студента только одно имя, но несколько курсов.) Это можно сделать сначала:

Student[] students = ...;
Subscriber<Student> subscriber = new Subscriber<Student>() {
    @Override
    public void onNext(Student student) {
        List<Course> courses = student.getCourses();
        for (int i = 0; i < courses.size(); i++) {
            Course course = courses.get(i);
            Log.d(tag, course.getName());
        }
    }
    ...
};
Observable.from(students)
    .subscribe(subscriber);

Еще очень просто. тогда, если я не хочу бытьSubscriberиспользуя цикл for вSubscriberнепосредственно пройти в одиночномCourseА как насчет объектов (это важно для повторного использования кода)? использоватьmap()Явно нет, потому чтоmap()Это преобразование «один к одному», и мое текущее требование — преобразование «один ко многим». Итак, как вы можете преобразовать студента в несколько курсов?

В это время необходимо использоватьflatMap()в настоящее время:

Student[] students = ...;
Subscriber<Course> subscriber = new Subscriber<Course>() {
    @Override
    public void onNext(Course course) {
        Log.d(tag, course.getName());
    }
    ...
};
Observable.from(students)
    .flatMap(new Func1<Student, Observable<Course>>() {
        @Override
        public Observable<Course> call(Student student) {
            return Observable.from(student.getCourses());
        }
    })
    .subscribe(subscriber);

Как видно из приведенного выше кода,flatMap()иmap()Есть одно общее: он также возвращает другой объект после преобразования входящего параметра. Но заметьте, иmap()разница в том,flatMap()вернулся вObservableобъект, и этоObservableобъекты не отправляются напрямую вSubscriberв методе обратного вызова.flatMap()Принцип таков: 1. Используйте объект входящего события для созданияObservableобъект; 2. не отправлять этоObservable, но активируйте его, чтобы он начал отправлять события; 3. Каждый созданныйObservableВсе отправленные события импортируются в один и тот жеObservable, и этоObservableОтвечает за объединение этих событий вSubscriberметод обратного вызова. Эти три шага разбивают событие на два уровня с помощью набора вновь созданныхObservableИсходный объект «сплющен» и распределен по единому пути. И это "сглаживание"flatMap()Так называемая квартира.

flatMap()Схема:

flatMap() 示意图

Расширения: Поскольку можно использовать вложенныеObservableДобавьте асинхронный код вflatMap()Также обычно используется для вложенных асинхронных операций, таких как вложенные сетевые запросы. Пример кода (доработка + RxJava):

networkClient.token() // 返回 Observable<String>,在订阅时请求 token,并在响应后发送 token
    .flatMap(new Func1<String, Observable<Messages>>() {
        @Override
        public Observable<Messages> call(String token) {
            // 返回 Observable<Messages>,在订阅时请求消息列表,并在响应后发送请求到的消息列表
            return networkClient.messages();
        }
    })
    .subscribe(new Action1<Messages>() {
        @Override
        public void call(Messages messages) {
            // 处理显示消息列表
            showMessages(messages);
        }
    });

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

  • throttleFirst(): отбрасывать новые события в течение определенного интервала времени после запуска каждого события. Обычно используется для фильтрации дребезга, например прослушивателей нажатия кнопок:RxView.clickEvents(button) // RxBinding 代码,后面的文章有解释 .throttleFirst(500, TimeUnit.MILLISECONDS) // 设置防抖间隔为 500ms .subscribe(subscriber);Мама больше не боится, что мои пользователи пожимают друг другу руки, чтобы открыть два одинаковых интерфейса.

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

2) Принцип трансформации: lift()

Хотя эти преобразования имеют разные функции, по сути они одинаковы.Обработка и повторная отправка последовательностей событий. А внутри RxJava они основаны на одном и том же базовом методе преобразования:lift(Operator). Первый взглядlift()Внутренняя реализация (только основной код):

// 注意:这不是 lift() 的源码,而是将源码中与性能、兼容性、扩展性有关的代码剔除后的核心代码。
// 如果需要看源码,可以去 RxJava 的 GitHub 仓库下载。
public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {
    return Observable.create(new OnSubscribe<R>() {
        @Override
        public void call(Subscriber subscriber) {
            Subscriber newSubscriber = operator.call(subscriber);
            newSubscriber.onStart();
            onSubscribe.call(newSubscriber);
        }
    });
}

Этот код интересен: он генерирует новыйObservableи вернуться, и создать новыйObservableиспользуемые параметрыOnSubscribeметод обратного вызоваcall()Реализация вObservable.subscribe()Такой же! Однако они не одинаковы~ Ключ к разнице лежит во второй строчке.onSubscribe.call(subscriber)серединаonSubscribeРеференты разные(Предупреждение о высоком уровне энергии: следующие несколько предложений могут вызвать серьезный физический дискомфорт) -

  • subscribe()в этом предложенииonSubscribeОтноситсяObservableсерединаonSubscribeобъект, это не проблема, ноlift()После этого все стало немного сложнее.
  • при содержанииlift()Время:
    1.lift()создалObservableпосле плюс оригинал доObservable, их уже дваObservable;
    2. А также новыйObservableновое вOnSubscribeплюс оригинал доObservableоригинал вOnSubscribe, есть дваOnSubscribe;
    3. Когда пользователь звонит черезlift()ПослеObservableизsubscribe()когда используешьlift()вернулся новыйObservable, поэтому он вызываетonSubscribe.call(subscriber), также используя новыйObservableновое вOnSubscribe, то есть вlift()тот, который генерируется вOnSubscribe;
    4. А этот новыйOnSubscribeизcall()в методеonSubscribe, значит оригиналObservableоригинал вOnSubscribe,на этоcall()метод, новыйOnSubscribeиспользоватьoperator.call(subscriber)создал новыйSubscriber(Operatorпрямо здесь, через свой собственныйcall()метод будет новымSubscriberи оригинальныйSubscriberсоздайте ассоциацию и вставьте свой собственный код «преобразования», чтобы реализовать преобразование), а затем воспользуйтесь преимуществами этого новогоSubscriberк оригиналуObservableподписываться.
    Вот и всеlift()процесс, немногоПодобно прокси-механизму, последовательность событий преобразуется посредством перехвата и обработки событий.

То же самое можно сказать и об уменьшении деталей: вObservableказненlift(Operator)метод, вернет новыйObservable, этот новыйObservableбудет действовать как прокси, ответственный за получение оригиналаObservableГенерируемые события, а после обработки отправляются вSubscriber.

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

lift() 原理图

Или вы можете посмотреть анимацию:

lift 原理动图

дважды и многократноlift()Аналогично, как показано ниже:

两次 lift

дать конкретноеOperatorреализация. Вот пример, который преобразует событиеIntegerобъект, преобразованный вStringПример, просто для справки:

observable.lift(new Observable.Operator<String, Integer>() {
    @Override
    public Subscriber<? super Integer> call(final Subscriber<? super String> subscriber) {
        // 将事件序列中的 Integer 对象转换为 String 对象
        return new Subscriber<Integer>() {
            @Override
            public void onNext(Integer integer) {
                subscriber.onNext("" + integer);
            }

            @Override
            public void onCompleted() {
                subscriber.onCompleted();
            }

            @Override
            public void onError(Throwable e) {
                subscriber.onError(e);
            }
        };
    }
});

рассказатьlift()Обоснование состоит в том, чтобы дать вам лучшее понимание RxJava, чтобы вы могли лучше его использовать. Но понимаешь ты или нетlift()Принцип RxJava не рекомендуется разработчикам настраиватьOperatorиспользовать напрямуюlift(), рекомендуется использовать существующийlift()методы упаковки (например,map() flatMap()и т. д.) для реализации требований, потому что прямое использование lift() очень подвержено некоторым трудно обнаруживаемым ошибкам.

3) составить: трансформация Наблюдаемого в целом

Кромеlift()Кроме,ObservableСуществует также метод преобразования, называемыйcompose(Transformer). это иlift()Разница в том, чтоlift()предназначен для элементов событий и последовательностей событий, аcompose()направлен наObservableтрансформировать себя.Например, пусть в программе есть несколькоObservable, и они оба должны применять один и тот же наборlift()трансформировать. Ты можешь написать:

observable1
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber1);
observable2
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber2);
observable3
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber3);
observable4
    .lift1()
    .lift2()
    .lift3()
    .lift4()
    .subscribe(subscriber1);

Вы думаете, что это слишком программная инженерия, поэтому вы меняете это на это:

private Observable liftAll(Observable observable) {
    return observable
        .lift1()
        .lift2()
        .lift3()
        .lift4();
}
...
liftAll(observable1).subscribe(subscriber1);
liftAll(observable2).subscribe(subscriber2);
liftAll(observable3).subscribe(subscriber3);
liftAll(observable4).subscribe(subscriber4);

Читаемость и ремонтопригодность улучшены. НоObservableобернут в метод, который полезен дляObservaleГибкость, кажется, добавляет немного ограничения. что делать? В это время вы должны использоватьcompose()решать:

public class LiftAllTransformer implements Observable.Transformer<Integer, String> {
    @Override
    public Observable<String> call(Observable<Integer> observable) {
        return observable
            .lift1()
            .lift2()
            .lift3()
            .lift4();
    }
}
...
Transformer liftAll = new LiftAllTransformer();
observable1.compose(liftAll).subscribe(subscriber1);
observable2.compose(liftAll).subscribe(subscriber2);
observable3.compose(liftAll).subscribe(subscriber3);
observable4.compose(liftAll).subscribe(subscriber4);

Как и выше, используйтеcompose()метод,Observableможно использовать входящиеTransformerобъектcallМетод обрабатывает сам себя напрямую, поэтому его не нужно оборачивать в метод.

compose()Принцип относительно прост, не прилагается.

5. Управление потоком: Планировщик (2)

В дополнение к гибким преобразованиям, еще одна интересная вещь в RxJava — это свободное управление потоками.

1) API планировщика (2)

Как упоминалось ранее, вы можете использоватьsubscribeOn()комбинироватьobserveOn()Чтобы добиться управления потоком, чтобы генерация и потребление событий происходили в разных потоках. Но пониманиеmap() flatMap()После изменения метода некоторые хорошие вещи (на самом деле, когда я впервые столкнулся с RxJava) спросили: Могу ли я переключать потоки еще несколько раз?

Ответ: да. так какobserveOn()указаноSubscriberнить, и этоSubscriberНе (строго говоря, должно быть "не обязательно", а здесь может пониматься как "нет")subscribe()в параметреSubscriber, ноobserveOn()текущий во время выполненияObservableсоответствующийSubscriber, т.е. его непосредственный подчиненныйSubscriber. другими словами,observeOn()Указывается поток, в котором выполняется операция после него. Поэтому, если есть необходимость переключать потоки несколько раз, просто вызовите его один раз в каждом месте, где вы хотите переключать потоки.observeOn()Вот и все. Над кодом:

Observable.just(1, 2, 3, 4) // IO 线程,由 subscribeOn() 指定
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.newThread())
    .map(mapOperator) // 新线程,由 observeOn() 指定
    .observeOn(Schedulers.io())
    .map(mapOperator2) // IO 线程,由 observeOn() 指定
    .observeOn(AndroidSchedulers.mainThread) 
    .subscribe(subscriber);  // Android 主线程,由 observeOn() 指定

как указано выше, черезobserveOn()В программе реализовано многократное переключение потоков.

Однако, в отличие отobserveOn(),subscribeOn()Локацию можно разместить где угодно, но вызвать ее можно только один раз.

Еще одна хорошая вещь (на самом деле, это был я в начале) спросил: если мне придется звонить несколько разsubscribeOn()Шерстяная ткань? Каков будет эффект?

Оставим этот вопрос в стороне, начнем с принципа управления потоком RxJava.

2) Принцип планировщика (2)

фактически,subscribeOn()иobserveOn()Также используется внутренняя реализация ,lift(). Внимательно посмотрите на картинку (стрелки разного цвета обозначают разные нити):

subscribeOn()Схема:

subscribeOn() 原理

observeOn()Схема:

observeOn() 原理

Как видно из рисунка,subscribeOn()иobserveOn()Все выполнили работу по переключению потоков (часть "расписание..." на рисунке). разница в том,subscribeOn()Переключение потоков происходит вOnSubscribeв, т.е. информирует верхний уровеньOnSubscribe, событие еще не начало отправляться, поэтомуsubscribeOn()На управление потоком можно повлиять с момента возникновения события; в то время какobserveOn()Переключение потоков происходит в его встроенномSubscriber, это происходит, когда он собирается дать следующий уровеньSubscriberПри отправке события, поэтомуobserveOn()Контроль — это нить, стоящая за этим.

Наконец, я использую диаграмму, чтобы объяснить, когда несколькоsubscribeOn()иobserveOn()Как происходит планирование потоков при смешанном использовании (из-за большого количества объектов на рисунке в структуру были внесены некоторые упрощенные корректировки по сравнению с рисунком выше):

线程控制综合调用

На рисунке 5 мест, которые содержат операции над событиями. Как видно из рисунка, ① и ② подвержены влиянию первогоsubscribeOn()Затронуты красной нитью; ③ и ④ затронуты первымobserveOn()под влиянием зеленой нити; ⑤ под влиянием второгоonserveOn()аффект, бегущий по пурпурной нити; а второйsubscribeOn(), так как поток является первымsubscribeOn()усечение, поэтому это не влияет на весь процесс. Это также отвечает на предыдущий вопрос: при использовании несколькихsubscribeOn(), только первыйsubscribeOn()ворваться.

3) Расширение: doOnSubscribe()

Однако, хотя более одногоsubscribeOn()Не влияет на поток обработки событий, но доступен перед потоком.

говорить передSubscriber, упомянулSubscriberизonStart()Может использоваться в качестве инициализации перед запуском процесса. тем не мениеonStart()из-заsubscribe()вызывается, когда это происходит, поэтому поток не может быть указан, а может быть выполнен только вsubscribe()Поток при вызове. Это приводит к тому, еслиonStart()Содержит код, требующий потоков (например, отображение ProgressBar на интерфейсе, который должен выполняться в основном потоке), будет риск нелегального потока, потому что иногда вы не можете предсказатьsubscribe()В каком потоке это будет выполняться.

и сSubscriber.onStart()Соответственно существует методObservable.doOnSubscribe(). это иSubscriber.onStart()Также вsubscribe()Выполняется после вызова и перед отправкой события, но с той разницей, что может указывать поток. по умолчанию,doOnSubscribe()выполнить вsubscribe()поток, который происходит; и если вdoOnSubscribe()после этого естьsubscribeOn(), он будет выполнен в ближайшееsubscribeOn()указанный поток.

Образец кода:

Observable.create(onSubscribe)
    .subscribeOn(Schedulers.io())
    .doOnSubscribe(new Action0() {
        @Override
        public void call() {
            progressBar.setVisibility(View.VISIBLE); // 需要在主线程执行
        }
    })
    .subscribeOn(AndroidSchedulers.mainThread()) // 指定主线程
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(subscriber);

как указано выше, вdoOnSubscribe()за которым следуетsubscribeOn(), можно указать поток, который готовит работу.

Применимые сценарии и использование RxJava

1. Комбинация с дооснащением

Retrofit — известная библиотека сетевых запросов для Square. Те, кто не использовал Retrofit, могут пропустить этот раздел, это не имеет значения, каждый сценарий, который я упомянул, является просто примером, и между примерами нет контекста, это просто роль для привлечения других, поэтому вы пропускаете здесь чтобы увидеть другие сценарии Это тоже хорошо.

Модернизация обеспечивает в дополнение к традиционнымCallbackform API и RxJava-версияObservableAPI формы. Ниже я использую метод сравнения, чтобы показать разницу между API версии Retrofit RxJava и традиционной версией.

чтобы получитьUserИнтерфейс объекта в качестве примера. Используя традиционный API Retrofit, вы можете определять запросы следующим образом:

@GET("/user")
public void getUser(@Query("userId") String userId, Callback<User> callback);

Во время построения программы Retrofit автоматически реализует метод и генерирует код, а затем разработчик может использовать следующий метод для получения конкретного пользователя и обработки ответа:

getUser(userId, new Callback<User>() {
    @Override
    public void success(User user) {
        userView.setUser(user);
    }

    @Override
    public void failure(RetrofitError error) {
        // Error handling
        ...
    }
};

А с помощью API в виде RxJava тот же запрос определяется так:

@GET("/user")
public Observable<User> getUser(@Query("userId") String userId);

При использовании так:

getUser(userId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<User>() {
        @Override
        public void onNext(User user) {
            userView.setUser(user);
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable error) {
            // Error handling
            ...
        }
    });

Увидеть разницу?

В форме RxJava Retrofit упаковывает запрос вObservable, вызывается после завершения запросаonNext()или вызывается после неудачного запросаonError().

По сравнению,Callbackформа иObservableФорма выглядит иначе, но суть похожа, и в деталяхObservableформа вроде быCallbackФорма почти. Так почему же Retrofit по-прежнему поддерживает RxJava?

Потому что это отлично работает! Из этого примера это не очевидно, потому что это самый простой случай. А когда ситуация усложняется,CallbackФорма может сразу начать давать головную боль. Например:

Предположим такую ​​ситуацию: ваша программа получаетUserОн не должен отображаться напрямую, но его необходимо сравнить и скорректировать с данными в базе данных перед отображением. использоватьCallbackПуть можно записать так:

getUser(userId, new Callback<User>() {
    @Override
    public void success(User user) {
        processUser(user); // 尝试修正 User 数据
        userView.setUser(user);
    }

    @Override
    public void failure(RetrofitError error) {
        // Error handling
        ...
    }
};

Есть проблема?

Это легко, но не делайте этого. Зачем? Потому что это повлияет на производительность. Работа с базой данных очень тяжелая, и операции чтения и записи очень часто занимают 10–20 мс, что может легко привести к зависанию интерфейса. Поэтому в целом вам следует избегать работы с базой данных в основном потоке, если это возможно. Таким образом, для повышения производительности этот код можно оптимизировать:

getUser(userId, new Callback<User>() {
    @Override
    public void success(User user) {
        new Thread() {
            @Override
            public void run() {
                processUser(user); // 尝试修正 User 数据
                runOnUiThread(new Runnable() { // 切回 UI 线程
                    @Override
                    public void run() {
                        userView.setUser(user);
                    }
                });
            }).start();
    }

    @Override
    public void failure(RetrofitError error) {
        // Error handling
        ...
    }
};

Проблема с производительностью решена, но... этот код слишком беспорядочный, а отступы сумасшедшие! Беспорядочный код часто является не только вопросом эстетики, потому что чем беспорядочнее код, тем труднее его читать.Если проект полон беспорядочного кода, это, несомненно, снизит читабельность кода, что приведет к сокращению команды. эффективность разработки и более высокий уровень ошибок.

В настоящее время, если вы используете форму RxJava, с ней намного проще обращаться. Код в форме RxJava выглядит так:

getUser(userId)
    .doOnNext(new Action1<User>() {
        @Override
        public void call(User user) {
            processUser(user);
        })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<User>() {
        @Override
        public void onNext(User user) {
            userView.setUser(user);
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable error) {
            // Error handling
            ...
        }
    });

Фоновый код и внешний код написаны в одной цепочке, что, очевидно, намного понятнее.

Другой пример: допустим/userДоступ к интерфейсу невозможен напрямую, но необходимо заполнить полученную онлайн-форму.token, как должен быть написан код?

Callbackспособ, вы можете использовать вложенныйCallback:

@GET("/token")
public void getToken(Callback<String> callback);

@GET("/user")
public void getUser(@Query("token") String token, @Query("userId") String userId, Callback<User> callback);

...

getToken(new Callback<String>() {
    @Override
    public void success(String token) {
        getUser(token, userId, new Callback<User>() {
            @Override
            public void success(User user) {
                userView.setUser(user);
            }

            @Override
            public void failure(RetrofitError error) {
                // Error handling
                ...
            }
        };
    }

    @Override
    public void failure(RetrofitError error) {
        // Error handling
        ...
    }
});

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

С RxJava код выглядит так:

@GET("/token")
public Observable<String> getToken();

@GET("/user")
public Observable<User> getUser(@Query("token") String token, @Query("userId") String userId);

...

getToken()
    .flatMap(new Func1<String, Observable<User>>() {
        @Override
        public Observable<User> onNext(String token) {
            return getUser(token, userId);
        })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<User>() {
        @Override
        public void onNext(User user) {
            userView.setUser(user);
        }

        @Override
        public void onCompleted() {
        }

        @Override
        public void onError(Throwable error) {
            // Error handling
            ...
        }
    });

использовать одинflatMap()Просто поймите логику, это все-таки цепочка. Выглядит круто, не правда ли?

Обновление 2016/03/31, а также пример проекта, который я написал:
rengwuxian RxJava Samples

Ну, вот и все, что касается дооснащения.

2. RxBinding

RxBinding— это библиотека с открытым исходным кодом Джейка Уортона, которая предоставляет набор API-интерфейсов Binding на основе RxJava для платформы Android. Так называемый Binding — аналогичная настройкаOnClickListener,настраиватьTextWatcherТакой API для регистрации объектов привязки.

Приведите пример настройки прослушивателей кликов. использоватьRxBinding, вы можете установить прослушиватель событий следующим образом:

Button button = ...;
RxView.clickEvents(button) // 以 Observable 形式来反馈点击事件
    .subscribe(new Action1<ViewClickEvent>() {
        @Override
        public void call(ViewClickEvent event) {
            // Click handling
        }
    });

Кажется, что нет никакой разницы, кроме изменения формы, что верно и по существу. Даже если вы посмотрите на его исходный код, вы обнаружите, что он даже не удивляет реализацией: его внутренности непосредственно обернутыsetOnClickListener()быть реализованным. Однако только это изменение формы оказываетсяRxBindingЦель: расширяемость. пройти черезRxBindingПреобразование прослушивателя кликов вObservableПосле этого есть возможность его расширить. Есть много способов расширения, в зависимости от ваших потребностей. Пример упоминался ранееthrottleFirst(), который используется для дешейка, то есть для устранения быстрых щелчков цепи, вызванных дрожанием рук:

RxView.clickEvents(button)
    .throttleFirst(500, TimeUnit.MILLISECONDS)
    .subscribe(clickAction);

если хотитеRxBindingДля получения дополнительной информации вы можете перейти на егоGitHub-проектПосмотрите ниже.

3. Различные асинхронные операции

поднятый впередRetrofitиRxBindingПример, два могут предоставить готовыеObservableбиблиотека. И если у вас есть некоторые асинхронные операции, которые не могут быть автоматически сгенерированы с этими библиотекамиObservable, вы также можете написать его самостоятельно. Например, чтение и запись базы данных, загрузка больших изображений, сжатие/распаковка файлов и другие трудоемкие операции, которые необходимо выполнять в фоновом режиме, могут быть реализованы с помощью RxJava.С примерами из предыдущих глав больше не должно быть примеры здесь. .

4. RxBus

Название RxBus похоже на библиотеку, но это не библиотека, а паттерн.Идея состоит в том, чтобы использовать RxJava для реализации EventBus, чтобы вам больше не нужно было использоватьOttoили ГринРоботEventBus. Что такое RxBus, вы можете увидетьэта статья. Кстати, Flipboard заменен на RxBus.Otto, до сих пор никаких побочных реакций.

Наконец

Для Android-разработчиков RxJava — сложная библиотека для начала работы, поскольку в ней слишком много незнакомых для Android-разработчиков концепций. Но это действительно круто. Поэтому я написал это «Подробное объяснение RxJava для разработчиков Android», надеясь дать некоторое руководство тем, кто до сих пор не понимает, что такое RxJava, или позволить людям, которые используют RxJava, но все еще сомневаются, увидеть некоторые более подробные сведения. анализ. В любом случае, если это может помочь вам, коллегам-разработчикам Android, цель этой статьи достигнута.

Оригинальный адрес блога автора: http://gank.io/post/560e15be2dca930e00da1083