【问题标题】:Avoiding memory leak while calling retrofit 2调用改造 2 时避免内存泄漏
【发布时间】:2018-08-30 01:15:13
【问题描述】:

通过关注this 的文章,我发现在 onCreate() 方法上调用 Retrofit enqueue() 可能会导致内存泄漏。

这是文章所说的,这样做:

在主线程中调用 Retrofit

public class MoviesActivity extends Activity {

    private TextView mNoOfMoviesThisWeek;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_movies_activity);
        mNoOfMoviesThisWeek = (TextView) findViewById(R.id.no_of_movies_text_view);

        MoviesRepository repository = ((MoviesApp) getApplication()).getRepository();
        repository.getMoviesThisWeek()
                .enqueue(new Callback<List<Movie>>() {

                    @Override
                    public void onResponse(Call<List<Movie>> call,
                                           Response<List<Movie>> response) {
                        int numberOfMovies = response.body().size();
                        mNoOfMoviesThisWeek.setText("No of movies this week: " + String.valueOf(numberOfMovies));
                    }

                    @Override
                    public void onFailure(Call<List<Movie>> call, Throwable t) {
                        // Oops.
                    }
                });
    }
}

现在,如果这个网络调用在一个非常慢的连接上运行,并且在调用结束之前,Activity 以某种方式旋转或销毁,那么整个 Activity 实例将被泄露。

我尝试在我的应用上做同样的事情。我在 onCreate() 方法中调用了一个大内容(240 个对象)ussign enqueue()。然后,在加载内容时,我多次旋转设备,LeakCanary 向我显示了 Activity 中的内存泄漏,如文章所述。

然后我尝试了两种方法来避免内存泄漏:

第一选择

使用静态内部类在后台线程上调用改造 execute() 方法。

在后台线程中调用 Retrofit

private static class RetrofitCall extends AsyncTask<Void, Void, List<Show>> {
        private WeakReference<TextView> numberOfShows;

        public RetrofitCall(TextView numberOfShows) {
            this.numberOfShows = new WeakReference<>(numberOfShows);
        }

        @Override
        protected List<Show> doInBackground(Void... voids) {
            List<Show> showList = new ArrayList<>();

            if (!isCancelled()) {
                Retrofit retrofit = new Retrofit.Builder()
                        .baseUrl(TvMazeService.BASE_URL)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build();

                TvMazeService service = retrofit.create(TvMazeService.class);
                try {
                    Response<List<Show>> response = service.getShows().execute();
                    if (response.isSuccessful()) {
                        showList = response.body();
                    }
                    return showList;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            return null;
        }

        @Override
        protected void onPostExecute(List<Show> shows) {
            super.onPostExecute(shows);
            TextView textView = numberOfShows.get();
            if (textView != null) {
                String number = String.valueOf(shows.size());
                textView.setText(number);
            }
        }
    }

然后我再次尝试使用 LeakCanary 获取内存泄漏,并且内存泄漏发生了。

第二个选项

使用ViewModel

正如您在文档中看到的那样,在使用 ViewModel 时,我在 ViewModel 类中调用了异步改造,当屏幕旋转(活动被破坏)时,它不需要再次加载数据,因为它保持保存状态。

这种方法也没有造成内存泄漏,并且在谈论内存时是最好的。

问题

1) 那么,使用 ViewModel 调用 Retrofit 是最好的选择,真的可以避免内存泄漏吗?

2) 像 MoviesActivity 那样在 onCreate() 中使用 enqueue() 调用改造有什么问题吗?

3) 在这种方法中,哪一种方法最适合进行调用以验证用户身份?

【问题讨论】:

  • 这是个好问题。你找到解决办法了吗?
  • 我仍在寻找这个主题,但是我得到了很多新信息。我会回答的

标签: android memory-leaks retrofit retrofit2


【解决方案1】:

1) 以正确的方式使用 ViewModel 不会导致内存泄漏,是一个不错的选择。你可以看到google's video explanation,还有这个讲座谈论difference between MVP and MVVM。这第二讲对这个话题给出了很好的解释。

2) 在 onCreate() 中调用改造 enqueue() 是一个问题,它会导致内存泄漏。问题是当你第一次开始你的活动时,它会调用改造,然后当你旋转你的设备时,所有的活动都会被销毁并重新创建。如果在数据加载完成之前旋转设备,再次调用 onCreate() 时会调用第二次 Retrofit,如果继续执行 10 次,则会调用 10 次 Retrofit,然后停止旋转设备。调用的结果将开始出现,bzzz :( 结果将显示 10 次,因为您调用了 10 次。这意味着存在巨大的内存泄漏。如果您实施这种方法并使用 LeakCanary,您将看到泄漏.

3) 最好的方法是什么?

  • 在 onCreate() 中使用 enqueue() 方法肯定不好。
  • 静态内部类(使用 AsyncTask)很好,但它无法承受配置更改,因为您需要在 onDestroy() 中取消它。这就是它不会导致内存泄漏的原因,因为 Task 在 onDestroy() 中被取消。
  • MVP 是一种非常好的进行改造调用的方法。您可以在此medium article 中了解更多信息,源代码为here
  • 阅读此article,了解 MVP 和 MVVM 之间的区别。
  • 最后,Google 建议开发者在这些场景中使用 ViewModel

您可以在另一个question 中关注我的讨论。我们谈论的是同一主题,但同时将用户登录到服务器。

【讨论】:

    【解决方案2】:

    如果在onCreate() 中调用enqueue() 会导致内存泄漏的原因是入队调用将持有对您的活动实例的引用,因为传递给它的回调实例(匿名类)持有对封闭类实例。只要在onDestroy()之前取消就没有问题了。

    【讨论】:

      猜你喜欢
      • 2014-07-30
      • 1970-01-01
      • 2018-04-08
      • 2013-06-24
      • 1970-01-01
      • 1970-01-01
      • 2016-03-29
      相关资源
      最近更新 更多