Khi nào nên sử dụng RxJava Observable và khi Callback đơn giản trên Android?


260

Tôi đang làm việc trên mạng cho ứng dụng của mình. Vì vậy, tôi quyết định dùng thử Retrofit của Square . Tôi thấy rằng họ hỗ trợ đơn giảnCallback

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

và của RxJava Observable

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

Cả hai trông khá giống nhau từ cái nhìn đầu tiên, nhưng khi được triển khai, nó trở nên thú vị ...

Trong khi với việc thực hiện gọi lại đơn giản sẽ trông tương tự như thế này:

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

đó là khá đơn giản và đơn giản. Và với Observablenó nhanh chóng được dài dòng và khá phức tạp.

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

Và đó không phải là nó. Bạn vẫn phải làm một cái gì đó như thế này:

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

Am i thiếu cái gì ở đây? Hoặc đây là một trường hợp sai để sử dụng Observables? Khi nào nên / nên thích Observablehơn Callback đơn giản?

Cập nhật

Sử dụng trang bị thêm đơn giản hơn nhiều so với ví dụ ở trên như @Niels đã thể hiện trong câu trả lời của anh ấy hoặc trong dự án ví dụ U2020 của Jake Wharton . Nhưng về cơ bản câu hỏi vẫn giữ nguyên - khi nào nên sử dụng cách này hay cách khác?


bạn có thể cập nhật liên kết của mình đến tập tin bạn đang nói đến trong U2020 không
hãy đăng ký

Nó vẫn hoạt động ...
Martynas Jurkus 17/07/14

4
Người đàn ông tôi có cùng suy nghĩ chính xác khi tôi đọc RxJava là điều mới. Tôi đã đọc một ví dụ trang bị thêm (vì tôi cực kỳ quen thuộc với nó) về một yêu cầu đơn giản và đó là mười hoặc mười lăm dòng mã và phản ứng đầu tiên của tôi là bạn phải đùa tôi = /. Tôi cũng không thể hiểu làm thế nào điều này thay thế một chiếc xe buýt sự kiện, vì xe buýt sự kiện tách bạn ra khỏi chiếc rxjava có thể quan sát được và giới thiệu lại khớp nối, trừ khi tôi nhầm.
Lo-Tan

Câu trả lời:


348

Đối với các công cụ kết nối mạng đơn giản, các ưu điểm của RxJava so với Callback là rất hạn chế. Ví dụ đơn giản getUserPhoto:

RxJava:

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

Gọi lại:

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

Biến thể RxJava không tốt hơn nhiều so với biến thể Callback. Bây giờ, hãy bỏ qua việc xử lý lỗi. Hãy chụp một danh sách các bức ảnh:

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

Gọi lại:

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

Bây giờ, biến thể RxJava vẫn không nhỏ hơn, mặc dù với Lambdas, nó sẽ trở nên gần gũi hơn với biến thể Callback. Hơn nữa, nếu bạn có quyền truy cập vào nguồn cấp dữ liệu JSON, sẽ rất kỳ lạ khi truy xuất tất cả các ảnh khi bạn chỉ hiển thị các PNG. Chỉ cần điều chỉnh nguồn cấp dữ liệu cho nó chỉ hiển thị PNG.

Kết luận đầu tiên

Nó không làm cho cơ sở mã của bạn nhỏ hơn khi bạn đang tải một JSON đơn giản mà bạn đã chuẩn bị ở đúng định dạng.

Bây giờ, hãy làm cho mọi thứ thú vị hơn một chút. Giả sử bạn không chỉ muốn truy xuất người dùng Ảnh, mà bạn còn có bản sao Instagram và bạn muốn truy xuất 2 JSON: 1. getUserDetails () 2. getUserPhotos ()

Bạn muốn tải song song hai JSON này và khi cả hai được tải, trang sẽ được hiển thị. Biến thể gọi lại sẽ trở nên khó khăn hơn một chút: bạn phải tạo 2 cuộc gọi lại, lưu trữ dữ liệu trong hoạt động và nếu tất cả dữ liệu được tải, hãy hiển thị trang:

Gọi lại:

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

