Android'de ne zaman RxJava Observable ve ne zaman basit Geri Arama kullanılmalıdır?


260

Uygulamam için ağ üzerinde çalışıyorum. Bu yüzden Square'in Retrofit'ini denemeye karar verdim . Basit desteklediklerini görüyorumCallback

@GET("/user/{id}/photo")
void getUserPhoto(@Path("id") int id, Callback<Photo> cb);

ve RxJava Observable

@GET("/user/{id}/photo")
Observable<Photo> getUserPhoto(@Path("id") int id);

Her ikisi de ilk bakışta oldukça benzer görünüyor, ancak uygulamaya geçince ilginç oluyor ...

Basit geri arama uygulaması ile buna benzer görünürken:

api.getUserPhoto(photoId, new Callback<Photo>() {
    @Override
    public void onSuccess() {
    }
});

ki bu oldukça basit ve anlaşılır. Ve bununla Observablebirlikte hızlı bir şekilde ayrıntılı ve oldukça karmaşık hale gelir.

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

Ve bu değil. Hala böyle bir şey yapmalısın:

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?
            }
        });

Burada bir şey mi eksik? Yoksa bu Observables kullanmak yanlış bir durum mudur? ObservableBasit Geri Arama ne zaman tercih edilmeli / yapılmalı ?

Güncelleme

@Niels cevabında veya Jake Wharton'ın U2020 örnek projesinde gösterdiği gibi, güçlendirmeyi kullanmak yukarıdaki örnekten çok daha basittir . Ama esasen soru aynı kalıyor - biri ne zaman şu ya da bu şekilde kullanılmalı?


U2020'de konuştuğunuz dosyaya bağlantınızı güncelleyebilir misiniz
letroll

Hala çalışıyor ...
Martynas Jurkus

4
Adam tam da RxJava okurken aynı düşünceleri vardı yeni bir şeydi. Basit bir isteğin bir uyarlama örneğini okudum (çünkü son derece aşina olduğum için) ve on veya on beş satır kod vardı ve ilk tepkim benimle dalga geçiyor olmanızdı = /. Ayrıca, olay otobüsü sizi gözlemlenebilir ve rxjava kuplajı yeniden ürettiğinden, yanılmadıkça, bir olay otobüsünün yerini nasıl değiştirdiğini de anlayamıyorum.
Lo-Tan

Yanıtlar:


349

Basit ağ iletişimi için, RxJava'nın Geri Arama'dan avantajları çok sınırlıdır. Basit getUserPhoto örneği:

RxJava:

api.getUserPhoto(photoId)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Photo>() {
            @Override
            public void call(Photo photo) {
               // do some stuff with your photo 
            }
     });

Geri aramak:

api.getUserPhoto(photoId, new Callback<Photo>() {
    @Override
    public void onSuccess(Photo photo, Response response) {
    }
});

RxJava varyantı Geri Arama varyantından daha iyi değildir. Şimdilik, hata işlemeyi görmezden gelelim. Fotoğrafların bir listesini alalım:

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

Geri aramak:

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

Şimdi, RxJava varyantı hala daha küçük değil, ancak Lambdas ile Geri Arama varyantına daha yakın olacak. Ayrıca, JSON özet akışına erişiminiz varsa, yalnızca PNG'leri görüntülerken tüm fotoğrafları almak biraz garip olur. Özet akışını yalnızca PNG'leri görüntüleyecek şekilde ayarlayın.

İlk sonuç

Doğru biçimde olmaya hazırladığınız basit bir JSON yüklediğinizde kod tabanınızı küçültmez.

Şimdi işleri biraz daha ilginç hale getirelim. Diyelim ki sadece userPhoto almak değil, aynı zamanda bir Instagram-clone var ve 2 JSON almak istiyorsunuz: 1. getUserDetails () 2. getUserPhotos ()

Bu iki JSON'u paralel olarak yüklemek istiyorsunuz ve her ikisi de yüklendiğinde sayfanın görüntülenmesi gerekiyor. Geri arama varyantı biraz daha zorlaşacaktır: 2 geri arama oluşturmanız, verileri etkinlikte saklamanız ve tüm veriler yüklenmişse sayfayı görüntülemeniz gerekir:

