Когда следует использовать RxJava Observable и когда простой обратный вызов на Android?


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

@GET("/user/{id}/photo")
void getUserPhoto(@Path("id") int id, Callback<Photo> cb);

и RxJava Observable

@GET("/user/{id}/photo")
Observable<Photo> getUserPhoto(@Path("id") int id);

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

в то время как с простой реализацией обратного вызова будет выглядеть примерно так:

api.getUserPhoto(photoId, new Callback<Photo>() {
    @Override
    public void onSuccess() {
    }
});

что довольно просто и понятно. И с Observable он быстро становится многословным и довольно сложным.

public Observable<Photo> getUserPhoto(final int photoId) {
    return Observable.create(new Observable.OnSubscribeFunc<Photo>() {
        @Override
        public Subscription onSubscribe(Observer<? super Photo> observer) {
            try {
                observer.onNext(api.getUserPhoto(photoId));
                observer.onCompleted();
            } catch (Exception e) {
                observer.onError(e);
            }

            return Subscriptions.empty();
        }
    }).subscribeOn(Schedulers.threadPoolForIO());
}

и это еще не все. Вы все равно должны сделать что-то вроде этого:

Observable.from(photoIdArray)
        .mapMany(new Func1<String, Observable<Photo>>() {
            @Override
            public Observable<Photo> call(Integer s) {
                return getUserPhoto(s);
            }
        })
        .subscribeOn(Schedulers.threadPoolForIO())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Action1<Photo>() {
            @Override
            public void call(Photo photo) {
                //save photo?
            }
        });

Я что-то пропустил? Или это неправильный случай, чтобы использовать Observables? Когда бы / следует предпочесть Observable за простой обратный вызов?

обновление

использование retrofit намного проще, чем пример выше, как показал @Niels в своем ответе или в примере проекта Джейка Уортона U2020. Но по существу вопрос остается тем же - когда следует использовать тот или иной способ?

7 230

7 ответов:

для простого сетевого материала Преимущества RxJava над обратным вызовом очень ограничены. Простой пример getUserPhoto:

RxJava:

api.getUserPhoto(photoId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Photo>() {
            @Override
            public void call(Photo photo) {
               // do some stuff with your photo 
            }
     });

обратного вызова:

api.getUserPhoto(photoId, new Callback<Photo>() {
    @Override
    public void onSuccess(Photo photo, Response response) {
    }
});

вариант RxJava не намного лучше, чем вариант обратного вызова. На данный момент, давайте проигнорируем обработку ошибок. Давайте возьмем список фото:

RxJava:

api.getUserPhotos(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<List<Photo>, Observable<Photo>>() {
    @Override
    public Observable<Photo> call(List<Photo> photos) {
         return Observable.from(photos);
    }
})
.filter(new Func1<Photo, Boolean>() {
    @Override
    public Boolean call(Photo photo) {
         return photo.isPNG();
    }
})
.subscribe(
    new Action1<Photo>() {
    @Override
        public void call(Photo photo) {
            list.add(photo)
        }
    });

обратного вызова:

api.getUserPhotos(userId, new Callback<List<Photo>>() {
    @Override
    public void onSuccess(List<Photo> photos, Response response) {
        List<Photo> filteredPhotos = new ArrayList<Photo>();
        for(Photo photo: photos) {
            if(photo.isPNG()) {
                filteredList.add(photo);
            }
        }
    }
});

теперь вариант RxJava все еще не меньше, хотя с лямбдами он будет ближе к варианту обратного вызова. Кроме того, если у вас есть доступ к каналу JSON, было бы странно извлекать все фотографии, когда вы только отображаете png. Просто отрегулируйте канал, чтобы он отображал только PNGs.

первый вывод

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

теперь давайте сделаем вещи немного интереснее. Допустим, вы не только хотите получить userPhoto, но у вас есть Instagram-клон, и вы хотите получить 2 JSONs: 1. getUserDetails() 2. getUserPhotos ()

вы хотите загрузить эти два JSONs параллельно, и когда оба загружаются, страница должна отображаться. Обратный вызов вариант станет немного сложнее: вам нужно создать 2 обратных вызова, сохранить данные в активности, и если все данные загружены, отобразите страницу:

обратного вызова:

api.getUserDetails(userId, new Callback<UserDetails>() {
    @Override
    public void onSuccess(UserDetails details, Response response) {
        this.details = details;
        if(this.photos != null) {
            displayPage();
        }
    }
});

api.getUserPhotos(userId, new Callback<List<Photo>>() {
    @Override
    public void onSuccess(List<Photo> photos, Response response) {
        this.photos = photos;
        if(this.details != null) {
            displayPage();
        }
    }
});

RxJava:

private class Combined {
    UserDetails details;
    List<Photo> photos;
}


Observable.zip(api.getUserDetails(userId), api.getUserPhotos(userId), new Func2<UserDetails, List<Photo>, Combined>() {
            @Override
            public Combined call(UserDetails details, List<Photo> photos) {
                Combined r = new Combined();
                r.details = details;
                r.photos = photos;
                return r;
            }
        }).subscribe(new Action1<Combined>() {
            @Override
            public void call(Combined combined) {
            }
        });

мы куда-то идем! Код RxJava теперь такой же большой, как опция обратного вызова. Код RxJava является более надежным; Подумайте, что произойдет, если нам понадобится третий JSON для загрузки (например, последние видео)? В RxJava потребуется только небольшая корректировка, в то время как вариант обратного вызова должен быть скорректирован в нескольких местах (на каждом обратном вызове нам нужно проверить, все ли данные получены).