Chúng tôi đang nhận được ở đâu đó! Mã của RxJava bây giờ lớn như tùy chọn gọi lại. Mã RxJava mạnh hơn; Hãy nghĩ về những gì sẽ xảy ra nếu chúng ta cần tải JSON thứ ba (như các Video mới nhất)? RxJava sẽ chỉ cần một điều chỉnh nhỏ, trong khi biến thể Callback cần được điều chỉnh ở nhiều nơi (trên mỗi cuộc gọi lại, chúng tôi cần kiểm tra xem tất cả dữ liệu có được truy xuất không).

Một vi dụ khac; chúng tôi muốn tạo một trường tự động hoàn thành, tải dữ liệu bằng Retrofit. Chúng tôi không muốn làm một webcall mỗi khi EditText có TextChangedEvent. Khi gõ nhanh, chỉ phần tử cuối cùng sẽ kích hoạt cuộc gọi. Trên RxJava, chúng ta có thể sử dụng toán tử gỡ lỗi:

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

Tôi sẽ không tạo biến thể Callback nhưng bạn sẽ hiểu đây là công việc nhiều hơn nữa.

Kết luận: RxJava đặc biệt tốt khi dữ liệu được gửi dưới dạng luồng. Retrofit Observable đẩy tất cả các yếu tố trên luồng cùng một lúc. Bản thân điều này không đặc biệt hữu ích so với Callback. Nhưng khi có nhiều yếu tố được đẩy trên luồng và thời gian khác nhau và bạn cần thực hiện các công việc liên quan đến thời gian, RxJava làm cho mã dễ bảo trì hơn rất nhiều.


6
Bạn đã sử dụng lớp nào cho inputObservable? Tôi có rất nhiều vấn đề với việc thảo luận và muốn tìm hiểu thêm một chút về giải pháp này.
Migore

@Migore RxBinding dự án có "Nền tảng ràng buộc" module cung cấp các lớp học như RxView, RxTextViewvv có thể được sử dụng cho inputObservable.
bão tuyết

@Niels bạn có thể giải thích làm thế nào để thêm xử lý lỗi? bạn có thể tạo một thuê bao với onCompleted, onError và onNext nếu bạn FlatMap, Lọc và sau đó đăng ký? Cảm ơn bạn rất nhiều vì lời giải thích lớn của bạn.
Nicolas Jafelle

4
Đây là một ví dụ tuyệt vời!
Ye Lin Aung

3
Giải thích tốt nhất bao giờ hết!
Denis Nek

66

Các công cụ quan sát đã được thực hiện trong Retrofit, vì vậy mã có thể là:

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

5
Có, nhưng câu hỏi là khi nào thích cái này hơn cái kia?
Christopher Perry

7
Vì vậy, làm thế nào là tốt hơn so với gọi lại đơn giản?
Martynas Jurkus

14
@MartynasJurkus obsables có thể hữu ích nếu bạn muốn xâu chuỗi một số chức năng. Ví dụ: người gọi muốn có được một bức ảnh được cắt theo kích thước 100x100. Api có thể trả về bất kỳ ảnh kích thước nào, vì vậy bạn có thể ánh xạ getUserPhoto có thể quan sát sang một ResizedPhotoObservable khác - người gọi chỉ được thông báo khi hoàn tất thay đổi kích thước. Nếu bạn không cần sử dụng nó, đừng ép buộc.
ataulm

2
Một phản hồi nhỏ. Không cần phải gọi .subscribeOn (Schedulers.io ()) như Retrofit đã chăm sóc này - github.com/square/retrofit/issues/430 (xem trả lời của Jake)
hiBrianLee

4
@MartynasJurkus thêm vào những thứ được nói bởi ataulm, không giống như Callbackbạn có thể hủy đăng ký Observeable, do đó tránh các vấn đề về vòng đời phổ biến.
Dmitry Zaytsev

35

Trong trường hợp getUserPhoto (), lợi thế của RxJava không lớn. Nhưng hãy lấy một ví dụ khác khi bạn sẽ nhận được tất cả ảnh cho người dùng, nhưng chỉ khi hình ảnh là PNG và bạn không có quyền truy cập vào JSON để thực hiện lọc trên máy chủ.

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

Bây giờ JSON trả về một danh sách Ảnh. Chúng tôi sẽ FlatMap chúng đến các mục riêng lẻ. Bằng cách đó, chúng tôi sẽ có thể sử dụng phương pháp bộ lọc để bỏ qua các ảnh không phải là PNG. Sau đó, chúng tôi sẽ đăng ký và nhận được một cuộc gọi lại cho từng ảnh riêng lẻ, một lỗiHandler và một cuộc gọi lại khi tất cả các hàng đã được hoàn thành.

