Kết quả đăng ký không được sử dụng


133

Tôi đã nâng cấp lên Android Studio 3.1 hôm nay, có vẻ như đã thêm một vài kiểm tra lint. Một trong những kiểm tra lint này là đối với subscribe()các cuộc gọi RxJava2 một lần không được lưu trong một biến. Ví dụ: nhận danh sách tất cả người chơi từ cơ sở dữ liệu Phòng của tôi:

Single.just(db)
            .subscribeOn(Schedulers.io())
            .subscribe(db -> db.playerDao().getAll());

Kết quả là một khối màu vàng lớn và chú giải công cụ này:

Kết quả subscribelà không được sử dụng

Ảnh chụp màn hình của Android Studio.  Mã được tô sáng màu Vàng với một chú giải công cụ.  Văn bản công cụ: Kết quả của đăng ký không được sử dụng.

Cách thực hành tốt nhất cho các cuộc gọi Rx một lần như thế này là gì? Tôi có nên giữ Disposabledispose()hoàn thành? Hay tôi chỉ nên @SuppressLintvà tiếp tục?

Điều này dường như chỉ ảnh hưởng đến RxJava2 ( io.reactivex), RxJava ( rx) không có lint này.


Trong cả hai giải pháp của bạn, tôi thành thật nghĩ rằng @SuppressLint không phải là giải pháp tốt nhất. Có thể tôi sai nhưng tôi thực sự nghĩ rằng mã không bao giờ nên thay đổi các cảnh báo và / hoặc gợi ý về IDE
Arthur Attout 27/03/18

@ArthurAttout Đồng ý, hiện tại tôi đang giữ Disposablephạm vi thành viên và gọi dispose()khi đơn hoàn thành, nhưng có vẻ như không cần thiết phải cồng kềnh. Tôi quan tâm xem liệu có cách nào tốt hơn để làm điều này.
Michael Dodd

8
Tôi nghĩ rằng cảnh báo lint này gây khó chịu khi luồng RxJava không được đăng ký từ bên trong Activity / Fragment / ViewModel. Tôi có một Hoàn thành có thể chạy một cách an toàn mà không liên quan đến vòng đời Hoạt động, nhưng tôi vẫn cần phải xử lý nó?
EM

xem xét RxLifecycle
최봉재

Câu trả lời:


122

IDE không biết những ảnh hưởng tiềm năng nào mà đăng ký của bạn có thể có khi không được xử lý, vì vậy nó coi nó là không an toàn. Ví dụ: bạn Singlecó thể chứa một cuộc gọi mạng, điều này có thể gây rò rỉ bộ nhớ nếu bạn Activitybị bỏ rơi trong khi thực hiện.

Một cách thuận tiện để quản lý một lượng lớn Disposables là sử dụng CompositeDis Dùng một lần ; chỉ cần tạo một CompositeDisposablebiến đối tượng mới trong lớp kèm theo của bạn, sau đó thêm tất cả các khoản mục của bạn vào CompositeDis Dùng (với RxKotlin, bạn có thể chỉ cần thêm addTo(compositeDisposable)vào tất cả các khoản mục của bạn). Cuối cùng, khi bạn hoàn thành với ví dụ của mình, hãy gọi compositeDisposable.dispose().

Điều này sẽ thoát khỏi các cảnh báo xơ xác và đảm bảo bạn Disposablesđược quản lý đúng cách.

Trong trường hợp này, mã sẽ trông như sau:

CompositeDisposable compositeDisposable = new CompositeDisposable();

Disposable disposable = Single.just(db)
        .subscribeOn(Schedulers.io())
        .subscribe(db -> db.get(1)));

compositeDisposable.add(disposable); //IDE is satisfied that the Disposable is being managed. 
disposable.addTo(compositeDisposable); //Alternatively, use this RxKotlin extension function.


compositeDisposable.dispose(); //Placed wherever we'd like to dispose our Disposables (i.e. in onDestroy()).

Tôi đang gặp lỗi biên dịch error: cannot find symbol method addTo(CompositeDisposable)với "rxjava: 2.1.13". Phương pháp này đến từ đâu? (RxSwift hoặc RxKotlin tôi cho rằng)
aeracode

2
Vâng, đó là một phương pháp RxKotlin.
khẩn cấp

1
Phải làm gì trong trường hợp có thể chảy được
Săn

Điều gì sẽ xảy ra nếu chúng ta đang làm điều này trong doOnSubscribe
Killer

2
Nó sẽ không gây rò rỉ bộ nhớ. Khi cuộc gọi mạng kết thúc và onComplete được gọi, bộ sưu tập rác sẽ xử lý phần còn lại trừ khi bạn giữ một tham chiếu rõ ràng về loại dùng một lần và không loại bỏ nó.
Gabriel Vasconcelos

26

Thời điểm Hoạt động sẽ bị hủy, danh sách Các mục tiêu sẽ bị xóa và chúng tôi vẫn ổn.

io.reactivex.disposables.CompositeDisposable mDisposable;

    mDisposable = new CompositeDisposable();

    mDisposable.add(
            Single.just(db)
                    .subscribeOn(Schedulers.io())
                    .subscribe(db -> db.get(1)));

    mDisposable.dispose(); // dispose wherever is required

9

Bạn có thể đăng ký với máy chủ dùng một lần :

Single.just(db)
    .subscribeOn(Schedulers.io())
    .subscribe(new DisposableSingleObserver<Object>() {
            @Override
            public void onSuccess(Object obj) {
                // work with the resulting todos...
                dispose();
            }

            @Override
            public void onError(Throwable e) {
                // handle the error case...
                dispose();
            }});

