언제 RxJava Observable을 사용해야하고 언제 Android에서 간단한 콜백을 사용해야합니까?


260

내 앱의 네트워킹을 위해 노력하고 있습니다. 그래서 Square 's Retrofit 을 사용해보기로 결정했습니다 . 나는 그들이 간단한 지원을 참조하십시오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간단한 콜백보다 언제 / 선호해야 합니까?

최신 정보

@Niels가 그의 대답이나 Jake Wharton의 예제 프로젝트 U2020 에서 보여 주듯이 개장을 사용하는 것은 위의 예보다 훨씬 간단 합니다. 그러나 본질적으로 문제는 동일하게 유지됩니다. 한 가지 방법을 사용해야합니까?


U2020에서 말하는 파일에 대한 링크를 업데이트 할 수 있습니까
letroll

그것은 여전히 ​​작동합니다 ...
Martynas Jurkus

4
RxJava를 읽을 때와 정확히 같은 생각을했습니다. 간단한 요청에 대한 개장 예를 읽었습니다 (매우 익숙하기 때문에) 10 ~ 15 줄의 코드였으며 첫 번째 반응은 나를 농담해야한다는 것입니다 = /. 또한 이벤트 버스가 관찰 가능 항목에서 사용자를 분리하고 실수하지 않는 한 rxjava가 커플 링을 다시 도입하므로 이벤트 버스를 대체하는 방법도 알 수 없습니다.
Lo-Tan

답변:


348

간단한 네트워킹 작업의 경우 콜백에 비해 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 변형은 더 작지 않지만 Lambdas에서는 콜백 변형에 더 가까워집니다. 또한 JSON 피드에 액세스 할 수 있으면 PNG 만 표시 할 때 모든 사진을 검색하는 것이 이상합니다. 피드 만 조정하면 PNG 만 표시됩니다.

첫 결론

올바른 형식으로 준비한 간단한 JSON을로드 할 때 코드베이스가 더 작아지지 않습니다.

이제 좀 더 재미있게 만들어 봅시다. userPhoto를 검색하고 싶지만 Instagram 복제본이 있고 두 개의 JSON을 검색하려고한다고 가정 해 보겠습니다. 1. getUserDetails () 2. getUserPhotos ()

이 두 JSON을 병렬로로드하려고하며 둘 다로드되면 페이지가 표시되어야합니다. 콜백 변형은 조금 더 어려워집니다. 콜백을 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는 데이터가 스트림으로 전송 될 때 매우 좋습니다. Retrofit Observable은 스트림의 모든 요소를 ​​동시에 푸시합니다. 콜백에 비해 그 자체로는 특히 유용하지 않습니다. 그러나 스트림에 여러 요소가 푸시되고 다른 시간이 걸리고 타이밍 관련 작업을 수행해야하는 경우 RxJava는 코드를 훨씬 더 유지 관리하기 쉽게 만듭니다.


6
어떤 수업에 사용 했 inputObservable습니까? 수신 거부에 많은 문제가 있으며이 솔루션에 대해 조금 더 배우고 싶습니다.
Migore

@Migore RxBinding의 프로젝트는 같은 클래스를 제공하는 "플랫폼 바인딩"모듈이 RxView, RxTextView에 사용할 수있는 등 inputObservable.
블리자드

@Niels 오류 처리를 추가하는 방법을 설명 할 수 있습니까? flatMap, Filter 및 구독 한 경우 onCompleted, onError 및 onNext를 사용하여 구독자를 만들 수 있습니까? 큰 설명 감사합니다.
Nicolas Jafelle

4
이것은 훌륭한 예입니다!
Ye Lin Aung

3
최고의 설명!
Denis Nek

66

Observable은 이미 Retrofit에서 수행되었으므로 코드는 다음과 같습니다.

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

5
예, 그러나 문제는 언제 서로 선호하는 것입니까?
크리스토퍼 페리

