使用Retrofit+RxJava加载API数据到RecyclerView时遇NetworkOnMainThreadException
解决Retrofit+RxJava出现NetworkOnMainThreadException的问题
兄弟,我一眼就看到你代码里的坑了——你重复订阅了同一个Observable,而且第二次订阅的是完全没经过线程切换处理的原始对象,这直接导致网络请求跑在了主线程上,触发了那个讨厌的异常。
先看你代码里的问题点:
Observable<MoviesResponse> call = apiService.getTopRatedMovies(API_KEY); // 这里做了线程切换,但只是调用subscribe()却没处理结果,链式调用的返回值被直接丢弃了 call.subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(); // 重点:你又订阅了原始的call!这个订阅完全没应用上面的线程配置,直接在主线程发起网络请求 call.subscribe(new Observer<MoviesResponse>() { ... });
正确的写法应该是这样
把线程切换和订阅逻辑合并成一个链式调用,确保你的Observable是经过线程配置后再被订阅的:
ApiInterface apiService = ApiService.getClient().create(ApiInterface.class); Observable<MoviesResponse> movieObservable = apiService.getTopRatedMovies(API_KEY); // 链式调用到底,一次性完成线程配置+订阅 movieObservable.subscribeOn(Schedulers.io()) // 推荐用io线程池,比newThread()更高效,能复用线程 .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<MoviesResponse>() { @Override public void onSubscribe(Disposable d) { // 这里可以加一些订阅开始的逻辑,比如显示加载Toast或者进度条 Toast.makeText(getApplicationContext(), "正在请求数据...", Toast.LENGTH_SHORT).show(); } @Override public void onNext(MoviesResponse moviesResponse) { // 拿到数据后直接更新RecyclerView if (moviesResponse != null && moviesResponse.getResults() != null) { // 假设你有一个提前初始化好的适配器 yourMovieAdapter.setMovieList(moviesResponse.getResults()); yourMovieAdapter.notifyDataSetChanged(); } } @Override public void onError(Throwable e) { // 处理请求失败的情况 Toast.makeText(getApplicationContext(), "请求出错:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } @Override public void onComplete() { // 请求完成的收尾逻辑,比如隐藏加载框 Toast.makeText(getApplicationContext(), "数据加载完成", Toast.LENGTH_SHORT).show(); } });
额外的几个小建议
- 别用Schedulers.newThread():换成
Schedulers.io(),它是专门为IO操作设计的线程池,会自动复用线程,比每次新建线程性能更好。 - 避免重复订阅Observable:RxJava的冷Observable每次订阅都会重新执行一次请求,不仅浪费资源,还会像你这样出现线程问题。
- 如果你的项目支持Java 8+,可以用Lambda简化代码,看起来更清爽:
movieObservable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( // onNext回调 moviesResponse -> { yourMovieAdapter.setMovieList(moviesResponse.getResults()); yourMovieAdapter.notifyDataSetChanged(); }, // onError回调 throwable -> Toast.makeText(getApplicationContext(), "请求失败:" + throwable.getMessage(), Toast.LENGTH_SHORT).show(), // onComplete回调 () -> Toast.makeText(getApplicationContext(), "加载完成", Toast.LENGTH_SHORT).show() );
内容的提问来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

