RxJava Observableを使用する必要がある場合と、Androidで単純なコールバックを使用する場合


260

アプリのネットワーキングに取り組んでいます。そこで、私はSquareのレトロフィットを試すことにしました。彼らはシンプルをサポートしていることがわかります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 14

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バリアントは、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を使用すると、コールバックバリアントに近づきます。さらに、JSONフィードにアクセスできる場合、PNGのみを表示しているときにすべての写真を取得するのはおかしいでしょう。フィードを調整してPNGのみを表示します。

最初の結論

適切な形式になるように準備した単純なJSONをロードする場合、コードベースは小さくなりません。

では、もう少し面白くしてみましょう。userPhotoを取得するだけでなく、Instagramクローンがあり、2つのJSONを取得するとします。1。getUserDetails()2. getUserPhotos()

これらの2つの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コードはより堅牢です。(最新のビデオのように)3番目のJSONをロードする必要がある場合はどうなるでしょうか?RxJavaはほんのわずかな調整で十分ですが、Callbackバリアントは複数の場所で調整する必要があります(コールバックごとに、すべてのデータが取得されたかどうかを確認する必要があります)。

もう一つの例; Retrofitを使用してデータをロードするオートコンプリートフィールドを作成します。EditTextにTextChangedEventがあるたびにWeb呼び出しを実行する必要はありません。高速で入力する場合、最後の要素のみが呼び出しをトリガーする必要があります。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を使用すると、コードの保守性が大幅に向上します。


6
あなたはどのクラスを使用しましたinputObservableか?私はデバウンスに関して多くの問題を抱えており、このソリューションについてもう少し知りたいと思います。
Migore

@Migore RxBindingのプロジェクトは次のようなクラスを提供し、「プラットフォーム結合」モジュールがあるRxViewRxTextViewのために使用することができるなどinputObservable
ブリザード

@Nielsはエラー処理を追加する方法を説明できますか?あなたがあなたがflatMap、Filter、そしてサブスクライブした場合、onCompleted、onError、onNextでサブスクライバーを作成できますか?大きな説明ありがとうございます。
Nicolas Jafelle 2015

4
これは素晴らしい例です!
Ye Lin Aung

3
史上最高の説明!
Denis Nek 2016年

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 2014年

14
@MartynasJurkusオブザーバブルは、複数の関数をチェーンしたい場合に役立ちます。たとえば、発信者は、100x100のサイズにトリミングされた写真を取得したいと考えています。APIは任意のサイズの写真を返す可能性があるため、getUserPhotoオブザーバブルを別のResizedPhotoObservableにマップできます-呼び出し元は、サイズ変更が行われたときにのみ通知を受けます。使用する必要がない場合は、強制しないでください。
ataulm 2014年

2
1つのマイナーなフィードバック。-改造は既にこのの世話をするよう.subscribeOn(Schedulers.ioを())を呼び出す必要はありませんgithub.com/square/retrofit/issues/430を(ジェイクの回答を参照してください)
hiBrianLee

4
@MartynasJurkusは、ataulmによる発言に加えて、Callbackからの退会が可能Observeableなため、一般的なライフサイクルの問題を回避できます。
ドミトリーザイツェフ2015年

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日目は特典なし
レイモンド

最初と同じ回答
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());

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クラスをシングルトンにすること自体が制限だと思いませんか?これはユーザープロフィールではなく、複数のユーザー向けの写真のセットであると想像してください。最後に要求されたユーザーの写真だけを取得しませんか?加えて、すべての向きの変更に関するデータを要求することは、私には少し耳障りに聞こえますが、どう思いますか?
ファリッド

2

通常、次のロジックを使用します。

  1. それが単純な1つの応答の呼び出しである場合、CallbackまたはFutureの方が適しています。
  2. 複数の応答(ストリーム)を伴う呼び出しである場合、または異なる呼び出しの間に複雑な相互作用がある場合(@Nielsの回答を参照)、Observablesの方が適しています。

1

他の回答のサンプルと結論により、単純な1ステップまたは2ステップのタスクに大きな違いはないと思います。ただし、コールバックはシンプルで簡単です。RxJavaはより複雑で、単純なタスクには大きすぎます。AbacusUtilによる3番目のソリューションがあります。私はすべての3つのソリューションで上記のユースケースを実装してみましょう:とコールバック、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

私は個人的に、データに対してフィルター、マップなどを行う必要がある場合、または以前の呼び出し応答に基づいて別のAPI呼び出しを行う必要がある場合に、Rxを使用してAPI応答を取得することを好みます


0

あなたはホイールを再発明しているように見えます、あなたがしていることはすでにレトロフィットに実装されています。

たとえば、改造の時に見ることができるRestAdapterTest.java彼らは、インターフェイスを定義し、戻り値の型として観察可能で、その後、それを使用します


答えてくれてありがとう。あなたの言っていることがわかりますが、これをどのように実装するかはまだわかりません。賞金を授与できるように、既存の改造の実装を使用した簡単な例を提供できますか?
Yehosef 2014

@Yehosef誰かがコメントを削除したか、OPのふりをしていましたか?))
Farid

他の人の質問に対して賞金を授与できます。私がこれを尋ねたとき、私は質問への答えが本当に必要でした-それで私は賞金を提供しました。バウンティアワードにカーソルを合わせると、それが私によって与えられたことがわかります。
Yehosef

0

楽しいアプリ、ペットプロジェクト、POC、または最初のプロトタイプを作成するときは、コールバック、非同期タスク、ルーパー、スレッドなどのシンプルなコアandroid / javaクラスを使用します。これらは使いやすく、何も必要ありません。サードパーティのlib統合。短時間で変更不可能なプロジェクトを構築するためだけの大きなライブラリ統合は、同様のことがバットのすぐ近くでできる場合、非論理的です。

しかし、これらは非常に鋭いナイフのようです。本番環境でこれらを使用することは常にクールですが、結果にもつながります。CleanコーディングとSOLIDの原則に精通していない場合、安全な並行コードを作成することは困難です。将来の変更を容易にし、チームの生産性を向上させるには、適切なアーキテクチャを維持する必要があります。

一方、RxJavaやコルーチンなどの同時実行ライブラリは10億回以上試され、テストされて、本番環境で使用可能な並行コードの記述に役立っています。繰り返しになりますが、これらのライブラリを使用して、同時実行コードを記述したり、すべての同時実行ロジックを抽象化したりするわけではありません。あなたはまだです。しかし、今ではそれが表示され、コードベース全体、さらに重要なことには開発チーム全体で並行コードを書き込むための明確なパターンを実施しています。

これは、生の同時実行性を処理する従来のコアクラスではなく、同時実行フレームワークを使用する主な利点です。ただし、誤解しないでください。私は外部ライブラリの依存関係を制限することに強い信念を持っていますが、この特定のケースでは、コードベース用のカスタムフレームワークを構築する必要があります。したがって、同時実行フレームワークは、コールバックなどのプレーンクラスを使用するよりも優先されます。


TL'DR

コードベース全体で同時にコーディングするためにRxJavaを使用している場合は、RxJava Observable / Flowableを使用してください。その場合、賢明な質問は、Observables to Flowablesを使用するかどうかです。そうでない場合は、先に進んで呼び出し可能オブジェクトを使用してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.