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

270

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 fuira les 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 contiendra une référence à l'activité qui l'empêche de récupérer les ordures. De même, les références de champ directes aux activités et aux fragments de ces instances plus longues peuvent provoquer des fuites. Les classes ViewModel ne doivent jamais pointer vers des vues ou des contextes non liés à une application.

Voici 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?

Keyur Nimavat
la source
2
la lecture de cet androiddesignpatterns.com/2013/01/… devrait vous donner une idée de pourquoi il devrait être statique
Raghunandan
Jusqu'à présent, j'ai toujours pu remplacer AsyncTask par un nouveau Thread (...). Statr () en combinaison avec runOnUiThread (...) si nécessaire, donc je n'ai plus à gérer cet avertissement.
Hong
1
Quelle est la solution en kotlin pour ce problème?
TapanHP
Veuillez reconsidérer la réponse qui doit être acceptée. Voir les réponses ci-dessous.
Ωmega
Dans mon cas, je reçois cet avertissement d'un Singleton qui n'a aucune référence directe à Activity (il reçoit la sortie de myActivity.getApplication()dans le constructeur privé pour le Singleton, afin d'initialiser les classes RoomDB et autres classes). Mes ViewModels obtiennent l'instance Singleton comme référence privée pour effectuer certaines opérations sur la base de données. Ainsi, les ViewModels importent le package Singleton, ainsi que l' android.app.Applicationun d'eux même android.app.Activity. Puisque "the Singleton" n'a pas besoin d'importer ces ViewModels pour fonctionner, même ainsi, des fuites de mémoire peuvent-elles se produire?
SebasSBM

Réponses:

65

Les classes internes non statiques contiennent une référence à la classe conteneur. Lorsque vous déclarez en AsyncTasktant que classe interne, elle peut vivre plus longtemps que la Activityclasse conteneur . Cela est dû à la référence implicite à la classe conteneur. Cela empêchera l'activité d'être récupérée, d'où la fuite de mémoire.

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

Anand
la source
1
La solution réside dans l'avertissement lui-même. Utilisez soit une classe imbriquée statique, soit une classe de niveau supérieur.
Anand
3
@KeyurNimavat Je pense que vous pouvez passer dans une référence faible à votre activité
peterchaula
42
alors quel est l'intérêt d'utiliser AsyncTask? s'il est plus facile d'exécuter de nouveaux Thread et handler.post ou view.post (pour mettre à jour l'interface utilisateur) à la fin dans la méthode d'exécution de Thread. Si AsyncTask est une classe statique ou de niveau supérieur, il est difficile d'accéder aux variables / méthodes nécessaires à partir de celle
user924
8
aucun code n'est fourni pour savoir comment l'utiliser correctement. J'ai déjà essayé d'y mettre de l'électricité statique, mais il y aura plus d'avertissements et d'erreurs
Kasnady
19
@Anand Veuillez supprimer cette réponse pour que la réponse la plus utile sur stackoverflow.com/a/46166223/145119 soit en haut.
Mithaldu
557

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 Contextmais vous courez alors le même risque de fuite de mémoire. (Android ne peut pas ramasser l'activité après sa fermeture si la classe AsyncTask a une référence forte à elle.) La solution est de faire une référence faible à l'activité (ou tout Contextce dont vous avez besoin).

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

  • Pour autant que je sache, ce type de danger de fuite de mémoire a toujours été vrai, mais je n'ai commencé à voir l'avertissement dans Android Studio 3.0. Beaucoup de AsyncTaskdidacticiels principaux ne sont toujours pas traités (voir ici , ici , ici et ici ).
  • Vous suivriez également une procédure similaire si vous AsyncTaskétiez une classe de niveau supérieur. Une classe interne statique est fondamentalement la même qu'une classe de niveau supérieur en Java.
  • Si vous n'avez pas besoin de l'activité elle-même mais souhaitez toujours le contexte (par exemple, pour afficher un Toast), vous pouvez passer une référence au contexte de l'application. Dans ce cas, le AsyncTaskconstructeur 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 AsyncTask est destinée à être de très courte durée (quelques secondes au plus), et elle libérera sa référence à l'activité lorsqu'elle se terminera de toute façon. Voir ceci et cela .
  • Excellent article: Comment divulguer un contexte: gestionnaires et classes internes

Kotlin

Dans Kotlin, n'incluezinner simplement pas le mot - clé pour la classe interne. Cela le rend statique par défaut.

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
        }
    }
}
Suragch
la source
1
@ManojFrekzz, Non, en fait, vous pouvez mettre à jour l'interface utilisateur en utilisant la référence faible à l'activité transmise. Consultez à onPostExecutenouveau ma méthode dans le code ci-dessus. Vous pouvez voir que j'ai mis à jour l'interface utilisateur TextViewlà-bas. Utilisez simplement activity.findViewByIdpour obtenir une référence à l'élément de l'interface utilisateur que vous devez mettre à jour.
Suragch
7
+1. C'est la solution la meilleure et la plus propre que j'aie jamais vue! Seulement, si vous souhaitez modifier l'interface utilisateur dans la méthode onPostExecute, vous devez également vérifier si l'activité est en cours de destruction: activity.isFinishing ()
zapotec
1
Remarque! Lorsque j'utilisais cette réponse, je continuais à exécuter des exceptions de pointeur nul car l'opération doInBackground était gourmande en mémoire, déclenchait la récupération de place, collectait la faiblesse de la référence et tuait la tâche asynctask. Vous voudrez peut-être utiliser une SoftReference au lieu d'une faible si vous savez que vos opérations d'arrière-plan sont plus gourmandes en mémoire.
PGMacDesign
2
@Sunny, transmettez une référence au Fragment au lieu de l'Activité. Vous voudriez retirer le activity.isFinishing()chèque et éventuellement le remplacer par un fragment.isRemoving()chèque. Cependant, je n'ai pas beaucoup travaillé avec des fragments récemment.
Suragch
1
@bashan, (1) Si la classe externe n'est pas une activité, alors dans votre AsyncTaskconstructeur vous passez une référence à votre classe externe. Et doInBackground()vous pouvez obtenir une référence à la classe externe avec MyOuterClass ref = classReference.get(). Vérifiez null. (2) onPostExecute()Vous ne mettez à jour l'interface utilisateur qu'avec les résultats de la tâche en arrière-plan. C'est comme chaque fois que vous mettez à jour l'interface utilisateur. La vérification activity.isFinishing()consiste simplement à s'assurer que l'activité n'a pas encore commencé à se terminer, auquel cas il serait inutile de mettre à jour l'interface utilisateur.
Suragch
23

