web-dev-qa-db-fra.com

Avertissement: Cette classe AsyncTask doit être statique ou des fuites peuvent se produire.

Je reçois un avertissement dans mon code qui dit:

Cette classe AsyncTask doit être statique ou des fuites peuvent se produire (Android.os.AsyncTask anonyme)

L'avertissement complet est:

Cette classe AsyncTask doit être statique ou des fuites peuvent se produire (Android.os.AsyncTask anonyme). Un champ statique entraîne la fuite des contextes. Les classes internes non statiques ont une référence implicite à leur classe externe. Si cette classe externe est par exemple un fragment ou une activité, cette référence signifie que le gestionnaire/chargeur/tâche de longue durée tiendra une référence à l'activité qui l'empêche de se faire ramasser. De même, les références de champ directes aux activités et aux fragments provenant de ces instances plus longues peuvent provoquer des fuites. Les classes ViewModel ne doivent jamais pointer sur des vues ou des contextes non applicatifs.

Ceci est mon code:

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

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

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

            return null;
        }
    }.execute();

Comment puis-je corriger cela?

215
Keyur Nimavat

Les classes internes non statiques contiennent une référence à la classe qui le contient. Lorsque vous déclarez AsyncTask en tant que classe interne, sa durée de vie peut être supérieure à celle de la classe contenant Activity. Cela est dû à la référence implicite à la classe contenante. Cela empêchera l'activité d'être ramassée, d'où la fuite de mémoire.

Pour résoudre votre problème, utilisez la classe imbriquée statique au lieu de la classe anonyme, locale et interne ou la classe de niveau supérieur.

49
Anand

Comment utiliser une classe AsyncTask interne statique

Pour éviter les fuites, vous pouvez rendre la classe interne statique. Le problème avec cela, cependant, est que vous n'avez plus accès aux vues de l'interface utilisateur de l'activité ou aux variables membres. Vous pouvez transmettre une référence à la variable Context, mais vous courez le même risque de fuite de mémoire. (Android ne peut pas récupérer l’activité après sa fermeture si la classe AsyncTask en fait une référence forte.) La solution consiste à faire une référence faible à l’Activité (ou à ce que vous ayez besoin de 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;
        }
    }
}

Remarques

  • Autant que je sache, ce type de risque de fuite de mémoire a toujours été vrai, mais je n'ai commencé à voir l'avertissement que dans Android Studio 3.0. La plupart des tutoriels AsyncTask disponibles ne sont toujours pas traités (voir ici , ici , ici , et ici ).
  • Vous suivriez également une procédure similaire si votre AsyncTask était une classe de niveau supérieur. Une classe interne statique est fondamentalement la même chose qu'une classe de niveau supérieur en Java.
  • Si vous n'avez pas besoin de l'activité elle-même mais souhaitez quand même le contexte (par exemple, pour afficher un Toast), vous pouvez transmettre une référence au contexte de l'application. Dans ce cas, le constructeur AsyncTask ressemblerait à ceci:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
    
  • Il existe certains arguments pour ignorer cet avertissement et utiliser uniquement la classe non statique. Après tout, la tâche asynchrone est censée être de très courte durée (quelques secondes au plus) et elle publiera sa référence à l’activité quand elle se terminera quand même. Voir this et this .
  • Excellent article: Comment faire fuir un contexte: Handlers & Inner Classes

Kotlin

En Kotlin, juste n'incluez pas le mot clé inner pour la classe interne. Cela le rend statique par défaut.

Je ne suis pas encore trop bon chez Kotlin, corrigez donc le code ci-dessous s'il peut être amélioré:

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
        }
    }
}
482
Suragch

Cette classe AsyncTask devrait être statique ou des fuites pourraient se produire car

  • Lorsque Activity est détruit, AsyncTask (les deux static ou non-static) sont toujours en cours d'exécution.
  • Si la classe interne est la classe non-static (AsyncTask), elle fera référence à la classe externe (Activity).
  • Si un objet ne contient aucune référence pointant vers lui, Garbage Collected le libérera. Si un objet est inutilisé et Garbage Collectedne peut pas relâchez-le => fuite de mémoire

=> Si AsyncTask est non-static, Activity ne déclenchera pas l'événement, il est détruit => fuite

Solution pour mettre à jour l'interface utilisateur après make AsyncTask comme classe statique sans fuite

1) Utilisez WeakReference comme @Suragch answer
2) Envoyer et supprimer Activity référence à (de) 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);
        }
    }
}
20
Phan Van Linh