Я пытаюсь вызвать API и добавить данные в свои LiveData, но после подписки я получаю MainThreadException в OnError. Пробовал разные планировщики, но безуспешно. Добавлена ​​трассировка стека из onError. частный ...

1
Piotr Zaremba 14 Дек 2020 в 22:51

1 ответ

Лучший ответ

Проблема

Ошибка onError (NetworkOnMainThreadException) передается подписчику при вызове distanceRepository.distanceResponseAPI(location.getLatitude() + "," + location.getLongitude(), getDestinations(), "my_google_api_key").

Почему выбрасывается исключение?

Выполнение сетевых запросов в UI-Eventloop запрещено, чтобы не блокировать UI.

Почему subscribeOn не помогает?

subscribeOn вызывает метод subscribeActual оператора восходящего потока в заданном планировщике. В вашем случае оператор подписки на flatMapSingle вызывается из рабочего потока в планировщике ввода-вывода. flatMapSingle вызывает myLocationService.getLocation() в том же потоке.

Как вы можете видеть в stacktrace, onNext из source-observable, который возвращается из getLocation, испускается из потока пользовательского интерфейса.

    at io.reactivex.processors.PublishProcessor$PublishSubscription.onNext(PublishProcessor.java:361)
    at io.reactivex.processors.PublishProcessor.onNext(PublishProcessor.java:244)
    at com.example.compass.viewModels.MyLocationServiceClass$1.onLocationChanged(MyLocationServiceClass.java:35)
    at android.location.LocationManager$ListenerTransport._handleMessage(LocationManager.java:292)
    at android.location.LocationManager$ListenerTransport.-wrap0(Unknown Source:0)
    at android.location.LocationManager$ListenerTransport$1.handleMessage(LocationManager.java:237)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:164)

Значение передается через onNext в вызывающем потоке (UI-Thread), потому что обратный вызов вызывается из UI-потока. Оператор flatMapSingle также вызывается в вызывающем UI-потоке. Он подписывается на возвращаемый Single из лямбда-выражения и подписывается на него в вызывающем потоке. Поэтому поток пользовательского интерфейса вызывает сетевой запрос в потоке пользовательского интерфейса, и среда выполнения Android создает исключение. SubscribeOn не гарантирует, что onNext находится ниже по течению на данном Scheduler. Это только гарантирует, что подписка происходит в этой ветке. В вашем случае наблюдаемый источник получит уведомление от среды выполнения Android в UI-Thread.

Решение?

Используйте observeOn сразу после myLocationService.getLocation(), чтобы переключить поток с одного оператора на другой. observeOn гарантирует, что нисходящий вызов onNext происходит в данном планировщике. Следовательно, подписка на внутренний поток в flatMapSingle будет происходить на данном рабочем потоке планировщика, а не на потоке пользовательского интерфейса. Вы также можете применить subscribeOn на Single в flatMapSingle. Это гарантирует, что сетевой вызов произойдет в заданном рабочем потоке планировщика.

        .flatMapSingle(new Function<Location, Single<DistanceResponseModel>>() {
            @Override
            public Single<DistanceResponseModel> apply(@NonNull Location location) throws Exception {
                return distanceRepository.distanceResponseAPI(location.getLatitude() + "," + location.getLongitude(), getDestinations(), "my_google_api_key")
                                         .subscribeOn(Schedulers.io());
            }
        })

Дальнейшее чтение

http://tomstechnicalblog.blogspot.com/2016/02/rxjava-understanding-observeon-and.html?m=1

2
Hans Wurst 15 Дек 2020 в 22:17