Cette AsyncTaskclasse doit être statique ou des fuites peuvent se produire car

  • Quand Activityest détruit, AsyncTask(les deux staticou non-static) 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, Garbage Collectedil le libérera. Si un objet n'est pas utilisé et Garbage Collected ne peut pas le libérer => fuite de mémoire

=> Si AsyncTaskc'est le cas non-static, Activityne déclenchera pas l'événement il est détruit => fuite

Solution pour mettre à jour l'interface utilisateur après avoir fait AsyncTask en tant que classe statique sans fuite

1) Utilisez WeakReferencecomme @Suragch réponse
2) Envoyez et supprimez la Activityré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);
        }
    }
}
Phan Van Linh
la source
5
@Suragch votre lien indique que, même si onDestroy n'est pas garanti d'être appelé, la seule situation où il ne l'est pas est lorsque le système tue le processus, donc toutes les ressources sont libérées de toute façon. Donc, ne faites pas de sauvegarde ici, mais vous pouvez faire une version de ressource ici.
Angelo Fuchs
2
Dans le cas d'un cas d'utilisation AsyncTask non statique, pourquoi ne pouvons-nous pas simplement définir la variable d'instance AsyncTask sur NULL, similaire à ceci. cela ne dira-t-il pas au GC de libérer l'activité si AsyncTask est en cours d'exécution?
Hanif
@Hanif définir la variable d'instance AsyncTask sur NUL n'aidera pas, car la tâche a toujours la référence via l'écouteur.
Susanta