Geri aramak:

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) {
            }
        });

Bir yere gidiyoruz! RxJava kodu artık geri arama seçeneği kadar büyük. RxJava kodu daha sağlamdır; Yüklenmesi için üçüncü bir JSON'a ihtiyacımız olursa ne olacağını düşünün (en yeni Videolar gibi)? Geri arama varyantının birden fazla yerde ayarlanması gerekirken, RxJava'nın sadece küçük bir ayarlaması gerekir (her geri aramada tüm verilerin geri getirilip getirilmediğini kontrol etmemiz gerekir).

Başka bir örnek; Retrofit kullanarak veri yükleyen bir otomatik tamamlama alanı oluşturmak istiyoruz. EditText'te her TextChangedEvent olduğunda web araması yapmak istemiyoruz. Hızlı yazarken, aramayı yalnızca son öğe tetiklemelidir. RxJava'da geri alma işlecini kullanabiliriz:

inputObservable.debounce(1, TimeUnit.SECONDS).subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                // use Retrofit to create autocompletedata
            }
        });

Geri Arama varyantını oluşturmayacağım, ancak bunun çok daha fazla iş olduğunu anlayacaksınız.

Sonuç: Veri akış olarak gönderildiğinde RxJava son derece iyidir. Retrofit Gözlemlenebilir, akıştaki tüm öğeleri aynı anda iter. Bu, Geri Arama ile karşılaştırıldığında kendi başına özellikle yararlı değildir. Ancak, akışa ve farklı zamanlarda itilen birden fazla öğe olduğunda ve zamanlama ile ilgili şeyler yapmanız gerektiğinde, RxJava kodu çok daha sürdürülebilir hale getirir.


6
Hangi sınıf için kullandınız inputObservable? Ayrılma ile ilgili birçok sorunum var ve bu çözüm hakkında biraz daha fazla bilgi edinmek istiyorum.
Migore

@Migore RxBinding projesi gibi sınıfları sağlar "Platformu bağlama" modülü vardır RxView, RxTextViewkullanılabilir vb inputObservable.
blizzard

@Niels nasıl hata işleme eklemek açıklayabilir misiniz? Düzleştir, Süz ve sonra abone olursanız onCompleted, onError ve onNext ile abone oluşturabilir misiniz? Büyük açıklamanız için çok teşekkür ederim.
Nicolas Jafelle

5
Bu müthiş bir örnek!
Ye Lin Aung

3
Şimdiye kadarki en iyi açıklama!
Denis Nek

66

Gözlenebilir şeyler Retrofit'te zaten yapılmıştır, bu nedenle kod şu olabilir:

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

5
Evet, ama soru ne zaman diğerini tercih edeceğidir?
Christopher Perry

7
Peki bu basit geri aramadan nasıl daha iyi?
Martynas Jurkus

14
@MartynasJurkus gözlemlenebilir, birkaç işlevi zincirlemek istiyorsanız yararlı olabilir. Örneğin, arayan 100x100 boyutlarına kırpılmış bir fotoğraf almak ister. API herhangi bir boyutta fotoğraf döndürebilir, böylece getUserPhoto'u gözlemlenebilir başka bir ResizedPhotoObservable ile eşleyebilirsiniz - arayan kişi yalnızca yeniden boyutlandırma tamamlandığında bildirim alır. Kullanmanız gerekmiyorsa zorlamayın.
ataulm

2
Bir küçük geri bildirim. RetroFit zaten bununla ilgilenirken .subscribeOn (Schedulers.io ()) 'i aramaya gerek yoktur - github.com/square/retrofit/issues/430 (bkz. Jake'in cevabı)
hiBrianLee 16:14

4
@MartynasJurkus ayrıca ataulm tarafından söylenenlere ek olarak Callback, aboneliğinizi iptal edemezsiniz Observeable, bu nedenle yaygın yaşam döngüsü sorunlarından kaçınabilirsiniz.
Dmitry Zaytsev

35

GetUserPhoto () durumunda, RxJava'nın avantajları iyi değildir. Ancak, bir kullanıcı için tüm fotoğrafları alacağınız zaman, ancak yalnızca resim PNG olduğunda ve sunucu tarafında filtreleme yapmak için JSON'a erişiminiz olmadığında başka bir örnek alalım.

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.
        }
    });

