Подтвердить что ты не робот

Предупреждение. Этот класс AsyncTask должен быть статическим или может возникнуть утечка

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

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

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

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

Это мой код:

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

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

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

            return null;
        }
    }.execute();

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

4b9b3361

Ответ 1

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

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

Ответ 2

Как использовать статический внутренний класс 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 UI
            TextView textView = activity.findViewById(R.id.textview);
            textView.setText(result);

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

Заметки

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

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

Котлин

В Kotlin просто не включайте ключевое слово inner для внутреннего класса. Это делает его статическим по умолчанию.

Я еще не слишком хорош в Kotlin, поэтому исправьте мой код ниже, если он может быть улучшен:

class MyActivity : AppCompatActivity() {

    internal var mSomeMemberVariable = 123

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

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

    private class MyTask
    internal constructor(context: MyActivity) : AsyncTask<Void, Void, String>() {

        private val activityReference: WeakReference<MyActivity> = WeakReference(context)

        override fun doInBackground(vararg params: Void): String {

            // do some long running task...

            return "task finished"
        }

        override fun onPostExecute(result: String) {

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

            // modify the activity UI
            val textView = activity.findViewById(R.id.textview)
            textView.setText(result)

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

Ответ 3

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

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

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

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

1) Используйте WeakReference как @Suragch answer
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);
        }
    }
}

Ответ 4

В любое время, когда вы делаете <Void, Void, Void> в AsyncTask, вы, вероятно, не используете его так, как предполагалось при его использовании. Если вы следуете за реализацией правильных крючков, вы можете избежать этой проблемы вместе. В вашем примере вызывается runOnUiThread, который является единственной зависимостью Activity, которая, как вам кажется, имеет в этом примере. AsyncTask уже предоставляет способ запуска действий в потоке пользовательского интерфейса. Вот краткое изложение, если что вы должны делать:

  • Создайте статическую AsyncTask или создайте конкретную версию вне Activity.
  • Передайте аргументы, необходимые для выполнения задания, используя task.execute(Params...)
  • Используйте аргументы для работы в doInBackground(Params...) и возвращайте результат вместо возврата null.
  • Используйте свой результат и обновляйте свои пользовательские интерфейсы на onPostExecute(Result) (или onProgressUpdate(Progress...)), так как оба они работают в потоке пользовательского интерфейса (поэтому вам не нужно вызывать runOnUiThread).

Документация для Android содержит довольно хороший пример использования рамки AsyncTask.

Обратите внимание: если ваш mAdapter является адаптером, прикрепленным к некоторому адаптеру (например, ListView и т.д.), и вы используете эту задачу для получения данных для представления, я настоятельно рекомендую следующий шаблон во избежание условий гонки:

  • Получить и вернуть данные в doInBackground
  • В onPostExecute (помните, что это выполняется для потока пользовательского интерфейса для вас) обновите набор данных, который пользовательский интерфейс использует с вашими данными результата, затем вызовите notifyDataSetChanged