Предупреждение: этот класс AsyncTask должен быть статическим, иначе могут возникнуть утечки

Я получаю предупреждение в своем коде, в котором говорится:

Этот класс AsyncTask должен быть статическим, иначе могут возникнуть утечки (анонимный android.os.AsyncTask)

Полное предупреждение:

Этот класс AsyncTask должен быть статическим, иначе могут возникнуть утечки (анонимный android.os.AsyncTask). Статическое поле будет пропускать контексты. Нестатические внутренние классы имеют неявную ссылку на свой внешний класс. Если этот внешний класс является, например, Fragment или Activity, то эта ссылка означает, что долго выполняющийся обработчик / загрузчик / задача будет содержать ссылку на операцию, которая не позволяет собирать мусор. Точно так же прямые ссылки на поля на действия и фрагменты из этих более длительных экземпляров могут вызвать утечки. Классы ViewModel никогда не должны указывать на представления или контексты вне приложения.

Это мой код:

 new AsyncTask<Void,Void,Void>(){

        @Override
        protected Void doInBackground(Void... params) {
            runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    mAdapter.notifyDataSetChanged();
                }
            });

            return null;
        }
    }.execute();

Как мне это исправить?

3 ответа

Решение

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

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

Как использовать статический внутренний класс AsyncTask

Чтобы предотвратить утечки, вы можете сделать внутренний класс статичным. Однако проблема в том, что у вас больше нет доступа к представлениям пользовательского интерфейса Activity или переменным-членам. Вы можете перейти по ссылке на Context но тогда вы рискуете утечкой памяти. (Android не может собирать мусор после закрытия, если класс AsyncTask имеет сильную ссылку на него.) Решение состоит в том, чтобы сделать слабую ссылку на действие (или что-то в этом роде). Context тебе нужно).

public class MyActivity extends AppCompatActivity {

    int mSomeMemberVariable = 123;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // start the AsyncTask, passing the Activity context
        // in to a custom constructor 
        new MyTask(this).execute();
    }

    private static class MyTask extends AsyncTask<Void, Void, String> {

        private WeakReference<MyActivity> activityReference;

        // only retain a weak reference to the activity 
        MyTask(MyActivity context) {
            activityReference = new WeakReference<>(context);
        }

        @Override
        protected String doInBackground(Void... params) {

            // do some long running task...

            return "task finished";
        }

        @Override
        protected void onPostExecute(String result) {

            // get a reference to the activity if it is still there
            MyActivity activity = activityReference.get();
            if (activity == null || activity.isFinishing()) return;

            // modify the activity's UI
            TextView textView = activity.findViewById(R.id.textview);
            textView.setText(result);

            // access Activity member variables
            activity.mSomeMemberVariable = 321;
        }
    }
}

Заметки

  • Насколько я знаю, этот тип утечки памяти всегда был правдой, но я только начал видеть предупреждение в Android Studio 3.0. Много основных AsyncTask учебники там до сих пор не имеют дело с этим (см. здесь, здесь, здесь и здесь).
  • Вы также должны следовать аналогичной процедуре, если ваш AsyncTask были на высшем уровне. Статический внутренний класс в основном такой же, как класс верхнего уровня в Java.
  • Если вам не нужна сама активность, но все еще требуется контекст (например, для отображения Toast), вы можете передать ссылку на контекст приложения. В этом случае AsyncTask Конструктор будет выглядеть так:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
    
  • Есть некоторые аргументы для игнорирования этого предупреждения и просто использования нестатического класса. В конце концов, AsyncTask предназначен для очень короткого срока службы (самое большее пару секунд), и он все равно выпустит свою ссылку на Activity, когда все равно завершится. Смотрите это и это.
  • Отличная статья: Как извлечь контекст: обработчики и внутренние классы

это AsyncTask класс должен быть статическим, иначе могут возникнуть утечки

  • когда Activity уничтожен, AsyncTask (и то и другое static или же non-static) Все еще работает
  • Если внутренний класс non-static (AsyncTask), он будет иметь ссылку на внешний класс (Activity).
  • Если объект не имеет ссылок на него, Garbage Collected выпустит это. Если объект не используется и Garbage Collected не может освободить его => утечка памяти

=> Если AsyncTask является non-static, Activity не выпустит событие, оно уничтожено => утечка

Решение для обновления пользовательского интерфейса после создания AsyncTask как статический класс без утечки

1) Использование WeakReference как ответ @Suragch
2) Отправить и удалить Activity ссылка на (с) AsyncTask

public class NoLeakAsyncTaskActivity extends AppCompatActivity {
    private ExampleAsyncTask asyncTask;

    @Override 
    protected void onCreate(Bundle savedInstanceState) {
        ...

        // START AsyncTask
        asyncTask = new ExampleAsyncTask();
        asyncTask.setListener(new ExampleAsyncTask.ExampleAsyncTaskListener() {
            @Override
            public void onExampleAsyncTaskFinished(Integer value) {
                // update UI in Activity here
            }
        });
        asyncTask.execute();
    }

    @Override
    protected void onDestroy() {
        asyncTask.setListener(null); // PREVENT LEAK AFTER ACTIVITY DESTROYED
        super.onDestroy();
    }

    static class ExampleAsyncTask extends AsyncTask<Void, Void, Integer> {
        private ExampleAsyncTaskListener listener;

        @Override
        protected Integer doInBackground(Void... voids) {
            ...
            return null;
        }

        @Override
        protected void onPostExecute(Integer value) {
            super.onPostExecute(value);
            if (listener != null) {
                listener.onExampleAsyncTaskFinished(value);
            }
        }

        public void setListener(ExampleAsyncTaskListener listener) {
            this.listener = listener;
        }

        public interface ExampleAsyncTaskListener {
            void onExampleAsyncTaskFinished(Integer value);
        }
    }
}
Другие вопросы по тегам