Avviso: questa classe AsyncTask deve essere statica o potrebbero verificarsi delle perdite


270

Ricevo un avviso nel mio codice che indica:

Questa classe AsyncTask dovrebbe essere statica o potrebbero verificarsi delle perdite (anonimo android.os.AsyncTask)

L'avvertimento completo è:

Questa classe AsyncTask dovrebbe essere statica o potrebbero verificarsi delle perdite (anonimo android.os.AsyncTask) Un campo statico perderà i contesti. Le classi interne non statiche hanno un riferimento implicito alla loro classe esterna. Se quella classe esterna è ad esempio un frammento o attività, questo riferimento indica che il gestore / caricatore / attività a esecuzione prolungata conterrà un riferimento all'attività che impedisce di raccogliere la spazzatura. Allo stesso modo, i riferimenti di campo diretti ad attività e frammenti di queste istanze più lunghe possono causare perdite. Le classi ViewModel non devono mai puntare a Viste o Contesti non applicativi.

Questo è il mio codice:

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

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

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

            return null;
        }
    }.execute();

Come posso correggere questo?


2
leggere questo androiddesignpatterns.com/2013/01/… dovrebbe darti un suggerimento sul perché dovrebbe essere statico
Raghunandan,

Finora sono sempre stato in grado di sostituire AsyncTask con un nuovo thread (...). Statr () in combinazione con runOnUiThread (...) se necessario, quindi non devo più occuparmi di questo avviso.
Hong

1
Qual è la soluzione in kotlin per questo problema?
TapanHP,

Si prega di riconsiderare quale risposta dovrebbe essere quella accettata. Vedi le risposte sotto.
Ωmega

Nel mio caso, ricevo questo avviso da un Singleton che non ha riferimenti diretti ad Activity (riceve l'output myActivity.getApplication()nel costruttore privato per Singleton, al fine di inizializzare le classi RoomDB e altre classi). I miei ViewModels ottengono l'istanza Singleton come riferimento privato per eseguire alcune operazioni sul DB. Quindi, i ViewModels importano il pacchetto Singleton e anche android.app.Applicationuno di essi android.app.Activity. Poiché "the Singleton" non ha bisogno di importare quei ViewModels per funzionare, anche così, potrebbero verificarsi perdite di memoria?
SebasSBM,

Risposte:


65

Le classi interne non statiche contengono un riferimento alla classe contenente. Quando dichiari AsyncTaskcome una classe interna, potrebbe vivere più a lungo della Activityclasse contenente . Ciò è dovuto al riferimento implicito alla classe contenente. Ciò impedirà la raccolta dei rifiuti nell'attività, quindi la perdita di memoria.

Per risolvere il problema, utilizzare la classe nidificata statica anziché la classe anonima, locale e interna oppure utilizzare la classe di livello superiore.


1
La soluzione sta nell'avvertimento stesso. Utilizzare una classe nidificata statica o una classe di livello superiore.
Anand,

3
@KeyurNimavat Penso che tu possa passare un debole riferimento alla tua attività
peterchaula

42
quindi qual è lo scopo di utilizzare AsyncTask? se è più semplice eseguire nuovi thread e handler.post o view.post (per aggiornare l'interfaccia utente) alla fine nel metodo run di Thread. Se AsyncTask è una classe statica o di livello superiore, è difficile accedere alle variabili / ai metodi necessari da esso
user924

8
non ci sono codici forniti su come è il modo giusto di usarlo. Ho mai provato a mettere statico lì, ma ci saranno più avvertimenti ed errori mostrati
Kasnady,

19
@Anand Elimina questa risposta in modo che la risposta più utile su stackoverflow.com/a/46166223/145119 possa essere in alto.
Mithaldu,

557

Come utilizzare una classe AsyncTask interna statica

Per evitare perdite, è possibile rendere statica la classe interna. Il problema, tuttavia, è che non hai più accesso alle viste dell'interfaccia utente o alle variabili dei membri dell'attività. È possibile passare un riferimento a Contextma si corre lo stesso rischio di perdita di memoria. (Android non può eseguire il garbage collection dell'Attività dopo la chiusura se la classe AsyncTask ha un forte riferimento ad essa.) La soluzione è fare un riferimento debole all'Attività (o qualunque cosa Contexttu abbia bisogno).

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;
        }
    }
}

