programing

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

bestprogram 2023. 10. 14. 10:23

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

저는 제 앱을 위해 네트워킹 작업을 하고 있습니다.그래서 Square의 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?
            }
        });

내가 뭘 빠트렸나요?아니면 이건 사용하기에 잘못된 케이스인가요?Observable ?Observable단순한 콜백으로?

갱신하다

@Niels가 자신의 답변이나 Jake Warton의 예 프로젝트 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만 표시되도록 피드를 조정하면 됩니다.

제1결론

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

이제 좀 더 재미있게 만들어 보겠습니다.userPhoto를 검색할 뿐만 아니라 Instagram-clone을 가지고 있으며 2개의 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을 사용하여 데이터를 로드하는 자동 완성 필드를 생성하려고 합니다.텍스트 편집에 텍스트가 변경된 이벤트가 있을 때마다 웹 호출을 수행하지 않습니다.빠르게 입력할 경우 마지막 요소만 통화를 트리거해야 합니다.RxJava에서는 디바운스 연산자를 사용할 수 있습니다.

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이 사진 목록을 반환합니다.개별 항목에 대해 플랫맵으로 표시합니다.그럼으로써, PNG가 아닌 사진을 필터 방식으로 무시할 수 있게 되고, 그 후 가입하고, 각 사진에 대한 콜백, 에러 핸들러, 그리고 모든 행이 완료되면 콜백을 받습니다.

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());

프래그먼트 또는 활동의 생성 방법에서 PhotoModel을 반환하는 Observable에 가입합니다.subscribeToPhoto()에 가입하면 최신 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. 간단한 1회 응답 통화라면 콜백이나 미래가 더 낫습니다.
  2. 응답이 여러 개인 통화일 경우(스트림), 또는 서로 다른 통화 간에 복잡한 상호작용이 있을 경우(@Niels의 답변 참조) 관측치가 더 좋습니다.

저는 개인적으로 데이터에 대한 필터, 맵 등을 수행해야 할 경우 또는 이전 호출 응답을 기반으로 다른 API 호출을 수행해야 할 경우 Rx를 사용하여 API 응답을 얻는 것을 선호합니다.

다른 답변의 샘플과 결론을 보면, 단순한 한 두 단계 작업은 큰 차이가 없다고 생각합니다.하지만 콜백은 단순하고 간단합니다.RxJava는 단순한 작업을 하기에는 더 복잡하고 너무 큽니다.세 번째 해결책은 주판공통입니다.콜백, RxJava, Completeable의 세 가지 솔루션을 모두 사용하여 위의 사용 사례를 구현해 보겠습니다.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
});

바퀴를 다시 만들고 있는 것 같습니다. 지금 하고 있는 일은 이미 복고풍으로 구현되어 있습니다.

예를 들어 retrofit의 RestAdapterTest.java에서 관찰 가능한 인터페이스를 반환 유형으로 정의한 다음 사용할 수 있습니다.

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

하지만, 이것들은 매우 날카로운 칼과 같습니다.생산 환경에서 이 제품을 사용하는 것은 항상 멋진 일이지만 결과를 가져올 수도 있습니다.Clean coding 및 SOLID 원리를 잘 모르면 안전한 동시 코드 작성이 어렵습니다.향후 변화를 촉진하고 팀 생산성을 향상시키기 위해 적절한 아키텍처를 유지해야 합니다.

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

이는 원시 동시성을 다루는 단순한 오래된 핵심 클래스 대신 동시성 프레임워크를 사용하는 것의 큰 이점입니다.하지만 오해는 하지 마세요.저는 외부 라이브러리의 의존성을 제한하는 것을 매우 중요하게 생각하지만, 이 특정한 경우에는 코드 기반에 대한 사용자 지정 프레임워크를 구축해야 하므로 시간이 많이 걸리는 작업이며 사전 경험이 있어야만 수행할 수 있습니다.따라서 콜백 등과 같은 일반 클래스를 사용하는 것보다 동시성 프레임워크를 선호합니다.


TL'DR

코드 베이스 전체에서 동시 코딩을 위해 RxJava를 이미 사용 중인 경우 RxJava Observable/Flowable을 사용하기만 하면 됩니다.그러면 관찰 항목을 유동 항목에 사용해야 하는 것이 현명한 질문입니다.만약 그렇지 않다면, 전화기를 사용하세요.

언급URL : https://stackoverflow.com/questions/21890338/when-should-one-use-rxjava-observable-and-when-simple-callback-on-android