LINUX.ORG.RU
решено ФорумMobile

AsyncTask class should be static, memory leaks

 , ,


0

2

Простейший код для Андроид 4+:

    public class MyAsyncTask extends AsyncTask<String, Void, String> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected String doInBackground(String... params) {
            ...
            return result;
        }

        @Override
        protected void onPostExecute(String result) {
            ...
        }
    }

This AsyncTask class should be static or leaks might occur (...MainActivity.MyAsyncTask) less... (⌘F1) A static field will leak contexts. Non-static inner classes have an implicit reference to their outer class. If that outer class is for example a Fragment or Activity, then this reference means that the long-running handler/loader/task will hold a reference to the activity which prevents it from getting garbage collected. Similarly, direct field references to activities and fragments from these longer running instances can cause leaks. ViewModel classes should never point to Views or non-application Contexts.

В Java я новичок.

Я правильно понимаю, что проблема - в возможности «подвисания» background-процесса, который не освободит память при закрытии MainActivity?

В случае Андроида - это какие практические ситуации? Сворачивание приложения при активном background-процессе? Или останов приложения?

Ээх, а в моё время линт был тупее и такие вещи приходилось держать в голове.

Почитай в принципе про вложенные классы в Java (когда ты объявляешь один класс внутри другого). Основная идея вот в чём.

public class ClassA {

    private int number = 0;
    
    private class ClassB {
    
        public void doSomething() {
            number = 1; // No errors
        }
    }
    
    private static class ClassC {
    
        public void doSomething() {
            number = 1; // Error: Non-static field cannot be referenced from static context
        }
    }
}
Если ты объявляешь один класс внутри другого без модификатора static, то он держит неявную ссылку на внешний класс и соответственно имеет доступ ко всем его филдам. В контексте Android это приводит к такой проблеме, ты объявил AsyncTask как нестатичный вложенный класс, он держит неявную ссылку на MainActivity(или фрагмент, зависит от того, где объявил, но сути не меняет). Предположим, что ты пуляешь этот запрос и во время выполнения либо выходишь из приложения клавишей назад, либо поворачиваешь телефон. В случае с выходом ты получишь одну утечку, потому что из-за этой неявной ссылки GC не сможет собрать MainActivity, а активити в свою очередь протечет со всей иерархией вьюшек внутри неё, контекстом и прочим системным барахлом. Но ещё хуже, если ты сделаешь поворот экрана. Будет создано новое активити, при этом по той же самой причине старое GC собрать не сможет и ты рискуешь наплодить огромное количество утечек просто поворачивая телефон.

Jefail ★★★★
()
Ответ на: комментарий от Jefail

Так asynctask когда-нибудь завершится. Если он не планируется долгоживущим (скажем, запрос к бд к девайсе, который обычно выполняется за доли секунды), то разве это проблема?

KivApple ★★★★★
()
Ответ на: комментарий от Jefail

К счастью. поворотов экрана не планируется. Так как:

  <activity android:name="com.mydomain.app.MainActivity"
      android:screenOrientation="portrait">
  </activity>

Хоть у меня запрос к REST-сервису и короткий (1-2 сек), но кнопку Назад - пользователь кликнуть всегда успеет.

Просто если объявить вложенный класс статическим, то придётся многое в этом Activity делать статическим - все поля, которые тянутся из AsyncTask. Я поначалу стал так делать - но потом понял, что что-то не так, и откатил всё обратно.

Mirage1_
() автор топика
Ответ на: комментарий от Mirage1_

Встроенный класс захватывает контекст. В твоем случае активности или фрагмента. AsyncTask выполняется Executer-ом. Следовательно, когда закончит выполняться AsyncTask, сборщик мусора сможет собрать твою активность, т.к. ссылка на Activity будет недоступна. Как бэ все нормально, но линт предупреждает.

В твоем случае можешь сделать Таск статическим и применить паттерн Observer. Подписался на событие, что-то сделать, когда завершилось. Отписаться по закрытию активности и отменить таск.

Это просто как вариант решения.

pozitiffcat ★★★
()
Ответ на: комментарий от pozitiffcat

Подписался на событие, что-то сделать, когда завершилось. Отписаться по закрытию активности и отменить таск.