Appunti

  • Per quanto ne so, questo tipo di pericolo di perdita di memoria è sempre stato vero, ma ho iniziato a vedere l'avviso solo in Android Studio 3.0. Molti dei principali AsyncTasktutorial là fuori non li trattano ancora (vedi qui , qui , qui e qui ).
  • Seguiresti anche una procedura simile se AsyncTaskfossi una classe di alto livello. Una classe interna statica è sostanzialmente la stessa di una classe di livello superiore in Java.
  • Se non hai bisogno dell'attività stessa ma desideri comunque il contesto (ad esempio, per visualizzare a Toast), puoi passare un riferimento al contesto dell'app. In questo caso il AsyncTaskcostruttore dovrebbe apparire così:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
  • Ci sono alcuni argomenti là fuori per ignorare questo avviso e usare semplicemente la classe non statica. Dopotutto, l'AsyncTask ha una durata molto breve (un paio di secondi al massimo) e rilascerà comunque il suo riferimento all'Attività al termine. Vedi questo e questo .
  • Articolo eccellente: come perdere un contesto: gestori e classi interne

Kotlin

In Kotlin semplicemente non includere la innerparola chiave per la classe interna. Questo lo rende statico per impostazione predefinita.

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, No, in realtà è possibile aggiornare l'interfaccia utente facendo uso del riferimento debole all'attività che è stata passata. Scopri di nuovo il mio onPostExecutemetodo nel codice sopra. Puoi vedere che ho aggiornato l'interfaccia utente TextViewlì. Basta usare activity.findViewByIdper ottenere un riferimento a qualsiasi elemento dell'interfaccia utente che è necessario aggiornare.
Suragch,

7
+1. Questa è la soluzione migliore e più pulita che abbia mai visto! Solo, nel caso in cui si desideri modificare l'interfaccia utente nel metodo onPostExecute, è necessario verificare anche se l'attività viene distrutta: activity.isFinishing ()
zapotec,

1
Nota! Quando ho usato questa risposta, ho continuato a imbattermi in Null Pointer Exceptions perché l'operazione doInBackground richiede molta memoria, ha attivato la garbage collection, ha raccolto il riferimento debole e ha eliminato l'asynctask. È possibile che si desideri utilizzare un SoftReference anziché uno debole se si sa che le operazioni in background richiedono più memoria.
PGMacDesign

2
@Sunny, passa un riferimento al frammento anziché all'attività. Si eliminerebbe l' activity.isFinishing()assegno e possibilmente sostituirlo con un fragment.isRemoving()assegno. Tuttavia, non ho lavorato molto con i frammenti di recente.
Suragch,

1
@bashan, (1) Se la classe esterna non è un'attività, nel tuo AsyncTaskcostruttore passi un riferimento alla tua classe esterna. E in doInBackground()te puoi ottenere un riferimento alla classe esterna con MyOuterClass ref = classReference.get(). Controlla per null. (2) In onPostExecute()stai solo aggiornando l'interfaccia utente con i risultati dell'attività in background. È come ogni altra volta che aggiorni l'interfaccia utente. Il controllo activity.isFinishing()è solo per assicurarsi che l'attività non abbia già iniziato a terminare, nel qual caso sarebbe inutile aggiornare l'interfaccia utente.
Suragch,

23

Questa AsyncTaskclasse dovrebbe essere statica o potrebbero verificarsi perdite perché

  • Quando Activityviene distrutto, AsyncTask(entrambi statico non-static) ancora in esecuzione
  • Se la classe interna è la classe non-static( AsyncTask), avrà riferimento alla classe esterna ( Activity).
  • Se un oggetto non ha riferimenti ad esso, Garbage Collectedlo rilascerà. Se un oggetto non è utilizzato e Garbage Collected non può rilasciarlo => perdita di memoria

=> Se lo AsyncTaskè non-static, Activitynon rilascerà l'evento che viene distrutto => perdita

Soluzione per l'aggiornamento dell'interfaccia utente dopo aver creato AsyncTask come classe statica senza perdite

1) Usa WeakReferencecome risposta @Suragch
2) Invia e rimuovi Activityriferimento a (da)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 il tuo link afferma che, mentre onDestroy non è garantito per essere chiamato, l'unica situazione in cui non è è quando il sistema uccide il processo, quindi tutte le risorse vengono comunque liberate. Quindi, non fare salvataggi qui, ma puoi fare un rilascio di risorse qui.
Angelo Fuchs,

2
Nel caso del caso d'uso AsyncTask non statico, perché non possiamo semplicemente impostare la variabile dell'istanza AsyncTask su NULL, in modo simile a questo. questo non dirà a GC di liberare l'attività anche se AsyncTask è in esecuzione?
Hanif

L'impostazione della variabile di istanza AsyncTask su NUL su @Hanif non sarà utile, poiché l'attività ha ancora il riferimento attraverso il listener.
Susanta,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.