268

我正在为我的应用程序建立网络。所以我决定试试 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中所示,使用改造比上面的示例简单得多。但本质上问题是一样的——什么时候应该使用一种方式或另一种方式?

4

9 回答 9

358

对于简单的网络内容,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 变体仍然不小,尽管使用 Lambdas 会更接近 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
            }
        });

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

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

于 2015-04-28T11:34:19.233 回答
68

The Observable stuff is already done in Retrofit, so the code could be this:

api.getUserPhoto(photoId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Photo>() {
         @Override
            public void call(Photo photo) {
                //save photo?
            }
     });
于 2014-03-04T11:35:51.690 回答
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 返回一个照片列表。我们会将它们平面映射到单个项目。通过这样做,我们将能够使用过滤器方法来忽略不是 PNG 的照片。之后,我们将订阅并获取每张照片的回调、errorHandler 以及所有行完成后的回调。

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

于 2014-03-07T09:34:26.297 回答
28

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

在 Fragment 或活动的 onCreate 方法中,您订阅了返回 photoModel.subscribeToPhoto() 的 Observable,它返回一个始终发出最新 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 是 Singleton,则无需担心方向更改,因为 BehaviorSubject 会发出最后的服务器响应,无论您何时订阅。

通过这行代码,我们实现了即时搜索并处理方向变化。你认为你可以用更少的代码来实现这个吗?我对此表示怀疑。

于 2014-06-12T13:33:15.423 回答
2

我们通常遵循以下逻辑:

  1. 如果它是一个简单的单响应调用,那么 Callback 或 Future 会更好。
  2. 如果是具有多个响应(流)的调用,或者不同调用之间存在复杂的交互(参见@Niels 的回答),那么 Observables 会更好。
于 2018-02-28T19:29:35.267 回答
1

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

于 2019-05-28T10:14:56.000 回答
1

通过其他答案中的样本和结论,我认为简单的一两步任务没有太大区别。但是,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
});
于 2017-06-03T18:31:59.550 回答
0

看起来您正在重新发明轮子,您正在做的事情已经在改造中实施。

例如,您可以查看改造的RestAdapterTest.java,他们在其中定义了一个以 Observable 作为返回类型的接口,然后使用它

于 2014-02-26T22:23:29.680 回答
0

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

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

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

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


TL'DR

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

于 2020-06-08T10:36:35.237 回答