Şimdi JSON bir Fotoğraf listesi döndürüyor. Onları tek tek öğelerle düzleştireceğiz. Böylece, PNG olmayan fotoğrafları yok saymak için filtre yöntemini kullanabileceğiz. Bundan sonra, abone olacağız ve her bir fotoğraf için bir geri arama, bir errorHandler ve tüm satırlar tamamlandığında bir geri arama alacağız.

TLDR Bu noktada ; geri arama yalnızca başarı ve başarısızlık için geri arama yapar; RxJava Observable, harita yapmanıza, azaltmanıza, filtrelemenize ve daha pek çok şey yapmanıza olanak tanır.


Önce kapalı abone olun ve gözlemleyin Sonradan güçlendirme ile gerekli değildir. Şebeke çağrısını eşzamansız olarak yapar ve arayanla aynı iş parçacığına bildirir. RetroLambda'yı da eklediğinizde lambda ifadeleri almak çok daha hoş olur.

ama bunu yapabilirim (filtre görüntüsü PNG'dir) .subscribe () Neden filtre kullanmalıyım? ve fistingp gerek yok
SERG

3
3 cevap @Niels. İlki soruyu cevaplıyor. 2'inci faydası yok
Raymond Chenon

ilk cevapla aynı
Mayank Sharma

27

Rxjava ile daha az kodla daha fazla şey yapabilirsiniz.

Uygulamanızda anında arama yapmak istediğinizi varsayalım. Geri aramalarla, önceki isteği iptal edip yenisine abone olmaktan endişe duyuyorsunuz, yön değiştirmeyi kendiniz hallediyorsunuz ... Bence çok fazla kod ve çok ayrıntılı.

Rxjava ile çok basit.

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

anında arama yapmak istiyorsanız, sadece TextChangeListener'ı dinlemeniz ve photoModel.setUserId(EditText.getText());

PhotoModel.subscribeToPhoto () döndüren Observable'a abone olduğunuz onCreate Parça veya etkinlik yönteminde, her zaman en son Gözlemlenebilir (istek) tarafından gönderilen öğeleri yayan bir Observable döndürür.

AndroidObservable.bindFragment(this, photoModel.subscribeToPhoto())
                 .subscribe(new Action1<Photo>(Photo photo){
      //Here you always receive the response of the latest query to the server.
                  });

Örneğin, PhotoModel bir Singleton ise, yönlendirme değişiklikleri hakkında endişelenmenize gerek yoktur, çünkü BehaviorSubject ne zaman abone olduğunuza bakılmaksızın son sunucu yanıtını yayar.

Bu kod satırları ile anında arama ve yönlendirme değişiklikleri gerçekleştirdik. Bunu daha az kodlu geri aramalarla uygulayabileceğinizi düşünüyor musunuz? Şüpheliyim.


PhotoModel sınıfını bir Singleton yapmanın kendisi bir kısıtlama olduğunu düşünmüyor musunuz? Bunun bir kullanıcı profili değil, birden çok kullanıcı için bir fotoğraf kümesi olduğunu düşünün, yalnızca son istenen kullanıcı fotoğrafını almıyor muyuz? Ayrıca her yön değişikliği hakkında bir veri istemek bana biraz kulağa geliyor, ne düşünüyorsun?
Farid

2

Genellikle şu mantıkla gideriz:

  1. Basit bir tek cevaplı çağrı ise, Geri Arama veya Gelecek daha iyidir.
  2. Birden fazla yanıtı olan (akış) bir çağrı veya farklı çağrılar arasında karmaşık etkileşim varsa (@Niels'in cevabına bakın ), Gözlenebilirler daha iyidir.

1

Diğer cevaplardaki örnekler ve sonuçlara göre, basit bir veya iki aşamalı görevler için büyük bir fark olmadığını düşünüyorum. Ancak, Geri Arama basit ve kolaydır. RxJava daha karmaşık ve basit görevler için çok büyük. Üçüncü çözüm: AbacusUtil . Geri arama, RxJava, CompletableFuture (AbacusUtil) ile: Benim her üç çözümleri ile yukarıdaki kullanım durumları uygulamak Let Retrolambda :

Fotoğrafı ağdan getir ve cihaza kaydet / göster:

// 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.
            }
        });