Trong trường hợp bạn cần loại bỏ trực tiếp Singleđối tượng (ví dụ trước khi nó phát ra), bạn có thể thực hiện phương thức onSubscribe(Disposable d)để lấy và sử dụng Disposabletham chiếu.

Bạn cũng có thể nhận ra SingleObservergiao diện của riêng bạn hoặc sử dụng các lớp con khác.


5

Như đã đề xuất, bạn có thể sử dụng một số toàn cầu CompositeDisposableđể thêm kết quả của hoạt động đăng ký ở đó.

Các RxJava2Extensions thư viện chứa các phương pháp hữu ích để tự động loại bỏ được tạo ra dùng một lần từ CompositeDisposablekhi nó hoàn thành. Xem phần đăng kýAutoDispose.

Trong trường hợp của bạn, nó có thể trông như thế này

SingleConsumers.subscribeAutoDispose(
    Single.just(db)
            .subscribeOn(Schedulers.io()),
    composite,
    db -> db.playerDao().getAll())

2

Bạn có thể sử dụng Uber AutoDispose và rxjava.as

        Single.just(db)
            .subscribeOn(Schedulers.io())
            .as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this)))
            .subscribe(db -> db.playerDao().getAll());

Đảm bảo rằng bạn hiểu khi bạn hủy đăng ký dựa trên ScopeProvider.


Điều này giả định rằng một nhà cung cấp vòng đời có sẵn. Ngoài ra, phương thức "as" được đánh dấu là không ổn định, vì vậy sử dụng nó sẽ dẫn đến cảnh báo Lint.
Dabbler

1
Cảm ơn @Dabbler, đã đồng ý. Các .as phương pháp đã thực nghiệm cho đến khi RxJava 2.1.7 và 2.2 của nó ổn định.
blaffie

1

Một lần nữa và một lần nữa tôi thấy mình trở lại câu hỏi làm thế nào để loại bỏ chính xác các mục đăng ký và đặc biệt là bài đăng này. Một số blog và các cuộc nói chuyện tuyên bố rằng việc không gọi được disposenhất thiết dẫn đến rò rỉ bộ nhớ, mà tôi nghĩ là một tuyên bố quá chung chung. Theo hiểu biết của tôi, cảnh báo lint về việc không lưu trữ kết quả subscribelà không thành vấn đề trong một số trường hợp, bởi vì:

  • Không phải tất cả các đài quan sát đều chạy trong ngữ cảnh của một hoạt động Android
  • Có thể quan sát có thể được đồng bộ
  • Vứt bỏ được gọi là ngầm, miễn là hoàn thành có thể quan sát được

Vì tôi không muốn loại bỏ các cảnh báo xơ xác, gần đây tôi đã bắt đầu sử dụng mẫu sau cho các trường hợp có thể quan sát đồng bộ:

var disposable: Disposable? = null

disposable = Observable
   .just(/* Whatever */)
   .anyOperator()
   .anyOtherOperator()
   .subscribe(
      { /* onSuccess */ },
      { /* onError */ },
      {
         // onComplete
         // Make lint happy. It's already disposed because the stream completed.
         disposable?.dispose()
      }
   )

Tôi quan tâm đến bất kỳ bình luận nào về vấn đề này, bất kể đó là xác nhận về tính đúng đắn hay việc phát hiện ra lỗ hổng.


0

Có một cách khác có sẵn, đó là tránh sử dụng Disposeables theo cách thủ công (thêm và xóa đăng ký).

Bạn có thể xác định một Đài quan sát và có thể quan sát được sẽ nhận được nội dung từ Chủ đề (trong trường hợp bạn sử dụng RxJava). Và bằng cách chuyển điều đó có thể quan sát được vào LiveData của bạn , điều đó sẽ hoạt động. Kiểm tra ví dụ tiếp theo dựa trên câu hỏi ban đầu:

private val playerSubject: Subject<Player> = BehaviorSubject.create()

private fun getPlayer(idPlayer: String) {
        playerSubject.onNext(idPlayer)
}

private val playerSuccessful: Observable<DataResult<Player>> = playerSubject
                        .flatMap { playerId ->
                            playerRepository.getPlayer(playerId).toObservable()
                        }
                        .share()

val playerFound: LiveData<Player>
    get() = playerSuccessful
        .filterAndMapDataSuccess()
        .toLiveData()

val playerNotFound: LiveData<Unit>
    get() = playerSuccessful.filterAndMapDataFailure()
        .map { Unit }
        .toLiveData()

// These are a couple of helpful extensions

fun <T> Observable<DataResult<T>>.filterAndMapDataSuccess(): Observable<T> =
filter { it is DataResult.Success }.map { (it as DataResult.Success).data }

fun <T> Observable<DataResult<T>>.filterAndMapDataFailure(): Observable<DataResult.Failure<T>> =
filter { it is DataResult.Failure }.map { it as DataResult.Failure<T> }

-10

Nếu bạn chắc chắn rằng dùng một lần xử lý chính xác, ví dụ như sử dụng toán tử doOnSubscribe (), bạn có thể thêm phần này vào Gradle:

android {
lintOptions {
     disable 'CheckResult'
}}

10
Điều này sẽ ngăn chặn kiểm tra lint này cho tất cả các trường hợp của một kết quả không được kiểm tra. Có rất nhiều lần bên ngoài ví dụ của OP trong đó ai đó nên xử lý kết quả trả về. Đây là sử dụng búa tạ để giết ruồi.
tir38

16
Xin đừng làm điều này! Có một lý do bạn đang nhận được những cảnh báo này. Nếu bạn biết những gì bạn đang làm (và biết rằng bạn thực sự không cần phải loại bỏ đăng ký của mình), bạn có thể ngăn chặn @SuppressLint("CheckResult")chỉ bằng phương pháp.
Victor Rendina
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.