Попередження: Цей клас AsyncTask повинен стати статичним або можуть виникнути витоки


270

Я отримую попередження у своєму коді:

Цей клас 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();

Як це виправити?


2
читаючи цей androiddesignpatterns.com/2013/01/…, слід дати вам підказку, чому він повинен стати статичним
Рагунандан

Поки я завжди міг замінити AsyncTask новою Thread (...). Statr () у поєднанні з runOnUiThread (...), якщо потрібно, тому мені більше не доводиться боротися з цим попередженням.
Гонг

1
Яке рішення в котліні для цієї проблеми?
TapanHP

Перегляньте, яка відповідь має бути прийнятою. Відповіді дивіться нижче.
Оммега

У моєму випадку я отримую це попередження від Singleton, який не має прямих посилань на Activity (він отримує вихід myActivity.getApplication()у приватний конструктор для Singleton, з метою ініціалізації класів RoomDB та інших класів). Мої ViewModels отримують екземпляр Singleton як приватну посилання для виконання деяких операцій над БД. Отже, ViewModels імпортує пакет Singleton, а також android.app.Applicationодин з них навіть android.app.Activity. Оскільки "Singleton" не потрібно імпортувати ці ViewModels для роботи, навіть так, може виникнути витоки пам'яті?
СебасБМ

Відповіді:


64

Нестатичні внутрішні класи містять посилання на клас, що містить. Коли ви декларуєте AsyncTaskяк внутрішній клас, він може жити довше, ніж містить Activityклас. Це пояснюється неявним посиланням на клас, що містить. Це запобіжить збиранню сміття, отже, витік пам'яті.

Щоб вирішити свою проблему, використовуйте статичний вкладений клас замість анонімного, локального та внутрішнього класу або використовуйте клас вищого рівня.


1
Рішення полягає в самому попередженні. Використовуйте статичний вкладений клас або клас вищого рівня.
Ананд

3
@KeyurNimavat Я думаю, ви можете передати слабку посилання на свою діяльність
peterchaula

42
тож який сенс використовувати AsyncTask? якщо простіше запустити новий Thread і handler.post або view.post (оновити інтерфейс користувача) наприкінці методу запуску Thread. Якщо AsyncTask статичний або вищого рівня, то важко отримати доступ до потрібних змінних / методів з нього
user924

8
не передбачено коду про те, як правильно використовувати його. Я коли-небудь намагався поставити там статику, але з'явиться більше попереджень та помилок
Каснадій

19
@Anand Будь ласка, видаліть цю відповідь, щоб більш корисна відповідь на сайті stackoverflow.com/a/46166223/145119 могла бути вгорі.
Мітхалду

555

Як використовувати статичний внутрішній клас AsyncTask

Щоб уникнути протікання, ви можете зробити внутрішній клас статичним. Проблема з цим полягає в тому, що у вас більше немає доступу до представлень інтерфейсу користувача або змінних учасників. Ви можете передати посилання на, 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.
  • Якщо вам не потрібна сама активність, але ви все ще хочете контекст (наприклад, для відображення a Toast), ви можете передати посилання на контекст програми. У цьому випадку AsyncTaskконструктор виглядатиме так:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
  • Існує кілька аргументів для ігнорування цього попередження та просто використання нестатичного класу. Зрештою, AsyncTask призначений бути дуже короткочасним (найдовше пару секунд), і він звільнить своє посилання на Активність, коли все-таки закінчиться. Дивіться це і це .
  • Відмінна стаття: Як просочити контекст: Обробники та внутрішні класи

Котлін

У Котліні просто не включайте innerключове слово для внутрішнього класу. Це робить його статичним за замовчуванням.

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's UI
            val textView = activity.findViewById(R.id.textview)
            textView.setText(result)

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

1
@ManojFrekzz, Ні, насправді ви можете оновити користувальницький інтерфейс, скориставшись слабким посиланням на активність, що передається. Перевірте мій onPostExecuteметод ще раз у наведеному вище коді. Видно, що я там оновив інтерфейс користувача TextView. Просто скористайтеся, activity.findViewByIdщоб отримати посилання на будь-який елемент інтерфейсу, який вам потрібно оновити.
Сурагч

7
+1. Це найкраще і найчистіше рішення, яке я коли-небудь бачив! Тільки у випадку, якщо ви хочете змінити інтерфейс користувача в методі onPostExecute, ви також повинні перевірити, чи знищується активність: Activity.isFinishing ()
zapotec

1
Примітка! Використовуючи цю відповідь, я продовжував працювати над Null Pointer Exceptions, оскільки операція doInBackground була інтенсивною пам’яттю, запустила збір сміття, зібрала слабкі реферати та вбила асинтакт. Можливо, ви хочете використовувати SoftReference замість слабкої, якщо ви знаєте, що фонові операції займають більшу пам'ять.
PGMacDesign

2
@ Sunny, передайте посилання на Фрагмент замість діяльності. Ви виймете activity.isFinishing()чек і, можливо, заміните його fragment.isRemoving()чеком. Однак останнім часом я не дуже працював з фрагментами.
Сурагч

1
@bashan, (1) Якщо зовнішній клас не є діяльністю, то у своєму AsyncTaskконструкторі ви передаєте посилання на свій зовнішній клас. А у doInBackground()вас можна отримати посилання на зовнішній клас с MyOuterClass ref = classReference.get(). Перевірте null. (2) onPostExecute()Ви оновлюєте інтерфейс користувача лише з результатами фонового завдання. Як і будь-який інший раз, коли ви оновлюєте інтерфейс користувача. Перевірка activity.isFinishing()полягає лише в тому, щоб переконатися, що діяльність ще не почалася, і в цьому випадку безглуздо оновити інтерфейс користувача.
Сурагч

23

Цей 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);
        }
    }
}


5
@Suragch у вашому посиланні стверджує, що, хоча onDestroy не гарантовано називається, єдина ситуація, коли це не так, коли система вбиває процес, тому всі ресурси все одно звільняються. Отже, не економте тут, але ви можете зробити випуск ресурсу тут.
Анджело Фукс

2
У випадку нестатичного випадку використання AsyncTask, чому ми не можемо просто встановити змінну екземпляра AsyncTask на NULL, аналогічно цьому. не хочу це сказати GC на безкоштовну активність, хоча AsyncTask працює?
Ханіф

Налаштування @Hanif змінної екземпляра AsyncTask на NUL не допоможе, оскільки завдання все ще має оновлення через слухача.
Сусанта
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.