Điểm TLDR ở đây; cuộc gọi lại chỉ trả lại cho bạn một cuộc gọi lại thành công và thất bại; RxJava Observable cho phép bạn thực hiện bản đồ, thu nhỏ, lọc và nhiều thứ khác.


Trước hết, đăng kýOn và obsOn không cần thiết với trang bị thêm. Nó sẽ thực hiện cuộc gọi mạng không đồng bộ và thông báo trên cùng một chuỗi với người gọi. Khi bạn thêm RetroLambda để có được biểu thức lambda, nó sẽ trở nên đẹp hơn nhiều.

nhưng tôi có thể làm điều đó (hình ảnh bộ lọc là PNG) trong .subscribe () Tại sao tôi nên sử dụng bộ lọc? và không cần trong bản đồ phẳng
SERG

3
3 câu trả lời từ @Niels. Người đầu tiên trả lời câu hỏi. Không có lợi cho người thứ 2
Raymond Chenon

cùng một câu trả lời đầu tiên
Mayank Sharma

27

Với rxjava bạn có thể làm nhiều thứ hơn với ít mã hơn.

Hãy giả sử rằng bạn muốn thực hiện tìm kiếm tức thì trong ứng dụng của mình. Với các cuộc gọi lại, bạn đã lo lắng về việc hủy đăng ký yêu cầu trước đó và đăng ký yêu cầu mới, tự xử lý thay đổi định hướng ... Tôi nghĩ rằng đó là rất nhiều mã và quá dài dòng.

Với rxjava rất đơn giản.

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

nếu bạn muốn triển khai tìm kiếm tức thì, bạn chỉ cần nghe TextChangeListener và gọi tới photoModel.setUserId(EditText.getText());

Trong phương thức onCreate của Fragment hoặc hoạt động mà bạn đăng ký với Observable trả về photoModel.subscribeToPhoto (), nó trả về một Observable luôn phát ra các mục được phát ra bởi (yêu cầu) mới nhất có thể quan sát được.

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

Ngoài ra, nếu PhotoModel là Singleton, chẳng hạn, bạn không cần lo lắng về thay đổi hướng, bởi vì BehaviorSubject phát ra phản hồi của máy chủ cuối cùng, bất kể khi bạn đăng ký.

Với dòng mã này, chúng tôi đã thực hiện tìm kiếm tức thì và xử lý các thay đổi hướng. Bạn có nghĩ rằng bạn có thể thực hiện điều này với các cuộc gọi lại với ít mã hơn không? Tôi nghi ngờ điều đó.


Bạn có nghĩ rằng việc biến lớp PhotoModel thành Singleton là một hạn chế không? Hãy tưởng tượng đó không phải là hồ sơ người dùng mà là bộ ảnh cho nhiều người dùng, chúng ta sẽ chỉ nhận được ảnh người dùng được yêu cầu cuối cùng chứ? Cộng với việc yêu cầu dữ liệu về mọi thay đổi định hướng nghe có vẻ hơi khó với tôi, bạn nghĩ sao?
Farid

2

Chúng ta thường đi với logic sau:

  1. Nếu đó là một cuộc gọi một phản hồi đơn giản, thì Gọi lại hoặc Tương lai sẽ tốt hơn.
  2. Nếu đó là một cuộc gọi có nhiều phản hồi (luồng) hoặc khi có sự tương tác phức tạp giữa các cuộc gọi khác nhau (xem câu trả lời của @Niels ), thì Observables sẽ tốt hơn.

1

Bằng các mẫu và kết luận trong các câu trả lời khác, tôi nghĩ không có sự khác biệt lớn đối với các nhiệm vụ một hoặc hai bước đơn giản. Tuy nhiên, Callback rất đơn giản và dễ hiểu. RxJava phức tạp hơn và quá lớn cho nhiệm vụ đơn giản. Có giải pháp thứ ba bằng cách: AbacusUtil . Hãy để tôi thực hiện các trường hợp sử dụng ở trên với cả ba giải pháp: Gọi lại, RxJava, CompleteableFuture (AbacusUtil) với Retrolambda :

Tải ảnh từ mạng và lưu / hiển thị trên thiết bị:

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

Tải chi tiết người dùng và hình ảnh song song

// 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 đã không yêu cầu đề xuất cho một thư viện mới
forresthopkinsa

1

