什么时候应该使用 RxJava Observable,什么时候应该在 Android 上使用简单的 Callback?

Posted

技术标签:

【中文标题】什么时候应该使用 RxJava Observable,什么时候应该在 Android 上使用简单的 Callback?【英文标题】:When should one use RxJava Observable and when simple Callback on Android? 【发布时间】:2014-03-20 08:45:32 【问题描述】:

我正在为我的应用开发网络。所以我决定试试 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?
            
        );

我在这里遗漏了什么吗?或者这是使用Observables 的错误案例? 什么时候/应该更喜欢Observable 而不是简单的回调?

更新

使用改造比上面的例子简单得多,正如@Niels 在他的回答或 Jake Wharton 的示例项目 U2020 中所示。但本质上问题是一样的 - 什么时候应该使用一种方式或另一种方式?

【问题讨论】:

你能更新你在U2020中提到的文件的链接吗 它仍在工作...... 伙计,当我阅读 RxJava 是新事物时,我也有同样的想法。我读了一个简单请求的改造示例(因为我非常熟悉它),它有十或十五行代码,我的第一反应是你一定是在开玩笑 =/ 。我也无法弄清楚这是如何替换事件总线的,因为事件总线将您与可观察对象分离,而 rxjava 重新引入了耦合,除非我弄错了。 【参考方案1】:

对于简单的网络内容,RxJava 相对于 Callback 的优势非常有限。简单的 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 变体并不比 Callback 变体好多少。现在,让我们忽略错误处理。 让我们来一张照片列表:

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 变体仍然不小,尽管使用 Lambda 会更接近 Callback 变体。 此外,如果您可以访问 JSON 提要,那么当您只显示 PNG 时检索所有照片会有点奇怪。只需将提要调整为仅显示 PNG。

第一个结论

当您加载准备采用正确格式的简单 JSON 时,它不会使您的代码库变小。

现在,让我们让事情变得更有趣。假设您不仅要检索 userPhoto,而且您有一个 Instagram 克隆,并且要检索 2 个 JSON: 1.getUserDetails() 2.getUserPhotos()

您希望并行加载这两个 JSON,当两者都加载时,页面应该会显示出来。 回调变体会变得有点困难:你必须创建 2 个回调,将数据存储在 Activity 中,如果所有数据都加载完毕,则显示页面:

回调:

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 只需要一个微小的调整,而 Callback 变体需要在多个地方进行调整(在每个回调中,我们需要检查是否检索到所有数据)。

另一个例子;我们想创建一个自动完成字段,它使用 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
            
        );

我不会创建 Callback 变体,但您会明白这是更多的工作。

结论: 当数据作为流发送时,RxJava 非常好。 Retrofit Observable 同时将所有元素推送到流中。 与回调相比,这本身并不是特别有用。但是当有多个元素在不同的时间推送到流上,并且你需要做与时间相关的事情时,RxJava 使代码更易于维护。

【讨论】:

inputObservable 使用了什么类?我有很多关于去抖动的问题,想了解更多关于这个解决方案的信息。 @Migore RxBinding 项目具有“平台绑定”模块,该模块提供RxViewRxTextView 等可用于inputObservable 的类。 @Niels 你能解释一下如何添加错误处理吗?如果您使用 flatMap、Filter 然后订阅,您可以使用 onCompleted、onError 和 onNext 创建订阅者吗?非常感谢您的精彩解释。 这是一个很好的例子! 有史以来最好的解释!【参考方案2】:

Observable 的东西已经在 Retrofit 中完成了,所以代码可能是这样的:

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

【讨论】:

是的,但问题是什么时候更喜欢一个而不是另一个? 那么这比简单的回调有什么好处呢? 如果您想链接多个函数,@MartynasJurkus observables 会很有用。例如,调用者想要获得一张裁剪为 100x100 尺寸的照片。该 api 可以返回任何大小的照片,因此您可以将 getUserPhoto 可观察对象映射到另一个 ResizedPhotoObservable - 调用者仅在调整大小完成时才会收到通知。如果你不需要使用它,不要强迫它。 一个小反馈。没有必要调用 .subscribeOn(Schedulers.io()) 因为 RetroFit 已经处理好了 - github.com/square/retrofit/issues/430(见 Jake 的回复) @MartynasJurkus 补充了 ataulm 所说的内容,与 Callback 不同,您可以取消订阅 Observeable,从而避免常见的生命周期问题。【参考方案3】:

在 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 的照片。之后,我们将订阅并获取每张照片的回调、errorHandler 以及所有行完成时的回调。

