【问题标题】:Android MVP - network request that survives across configuration changeAndroid MVP - 在配置更改后仍然存在的网络请求
【发布时间】:2017-03-17 01:04:36
【问题描述】:

我遵循了网络上的一些教程和开源,并使用 RxJava、Dagger 2 和 Retrofit 构建了基于 MVP 架构的简单应用程序。一切正常,除了当我开始下载数据并立即旋转屏幕之前的请求被取消并发出新的请求时。

网络请求被取消的原因是我正在取消订阅我视图的onDestroyView 内的 Observable。那是为了防止内存泄漏!

如何保留以前的网络请求不让Subscription 泄漏?

这里是视图:

public class MoviesFragment extends Fragment implements MoviesView{

    @Inject
    MoviesPresenter moviesPresenter;
   //....

    public MoviesFragment(){

    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        setRetainInstance(true);
        ((BaseApplication) getActivity().getApplication()).createListingComponent().inject(this);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View rootView = inflater.inflate(R.layout.fragment_movies, container, false);
        ButterKnife.bind(this, rootView);
        return rootView;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState)
    {
        super.onViewCreated(view, savedInstanceState);
        moviesPresenter.setView(this);
    }

    // ....

    @Override
    public void onDestroyView()
    {
        super.onDestroyView();
        moviesPresenter.destroy();
        ButterKnife.unbind(this);
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
    }

    @Override
    public void onDestroy()
    {
        super.onDestroy();
        ((BaseApplication)getActivity().getApplication()).releaseListingComponent();
    }
    //....
}

这里是演示者:

public class MoviesPresenterImpl implements MoviesPresenter {

    private final MoviesInteractor moviesInteractor;
    private MoviesView view;
    private Subscription fetchSubscription;

    public MoviesPresenterImpl(MoviesInteractor moviesInteractor) {
        this.moviesInteractor = moviesInteractor;
    }

    @Override
    public void downloadMovies() {

        fetchSubscription = moviesInteractor.getMovieList(new MoviesInteractorImpl.GetMovieListCallback() {
            @Override
            public void onSuccess(List<MovieModel> movieModels) {
                onMovieFetchSuccess(movieModels);
            }

            @Override
            public void onError(NetworkError networkError) {
                onMovieFetchFailed(new Throwable(networkError));
            }
        });

    }

    @Override
    public void setView(MoviesView view) {
        this.view = view;
        downloadMovies();
    }

    @Override
    public void destroy() {
        view = null;
        fetchSubscription.unsubscribe();
    }

    private void onMovieFetchSuccess(List<MovieModel> movies) {
        if (isViewAttached()) {
            view.showMovies(movies);
        }
    }

    //....
}

这里是 API 和 Presenter 之间的交互器:

public class MoviesInteractorImpl implements MoviesInteractor {

    private Observable<MoviesResponseModel> call;

    public MoviesInteractorImpl(MoviesRetrofitService moviesRetrofitService) {
        call = moviesRetrofitService.getMovies("en", "popularity.desc", "MY_API_KEY");
    }

    @Override
    public Subscription getMovieList(final GetMovieListCallback callback) {

        return call
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<MoviesResponseModel>() {
                    @Override
                    public void onStart() {
                        super.onStart();
                    }

                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {
                        callback.onError(new NetworkError(e));
                    }

                    @Override
                    public void onNext(MoviesResponseModel cityListResponse) {
                        callback.onSuccess(cityListResponse.getMovieList());
                    }
                });
    }

    public interface GetMovieListCallback {
        void onSuccess(List<MovieModel> movieModels);

        void onError(NetworkError networkError);
    }

}

自定义范围,这样只要 View 还活着,Presenter 就会一直存在。

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ListingScope {
}

匕首模块:

@Module
public class ListingModule {

    @Provides
    public MoviesRetrofitService provideMoviesRetorfitService(Retrofit retrofit) {
        return retrofit.create(MoviesRetrofitService.class);
    }

    @Provides
    MoviesInteractor provideMoviesInteractor(MoviesRetrofitService moviesRetrofitService){
        return new MoviesInteractorImpl(moviesRetrofitService);
    }

    @Provides
    MoviesPresenter provideMoviesPresenter(MoviesInteractor moviesInteractor){
        return new MoviesPresenterImpl(moviesInteractor);
    }

}

匕首组件 - 子组件:

@ListingScope
@Subcomponent(modules = {ListingModule.class})
public interface ListingComponent {
    MoviesFragment inject(MoviesFragment moviesFragment);
}

【问题讨论】:

    标签: android mvp dagger-2 rx-android onconfigurationchanged


    【解决方案1】:

    您可以等待取消订阅,直到您通过 GetMovieListCallback 收到 MoviesInteractor 的响应。

    如果在onMovieFetchSuccessonMovieFetchFailed 之前调用setView,则fetchSubscription 不会取消订阅。 如果在 MoviesView 重置之前获取完成,您可以保存该电影并在 MoviesView 重置后立即更新视图。

    public class MoviesInteractorImpl implements MoviesInteractor {
        private boolean needToUnsubscribe = false;
        private List<MovieModel> lastMovies;
    
        //....
    
        @Override
        public void setView(MoviesView view) {
            this.view = view;
            needToUnsubscribe = false;
            if(lastMovies != null) {
                view.showMovies(movies);
            }
            downloadMovies();
        }
    
        @Override
        public void destroy() {
            view = null;
            needToUnsubscribe = true;
        }
    
        private void onMovieFetchSuccess(List<MovieModel> movies) {
            lastMovies = movies;
            if (isViewAttached()) {
                view.showMovies(movies);
            }
            if(needToUnsubscribe) {
                fetchSubscription.unsubscribe();
            }
        }
    
        private void onMovieFetchFailed(Throwable throwable) {
             //....
            lastMovies = null;
            if(needToUnsubscribe) {
                fetchSubscription.unsubscribe();
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      您可以通过清单上的一个简单属性来防止破坏活动(以及它的片段):

      android:configChanges="orientation|screenSize"
      

      使用此选项,当您旋转屏幕时,Activity 不会执行任何生命周期方法(onResume、onPause 等)

      关于它有一个具体的问题: Android, how to not destroy the activity when I rotate the device?

      及官方文档:https://developer.android.com/guide/topics/resources/runtime-changes.html

      【讨论】:

      • 来自文档“自己处理配置更改会使使用替代资源变得更加困难,因为系统不会自动为您应用它们。当您必须时,应将此技术视为最后的手段避免由于配置更改而重新启动,并且不建议用于大多数应用程序。”
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-09
      • 2023-01-31
      • 2018-11-15
      • 2020-01-12
      • 2012-03-01
      • 1970-01-01
      • 2020-07-01
      相关资源
      最近更新 更多