7
그렇다면 이것이 단순한 콜백보다 나은 점은 무엇입니까?
Martynas Jurkus

14
@MartynasJurkus observables는 여러 함수를 연결하려는 경우 유용 할 수 있습니다. 예를 들어, 발신자는 100x100 크기로 자른 사진을 가져 오려고합니다. API는 모든 크기의 사진을 반환 할 수 있으므로 getUserPhoto 옵저버 블을 다른 ResizedPhotoObservable에 매핑 할 수 있습니다. 크기 조정이 완료된 경우에만 호출자에게 알림이 전송됩니다. 그것을 사용할 필요가 없다면 강요하지 마십시오.
ataulm

2
하나의 사소한 피드백. RetroFit이 이미이 문제를 처리하므로 .subscribeOn (Schedulers.io ())을 호출 할 필요가 없습니다 ( github.com/square/retrofit/issues/430 (제이크의 답변 참조))
hiBrianLee

4
@MartynasJurkus는 ataulm에서 언급 한 내용에 추가로 Callback가입을 취소 할 수있는 것과 달리 Observeable일반적인 수명주기 문제를 피합니다.
Dmitry Zaytsev

35

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을 사용하면 매핑, 축소, 필터링 및 기타 여러 작업을 수행 할 수 있습니다.


먼저 subscribeOn 및 observeOn은 개장시 필요하지 않습니다. 네트워크 호출을 비동기 적으로 수행하고 호출자와 동일한 스레드에 알립니다. 람다 식을 얻기 위해 RetroLambda를 추가하면 훨씬 멋집니다.

하지만 .subscribe ()에서 필터 이미지를 PNG로 사용할 수 있습니다. 왜 필터를 사용해야합니까? 플랫 맵이 필요 없습니다
SERG

3
@Niels의 답변 3 개 첫 번째 질문에 대답합니다. 2 번째 혜택
Raymond Chenon

첫 번째 답변과 동일한 답변
Mayank Sharma

27

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 메소드에서 photoModel.subscribeToPhoto ()를 반환하는 Observable에 가입하면 항상 최신 Observable (요청)에서 방출 된 항목을 방출하는 Observable을 반환합니다.

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는 구독시기에 관계없이 마지막 서버 응답을 생성하므로 방향 변경에 대해 걱정할 필요가 없습니다.

이 코드 줄을 사용하여 즉시 검색을 수행하고 방향 변경을 처리했습니다. 적은 코드로 콜백으로 이것을 구현할 수 있다고 생각하십니까? 나는 그것을 의심한다.


PhotoModel 클래스를 Singleton으로 만드는 것 자체가 제한이라고 생각하지 않습니까? 사용자 프로필이 아니라 여러 사용자를위한 사진 세트를 상상해보십시오. 마지막으로 요청한 사용자 사진 만 가져 오지 않습니까? 또한 모든 방향 변경에 대한 데이터를 요청하면 소리가 조금 들립니다. 어떻게 생각하십니까?
Farid

2

우리는 일반적으로 다음과 같은 논리를 따릅니다.

  1. 간단한 단일 응답 전화 인 경우 콜백 또는 미래가 더 좋습니다.
  2. 여러 응답 (스트림)을 가진 호출이거나 다른 호출간에 복잡한 상호 작용이있는 경우 (@Niels ' answer 참조 ) Observables가 더 좋습니다.

1

다른 답변의 샘플과 결론에 따르면 간단한 한두 단계 작업에는 큰 차이가 없다고 생각합니다. 그러나 콜백은 간단하고 간단합니다. RxJava는 더 복잡하고 간단한 작업을하기에는 너무 큽니다. AbacusUtil 의 세 번째 해결책이 있습니다 . 콜백, RxJava, CompletableFuture (AbacusUtil)와 : 나에게 세 가지 솔루션 위의 사용 사례 구현하자 Retrolambda를 :

네트워크에서 사진을 가져오고 장치에 저장 / 표시 :

// 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
});