Kullanıcı ayrıntılarını ve fotoğrafı paralel olarak yükle

// 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 yeni bir kütüphane için öneri istemiyordu
forresthopkinsa

1

Kişisel olarak, verilerde filtre, harita veya benzeri bir şey yapmak zorunda olduğum durumlarda veya önceki aramaların yanıtlarına göre başka bir api araması yapmak zorunda olduğum durumlarda api yanıtları almak için Rx kullanmayı tercih ederim.


0

Görünüşe göre tekerleği yeniden keşfediyorsunuz, yaptığınız şey sonradan uyarlanmış durumda.

Bir örnek olarak, retrofit'in RestAdapterTest.java dosyasına bakabilir ve burada Observable ile bir dönüş türü olarak bir arabirim tanımlayabilir ve daha sonra kullanabilirsiniz .


Cevap için teşekkürler. Ne dediğini görüyorum ama bunu nasıl uygulayacağımdan henüz emin değilim. Size ödül verebilmem için mevcut güçlendirme uygulamasını kullanarak basit bir örnek verebilir misiniz?
Yehosef

@ Birisi yorumunu sildi mi yoksa OP gibi davranıyor musunuz? ))
Farid

Başkasının sorusuna bir ödül verebilirsiniz. Bunu sorduğumda, sorunun cevabına gerçekten ihtiyacım vardı - bu yüzden bir ödül teklif ettim. Ödül ödülünün üzerine gelirseniz, ödülün bana verildiğini görürsünüz.
Yehosef

0

Eğlenmek için bir uygulama, bir evcil hayvan projesi veya bir POC veya 1. prototip oluştururken geri aramalar, async görevleri, loopers, iş parçacığı, vb.Gibi basit çekirdek android / java sınıflarını kullanırsınız. 3. taraf lib entegrasyonu. Sadece küçük bir zaman değişmeyen bir proje inşa etmek için büyük bir kütüphane entegrasyonu yarasanın hemen yanında benzer bir şey yapılabildiğinde mantıksızdır.

Ancak, bunlar çok keskin bir bıçak gibidir. Bunları bir üretim ortamında kullanmak her zaman iyidir, ancak sonuçları olacaktır. Temiz kodlama ve SOLID ilkeleri konusunda bilgili değilseniz, güvenli eşzamanlı kod yazmak zordur. Gelecekteki değişiklikleri kolaylaştırmak ve ekip verimliliğini artırmak için uygun bir mimariye sahip olmanız gerekir.

Öte yandan, RxJava, Co-rutinleri vb. Şimdi, bu kütüphaneleri kullanarak eşzamanlı kod yazmıyorsunuz veya tüm eşzamanlılık mantığını soyutlamıyorsunuz. Hala öylesin. Ancak şimdi görünür ve kod tabanı boyunca ve daha da önemlisi geliştirme ekibiniz boyunca eşzamanlı kod yazmak için net bir model uyguluyor.

Bu, ham eşzamanlılıkla ilgilenen eski temel çekirdek sınıflar yerine bir eşzamanlılık çerçevesi kullanmanın önemli bir yararıdır. Ancak, beni yanlış anlamayın. Ben dış kütüphanenin bağımlılıklarını sınırlamak için büyük bir inançlıyım, ancak bu özel durumda, kod tabanınız için zaman alıcı bir görev olan ve sadece önceden deneyim sonra yapılabilecek özel bir çerçeve oluşturmak zorunda kalacaksınız. Bu nedenle, eşzamanlılık çerçeveleri, geri aramalar, vb. Gibi düz sınıflar kullanmaya tercih edilir.


TL'DR

Kod tabanı boyunca eşzamanlı kodlama için zaten RxJava kullanıyorsanız, RxJava Observable / Flowable kullanın. O zaman sorulacak akıllıca bir soru, Gözlenebilirler'i Akışkanlara kullanmam gerektiğidir. Değilse devam edin ve bir çağrılabilir kullanın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.