在调用改造2时避免内存泄漏

时间:2018-08-30 01:15:13

标签: android memory-leaks retrofit retrofit2

根据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.
                    }
                });
    }
}
  

现在,如果此网络呼叫在非常慢的连接上运行,并且在呼叫结束之前,活动被某种程度地旋转或破坏,那么整个活动实例将被泄漏。

我尝试在我的应用上执行相同的操作。我在onCreate()方法中调用了大内容(240个对象)usign enqueue()。然后,当内容加载时,我多次旋转设备,LeakCanary向我展示了Activity中的内存泄漏,如文章所述。

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

第一个选项

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

在后台线程中调用改造

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类中调用了Retrofit异步,并且在旋转屏幕(活动被破坏)时,无需再次加载数据,因为它保持了保存状态。

这种方法也不会造成内存泄漏,是谈论内存时最好的方法。

问题

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

2)是否可以像MoviesActivity一样在onCreate()中使用enqueue()调用改造?

3)在这种方法中,拨打电话进行用户身份验证的最佳方法是哪种?

2 个答案:

答案 0 :(得分:1)

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

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

3)最佳方法是什么?

  • 在onCreate()中使用enqueue()方法绝对不好。
  • 静态内部类(使用AsyncTask)是好的,但是它不能幸免于配置更改,因为您需要在onDestroy()中将其取消。这就是为什么它不会导致内存泄漏的原因,因为在onDestroy()中取消了Task。
  • MVP是进行改装呼叫的一种非常好的方法。您可以在此medium article中了解更多信息,源代码为here
  • 如本article所述,了解MVP和MVVM之间的区别。
  • 最后,Google建议开发人员在这种情况下使用ViewModel

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

答案 1 :(得分:0)

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