TLDR 点这里;回调仅返回成功和失败的回调; RxJava Observable 允许你做 map、reduce、filter 和更多的事情。

【讨论】:

首先 subscribeOn 和 observeOn 对改造不是必需的。它将异步执行网络调用,并在与调用者相同的线程上通知。当您添加 RetroLambda 以获取 lambda 表达式时,它会变得更好。 但我可以在 .subscribe() 中做到(过滤图像为 PNG) 为什么要使用过滤器?并且在平面图中不需要 来自@Niels 的 3 个答案。第一个回答了这个问题。第二次没有好处 与第一个答案相同【参考方案4】:

使用 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.subscribeToPhoto() 的 Observable 的 Fragment 或活动的 onCreate 方法中,它返回一个始终发出最新 Observable(request) 发出的项目的 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 本身就是一种限制吗?想象一下,这不是用户个人资料,而是多个用户的照片集,我们不会只得到最后请求的用户照片吗?再加上要求每次方向变化的数据对我来说听起来有点不对劲,你怎么看?【参考方案5】:

我们通常遵循以下逻辑:

    如果是简单的单响应调用,则 Callback 或 Future 更好。 如果是具有多个响应(流)的调用,或者不同调用之间存在复杂的交互(参见@Niels'answer),那么 Observables 会更好。

【讨论】:

【参考方案6】:

通过其他答案中的示例和结论,我认为简单的一两步任务没有太大区别。但是,Callback 简单明了。 RxJava 对于简单的任务来说更复杂而且太大。第三种解决方案是:AbacusUtil。让我用所有三种解决方案实现上述用例:Callback、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
);

【讨论】:

OP 没有征求新图书馆的建议【参考方案7】:

我个人更喜欢使用 Rx 来获取 api 响应,以防我必须对数据进行过滤、映射或类似操作,或者如果我必须根据之前的调用响应进行另一个 api 调用

【讨论】:

【参考方案8】:

看起来你正在重新发明***,你所做的已经在改造中实现了。

例如,您可以查看 Retrofit 的 RestAdapterTest.java,其中 define an interface 以 Observable 作为返回类型,然后是 use it。

【讨论】:

感谢您的回答。我明白你在说什么,但我还不确定我将如何实现这一点。您能否提供一个使用现有改造实现的简单示例,以便我可以奖励您? @Yehosef 是不是有人删除了他们的评论或者你假装是 OP? )) 你可以奖励别人的问题。当我问这个问题时,我真的很需要这个问题的答案——所以我提供了赏金。如果您将鼠标悬停在赏金上,您会看到它是由我授予的。【参考方案9】:

当您创建一个有趣的应用程序、宠物项目、POC 或第一个原型时,您会使用简单的核心 android/java 类,如回调、异步任务、looper、线程等。它们易于使用和执行不需要任何第三方库集成。当类似的事情可以立即完成时,仅仅为了构建一个小型不可更改项目而进行的大型库集成是不合逻辑的。

然而,这些就像一把非常锋利的刀。在生产环境中使用它们总是很酷,但它们也会产生后果。如果您不熟悉 Clean 编码和 SOLID 原则,则很难编写安全的并发代码。您必须维护适当的架构以促进未来的变化并提高团队生产力。

另一方面,RxJava、协程等并发库已经过十亿次的尝试和测试,以帮助编写生产就绪的并发代码。再说一次,并不是说使用这些库您没有编写并发代码或抽象出所有并发逻辑。你还是。但是现在,它是可见的,并强制执行了一个清晰的模式,用于在整个代码库中编写并发代码,更重要的是在整个开发团队中。

这是使用并发框架而不是处理原始并发的普通旧核心类的主要好处。不过,不要误会我的意思。我非常相信限制外部库的依赖关系,但在这种特定情况下,您必须为您的代码库构建一个自定义框架,这是一项耗时的任务,并且只能在获得高级经验后才能完成。因此,并发框架比使用普通类(如回调等)更受欢迎。


TL'DR

如果您已经在整个代码库中使用 RxJava 进行并发编码,只需使用 RxJava Observable/Flowable。一个明智的问题是我应该将 Observables 用于 Flowables。 如果没有,请继续使用可调用对象。

【讨论】:

以上是关于什么时候应该使用 RxJava Observable,什么时候应该在 Android 上使用简单的 Callback?的主要内容,如果未能解决你的问题,请参考以下文章

是时候学习 RxJava 了

RXJAVA的使用

RxJava 系列 RxJava 1.0 简介

为啥 RxJava 的“订阅”方法会被多次调用?

RxJava入门优秀博客推荐

真的有必要用rxjava吗