4
OP는 새로운 도서관에 대한 제안을 요구하지 않았다
forresthopkinsa

1

필자는 개인적으로 Rx를 사용하여 데이터에서 필터링, 맵 또는 이와 유사한 작업을 수행해야하거나 이전 호출 응답을 기반으로 다른 API 호출을 수행 해야하는 경우 API 응답을 얻는 것을 선호합니다.


0

휠을 재발 명하는 것처럼 보입니다. 현재 수행중인 작업은 이미 개조하여 구현되었습니다.

예를 들어 retrofit의 RestAdapterTest.java를 살펴보면 , Observable을 반환 유형으로 인터페이스정의한 다음이를 사용할 수 있습니다.


답변 해주셔서 감사합니다. 나는 당신이 무엇을 말하는지 알지만 아직 어떻게 구현할 지 잘 모르겠습니다. 현상금을 수여 할 수 있도록 기존 개조 구현을 사용하여 간단한 예를 제공 할 수 있습니까?
Yehosef

@Yehosef 누군가가 자신의 댓글을 삭제했거나 OP 인 척하고 있습니까? ))
Farid

다른 사람의 질문에 대해 현상금을 수여 할 수 있습니다. 내가 이것을 물었을 때, 나는 그 질문에 대한 답이 정말로 필요했다. 그래서 나는 현상금을 제공했다. 현상금 상을 가리키면 본인이 상을 받았다는 것을 알 수 있습니다.
Yehosef

0

재미있는 앱, 애완 동물 프로젝트 또는 POC 또는 첫 번째 프로토 타입을 만들 때 콜백, 비동기 작업, 루퍼, 스레드 등과 같은 간단한 핵심 Android / java 클래스를 사용합니다. 사용하기 쉽고 필요하지 않습니다. 타사 lib 통합. 소규모의 변경 불가능한 프로젝트를 구축하기위한 대규모 라이브러리 통합은 비슷한 작업을 바로 수행 할 수있을 때 비논리적입니다.

그러나 이것들은 매우 날카로운 칼과 같습니다. 프로덕션 환경에서 이들을 사용하는 것은 항상 멋지지만 결과도 있습니다. Clean 코딩 및 SOLID 원칙에 정통하지 않으면 안전한 동시 코드를 작성하기가 어렵습니다. 향후 변경을 용이하게하고 팀 생산성을 향상 시키려면 적절한 아키텍처를 유지해야합니다.

반면에 RxJava, Co-routines 등과 같은 동시성 라이브러리는 프로덕션 준비가 가능한 동시 코드 작성을 돕기 위해 10 억 번 이상 시도되고 테스트됩니다. 다시 말하지만, 이러한 라이브러리를 사용하면 동시 코드를 작성하거나 모든 동시성 논리를 추상화하지 않습니다. 당신은 여전히 ​​있습니다. 그러나 이제는 코드베이스 전체와 개발 팀 전체에서 동시 코드를 작성하기위한 명확한 패턴을 보여줍니다.

이는 원시 동시성을 처리하는 일반 이전 코어 클래스 대신 동시성 프레임 워크를 사용하면 얻을 수있는 주요 이점입니다. 그러나 나를 오해하지 마십시오. 나는 외부 라이브러리의 종속성을 제한하는 것을 믿지만이 특별한 경우에는 시간이 많이 걸리는 작업이며 사전 경험 후에 만 ​​수행 할 수있는 코드베이스에 대한 사용자 정의 프레임 워크를 작성해야합니다. 따라서 동시성 프레임 워크는 콜백 등과 같은 일반 클래스를 사용하는 것보다 선호됩니다.


TL'DR

코드 기반에서 동시 코딩을 위해 이미 RxJava를 사용하고 있다면 RxJava Observable / Flowable을 사용하십시오. 현명한 질문은 Observables to Flowables를 사용해야한다는 것입니다. 그렇지 않은 경우 계속해서 호출 가능을 사용하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.