Почему метод обратного вызова для асинхронного сетевого вызова не может вызвать утечку памяти при завершении операции?

Мы знаем, что анонимный внутренний класс может вызвать утечку памяти. Но почему он не работает при асинхронном сетевом вызове.
Например:

OkHttpClient client = new OkHttpClient();

 Request request = new Request.Builder()
                .get()
                .url(url)
                .build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                // String str = response.body().string();
                // do sth to our View, but those views may be null when activity finished
                }

            }
        });

Мы изменим наши состояния представления при вызове метода обратного вызова, но эти представления всегда будут нулевыми, когда активность будет завершена. Почему этот анонимный экземпляр внутреннего класса для обратного вызова не может вызвать утечку активности.

Ответ 1

почему этот анонимный экземпляр внутреннего класса для обратного вызова не может вызвать утечку активности

Я предполагаю, что вы имеете в виду, что это не вызывает утечку памяти, но это, безусловно, могло бы, учитывая, что область, в которой вы создаете анонимный Callback представляет собой Activity.

Если экземпляр внутреннего класса внутри Android Activity, а затем передать ссылку на этот экземпляр в какой - то другой компонент, если этот компонент доступен, так будет экземпляром внутреннего класса. Например, рассмотрим следующее:

class MemorySink {

    static private List<Callback> callbacks = new ArrayList<>();

    public static void doSomething(Callback callback){
        callbacks.add(callback);
    }
}

Если вы создали экземпляры Callback из некоторых действий и doSomething(callback) их doSomething(callback), когда одно из Activity уничтожено, система больше не будет использовать этот экземпляр, ожидается, что сборщик мусора выпустит этот экземпляр. Но, если MemorySink здесь есть ссылка на Callback, который имеет ссылку на эту Activity, экземпляр этой Activity будет оставаться в памяти даже после того, как разрушаются. Bam, утечка памяти.

Таким образом, вы говорите, что ваш образец не вызывает утечку памяти, я сначала предлагаю вам попробовать MemorySink, создать простую Activity "MainActivity" с 2 кнопками и, возможно, некоторыми изображениями, чтобы увеличить объем памяти. В onCreate задайте слушателя на первой кнопке следующим образом:

    findViewById(R.id.firstButton).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startActivity(new Intent(MainActivity.this, MainActivity.class));
            MemorySink.doSomething(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {

                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {

                }
            });
            finish();
        }
    });

Вы только что создали утечку памяти, используя Callback. Каждый раз, когда вы нажимаете кнопку в MainActivity, экземпляр MainActivity будет уничтожен и будет создан новый. Но старый экземпляр MainActivity будет сохранен в памяти. Я приглашаю вас многократно нажимать кнопку, а затем выгружать память (с Android Profiler в Android Studio) или использовать LeakCanary.

Итак, мы создали утечку памяти с помощью Callback, тот же класс из OP. Теперь добавим этот метод в MemorySink:

public static void releaseAll() {
    callbacks.clear();
}

И назовите его с другой кнопки MainActivity. Если вы нажмете первую кнопку много раз (лучше, если у вас есть изображения в MainActivity), вы увидите, что MainActivity памяти MainActivity, даже если вы вручную запускаете сбор мусора (профиль Android). Затем вы нажимаете эту вторую кнопку, освобождаются все ссылки на Callback, запускают сборку мусора и память опускается. Больше утечки памяти.

Поэтому проблема заключается не в том, что Callback может или не может создать утечку памяти, это, безусловно, может. Проблема в том, где вы передаете этот Callback. В этом случае OkHttpClient не создает утечку памяти, поэтому вы говорите, но это не гарантирует, что это всегда произойдет. В этом случае вам нужно быть уверенным в реализации OkHttpClient чтобы сказать, что он не будет генерировать утечку памяти.

Мое предложение состоит в том, чтобы всегда предполагать, что утечка памяти может произойти, если вы передаете ссылку на Activity на какой-либо внешний класс.

Ответ 2

Да, вы правы, поэтому вы можете добавить

 if (response.isSuccessful()) {
                // String str = response.body().string();
                if(view != null){
                //do sth to view
                }
 }

Ответ 3

Нестатический внутренний класс имеет сильную ссылку на экземпляр внешнего класса. С другой стороны, статический внутренний класс не имеет сильной ссылки на экземпляр внешнего класса. Он использует WeakReference для ссылки на внешний класс. вы можете узнать больше здесь

Ответ 4

client.newCall(request).enqueue(new Callback() {...})

Здесь вы передаете объект обратного вызова для переоснащения. Таким образом, вы говорите, что модифицировали, чтобы использовать его в этом процессе для обратного вызова вам, когда вызов завершен.

Этот процесс не находится в текущей деятельности, и он находится в отдельном потоке и контексте (концептуально, а не в контексте android). После того, как ваша деятельность будет уничтожена чем-то (например, вращением), служба переоснащения, вероятно, все еще жива и держит ссылку на этот объект, и ваша деятельность вызывает утечку памяти (ее нельзя очистить от памяти и, таким образом, загрязняет память), потому что ваша дооснащение объекта обратного вызова.

Если вы хотите исправить эту утечку памяти, вам нужно будет отменить вызов и удалить обратный вызов в onStop, но таким образом вы потеряете вызов при вращении.

Лучше всего делать, что очень рекомендуется, - это то, что вы не выполняете асинхронный материал внутри самой активности и делаете это в отдельном объекте (например, Presenter в шаблоне MVP или в ViewModel в MVVM), который имеет другой жизненный цикл чем активность (жизненный цикл, который не уничтожается вращением или...).