другой пример; мы хотим создать поле автозаполнения, которое загружает данные с помощью Retrofit. Мы не хотим делать веб-вызов каждый раз, когда EditText имеет TextChangedEvent. При быстром вводе только последний элемент должен вызвать вызов. На RxJava мы можем использовать оператор debounce:

inputObservable.debounce(1, TimeUnit.SECONDS).subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                // use Retrofit to create autocompletedata
            }
        });

Я не буду создайте вариант обратного вызова, но вы поймете, что это гораздо больше работы.

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

наблюдаемый материал уже сделан в модификации, поэтому код может быть таким:

api.getUserPhoto(photoId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Photo>() {
         @Override
            public void call(Photo photo) {
                //save photo?
            }
     });

в случае getUserPhoto() преимущества для RxJava не велики. Но давайте возьмем другой пример, когда вы получите все фотографии для пользователя, но только когда изображение PNG, и у вас нет доступа к JSON, чтобы сделать фильтрацию на стороне сервера.

api.getUserPhotos(userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<List<Photo>, Observable<Photo>>() {
    @Override
    public Observable<Photo> call(List<Photo> photos) {
         return Observable.from(photos);
    }
})
.filter(new Func1<Photo, Boolean>() {
    @Override
    public Boolean call(Photo photo) {
         return photo.isPNG();
    }
})
.subscribe(
    new Action1<Photo>() {
    @Override
        public void call(Photo photo) {
            // on main thread; callback for each photo, add them to a list or something.
            list.add(photo)
        }
    }, 
    new Action1<Throwable>() {
    @Override
        public void call(Throwable throwable) {
            // on main thread; something went wrong
            System.out.println("Error! " + throwable);
        }
    }, 
    new Action0() {
        @Override
        public void call() {
            // on main thread; all photo's loaded, time to show the list or something.
        }
    });

теперь в JSON возвращает список фотографий. Мы с помощью flatMap их отдельных элементов. Таким образом, мы сможем использовать метод фильтра, чтобы игнорировать фотографии, которые не являются PNG. После этого мы подпишемся и получим обратный вызов для каждой отдельной фотографии, errorHandler и обратного вызова, когда все строки были завершены.

TLDR Точка здесь; обратный вызов только возвращает вам обратный вызов для успеха и неудачи; RxJava Observable позволяет вам делать карту, уменьшать, фильтровать и многое другое.

С rxjava вы можете делать больше вещей с меньшим кодом.

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

С rxjava очень просто.

public class PhotoModel{
  BehaviorSubject<Observable<Photo>> subject = BehaviorSubject.create(...);

  public void setUserId(String id){
   subject.onNext(Api.getUserPhoto(photoId));
  }

  public Observable<Photo> subscribeToPhoto(){
    return Observable.switchOnNext(subject);
  }
}

Если вы хотите реализовать мгновенный поиск, вам нужно только прослушать TextChangeListener и позвонить к photoModel.setUserId(EditText.getText());

в методе onCreate фрагмента или активности вы подписываетесь на Observable, который возвращает фотомодель.subscribeToPhoto (), он возвращает наблюдаемый, который всегда испускает элементы, испускаемые последним наблюдаемым(запросом).

AndroidObservable.bindFragment(this, photoModel.subscribeToPhoto())
                 .subscribe(new Action1<Photo>(Photo photo){
      //Here you always receive the response of the latest query to the server.
                  });

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

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

похоже, вы изобретаете колесо, то, что вы делаете, уже реализовано в модернизации.

, вы могли бы взглянуть на ретрофит это RestAdapterTest.java, где они определить интерфейс С Observable как возвращаемый тип, а затем использовать.

по образцам и выводам в других ответах, я думаю, что нет большой разницы для простых одно-или двухэтапных задач. Однако обратный вызов прост и понятен. RxJava является более сложным и слишком большим для простой задачи. Существует третье решение: AbacusUtil. Позвольте мне реализовать выше примеры использования со всеми тремя решениями: Callback, RxJava, CompletableFuture (AbacusUtil) с Retrolambda:

Fetch фото из сети и сохранение / отображение на устройстве:

// By Callback
api.getUserPhoto(userId, new Callback<Photo>() {
    @Override
    public void onResponse(Call<Photo> call, Response<Photo> response) {
        save(response.body()); // or update view on UI thread.
    }

    @Override
    public void onFailure(Call<Photo> call, Throwable t) {
        // show error message on UI or do something else.
    }
});

// By RxJava
api.getUserPhoto2(userId) //
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(photo -> {
            save(photo); // or update view on UI thread.
        }, error -> {
            // show error message on UI or do something else.
        });

// By Thread pool executor and CompletableFuture.
TPExecutor.execute(() -> api.getUserPhoto(userId))
        .thenRunOnUI((photo, error) -> {
            if (error != null) {
                // show error message on UI or do something else.
            } else {
                save(photo); // or update view on UI thread.
            }
        });

загрузите данные пользователя и фотографию параллельно

// By Callback
// ignored because it's little complicated

// By RxJava
Observable.zip(api.getUserDetails2(userId), api.getUserPhoto2(userId), (details, photo) -> Pair.of(details, photo))
        .subscribe(p -> {
            // Do your task.
        });

// By Thread pool executor and CompletableFuture.
TPExecutor.execute(() -> api.getUserDetails(userId))
          .runOnUIAfterBoth(TPExecutor.execute(() -> api.getUserPhoto(userId)), p -> {
    // Do your task
});

мы обычно идем со следующей логикой:

  1. Если это простой вызов с одним ответом, то обратный вызов или будущее лучше.
  2. если это вызов с несколькими ответами (поток), или когда есть сложное взаимодействие между различными вызовами (см. @Niels'ответ), то наблюдаемые лучше.