Вышеприведённую терминологию я понимаю чисто интуитивно.

А если я не буду отменять таск? Проблема возникнет в случае перехода активности в onPause(), onStop() или onDestroy() ?

onPause() и onStop() ведь не должны приводить к удалению объекта активности.

Mirage1_
() автор топика
Ответ на: комментарий от KivApple

База может быть и не проблема, а интернет запрос на edge может затянуться. Да и вообще, проблема больше в сути того, что течёт, а не в том, насколько реально от этого огрести.

Jefail ★★★★
()
Ответ на: комментарий от pozitiffcat

Вторым способом являются слабые ссылки, так не придётся руками отписываться.

Jefail ★★★★
()
Ответ на: комментарий от Jefail

А это уже должно подвести тебя к идее о том, что что-то не так с архитектурой.

Увы, Google пока не даёт вменяемой альтернативы джаве.

Mirage1_
() автор топика
Ответ на: комментарий от Jefail

Я думаю, выходом из положения в это задаче могла бы быть замена асинхорнных (AsyncTask) запросов к REST-сервису на блокирующие, в цикле.

Можешь ткнуть в простой пример блокирующего HTTPS-запроса? Чтобы работало на всех Андроидах 4+. Я что-то туплю. Смотрел, для HTTP - нашёл, а вот для HTTPS - нет. Только асинхронно.

Mirage1_
() автор топика
Ответ на: комментарий от Mirage1_

Ты не можешь использовать блокирующий запрос в том смысле в котором хочешь. Запрос сети в основном потоке вызовет исключение и программа упадет. Начиная с адроида 4, под запросы нужно использовать другой поток, а это по любому асинхронность с которой надо будет иметь дело.

F457 ★★★★
()
Ответ на: комментарий от Mirage1_

что что-то не так с _архитектурой_.

Увы, Google пока не даёт вменяемой альтернативы _джаве_

Архитектура не имеет никакого отношения к языку. Проблемы архитектуры андроида возникают на любом языке.

F457 ★★★★
()

Про сеть в Андроиде с асинк тасками. Правильный ответ на это: архитектурные компоненты. Конкретно viewModel и liveData. Если не хочешь огрести всех проблем и набить все шишки, которые люди набили за 10 лет. Хотя можно и пойти долгим путем по всем граблям. Будет бесценный опыт.

Короткий и работающий путь. Пишу с телефона по памяти посему будут неточности. В viewModel запускается asynkTask. Можно его не создавать статиком, утечек не будет ибо в методе viewModel.onClear мы обязательно вызовем завершение этой таски, а ссылок на активность у нас нет. Завершение viewModel - это значит юзер закрыл экран и результат запроса уже не нужен.

Далее результат запроса сохраняем в лайфдату. В активности подписываемся на лайф дату и делаем с данными что нам нужно. Правильно все что должно отображаться создать в виде лайф дат в вью модели, а в активности на них подписываться и отобрать.

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

Итого в итоге у нас: Запросы изолированы от жизненного цикла активности. Активность свободна от кода получения данных и занимается только их отображением. За счет лайфдаты данные у нас не придут в неправильный этап жизненного цикла: нет IllegalStateException, потери результата запроса и утечек.

После освоения этих двух основных библиотек. Можно двигаться дальше. Заменив асинк таски для сети на ретрофит...

F457 ★★★★
()
Ответ на: комментарий от F457

А если оставить как есть - могут ли две-три задачи AsyncTask иметь из своих методов одновременный доступ к глобальной переменной?

Если поле класса объявить static, например?

Mirage1_
() автор топика
Ответ на: комментарий от Mirage1_

могут ли две-три задачи AsyncTask иметь из своих методов одновременный доступ к глобальной переменной?

Конечно могут. Онаж глобальная. Но я надеюсь, ты осознаешь вероятность отстрела ноги с использованием глобальных переменных для расшаривания состояния... Хотя ты еще сверху желаешь шлифануть это многопоточным доступом, разве что нынче 1 тред на все асинк таски - будет не очень больно... Тут каждый сам кузнец своего счастья. Best practices я тебе описал.

F457 ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.