Cá nhân tôi thích sử dụng Rx để nhận phản hồi api trong các trường hợp tôi phải lọc, ánh xạ hoặc một cái gì đó tương tự trên dữ liệu Hoặc trong trường hợp tôi phải thực hiện các cuộc gọi api khác dựa trên phản hồi cuộc gọi trước đó


0

Có vẻ như bạn đang phát minh lại bánh xe, những gì bạn đang làm đã được triển khai trong trang bị thêm.

Ví dụ: bạn có thể xem RestAd ModuleTest.java của retrofit , nơi họ xác định giao diện với Observable là kiểu trả về, sau đó sử dụng nó .


Cảm ơn câu trả lời. Tôi thấy những gì bạn đang nói nhưng tôi không chắc chắn làm thế nào tôi sẽ thực hiện điều này. Bạn có thể cung cấp một ví dụ đơn giản bằng cách sử dụng triển khai trang bị thêm hiện tại để tôi có thể trao thưởng cho bạn tiền thưởng không?
Yehosef

@Yehosef đã có ai đó xóa bình luận của họ hoặc bạn đang giả vờ là OP? ))
Farid

Bạn có thể thưởng một tiền thưởng cho câu hỏi của người khác. Khi tôi hỏi điều này, tôi thực sự cần câu trả lời cho câu hỏi - vì vậy tôi đã đưa ra một khoản tiền thưởng. Nếu bạn di chuột vào giải thưởng tiền thưởng, bạn sẽ thấy rằng nó đã được trao bởi tôi.
Yehosef

0

Khi bạn đang tạo một ứng dụng để giải trí, dự án thú cưng hoặc POC hoặc nguyên mẫu đầu tiên, bạn sử dụng các lớp android / java lõi đơn giản như gọi lại, tác vụ không đồng bộ, vòng lặp, luồng, v.v. Chúng đơn giản để sử dụng và không yêu cầu bất kỳ Tích hợp lib bên thứ 3. Một tích hợp thư viện lớn chỉ để xây dựng một dự án không thể thay đổi trong thời gian nhỏ là phi logic khi một cái gì đó tương tự có thể được thực hiện ngay trên con dơi.

Tuy nhiên, đây giống như một con dao rất sắc. Sử dụng những thứ này trong môi trường sản xuất luôn tuyệt vời nhưng cũng sẽ có hậu quả. Thật khó để viết mã đồng thời an toàn nếu bạn không thành thạo các nguyên tắc mã hóa sạch và RẮN. Bạn sẽ phải duy trì một kiến ​​trúc phù hợp để tạo điều kiện cho những thay đổi trong tương lai và cải thiện năng suất của nhóm.

Mặt khác, các thư viện đồng thời như RxJava, Co-Routines, v.v ... đã được thử và kiểm tra hơn một tỷ lần để giúp viết mã đồng thời sẵn sàng sản xuất. Bây giờ, một lần nữa, không phải là sử dụng các thư viện này mà bạn không viết mã đồng thời hoặc trừu tượng hóa tất cả logic đồng thời đi. Bạn vẫn là. Nhưng bây giờ, nó hiển thị và thực thi một mẫu rõ ràng để viết mã đồng thời trong toàn bộ cơ sở mã và quan trọng hơn là trong toàn bộ nhóm phát triển của bạn.

Đây là một lợi ích chính của việc sử dụng khung đồng thời thay vì các lớp lõi cũ đơn giản xử lý đồng thời thô. Tuy nhiên, đừng hiểu lầm tôi. Tôi là một người tin tưởng lớn trong việc hạn chế sự phụ thuộc của thư viện bên ngoài, nhưng trong trường hợp cụ thể này, bạn sẽ phải xây dựng một khung tùy chỉnh cho cơ sở mã của mình, đây là một nhiệm vụ tốn thời gian và chỉ có thể được thực hiện sau khi có kinh nghiệm trước. Do đó, các khung đồng thời được ưu tiên hơn sử dụng các lớp đơn giản như gọi lại, v.v.


TL'DR

Nếu bạn đã sử dụng RxJava để mã hóa đồng thời trên toàn bộ cơ sở mã, chỉ cần sử dụng RxJava Observable / Flowable. Một câu hỏi khôn ngoan để hỏi sau đó là tôi có nên sử dụng Observables cho Flowables không. Nếu không thì hãy tiếp tục và sử dụng một cuộc gọi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.