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

내 앱의 네트워킹을 위해 노력하고 있습니다. 그래서 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 에서 보여 주듯이 개장을 사용하는 것은 위의 예보다 훨씬 간단 합니다. 그러나 본질적으로 문제는 동일하게 유지됩니다. 한 가지 방법을 사용해야합니까?



답변

간단한 네트워킹 작업의 경우 콜백에 비해 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는 코드를 훨씬 더 유지 관리하기 쉽게 만듭니다.


답변

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

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

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


답변

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

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

답변

다른 답변의 샘플과 결론에 따르면 간단한 한두 단계 작업에는 큰 차이가 없다고 생각합니다. 그러나 콜백은 간단하고 간단합니다. 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
});


답변

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