Advertencia: esta clase AsyncTask debe ser estática o pueden producirse fugas

270

Recibo una advertencia en mi código que dice:

Esta clase AsyncTask debe ser estática o pueden producirse fugas (anónimo android.os.AsyncTask)

La advertencia completa es:

Esta clase AsyncTask debería ser estática o podrían producirse fugas (android.os.AsyncTask anónimo) Un campo estático filtrará contextos. Las clases internas no estáticas tienen una referencia implícita a su clase externa. Si esa clase externa es, por ejemplo, un Fragmento o Actividad, entonces esta referencia significa que el manejador / cargador / tarea de larga duración mantendrá una referencia a la actividad que evita que se recolecte basura. Del mismo modo, las referencias directas de campo a actividades y fragmentos de estas instancias de ejecución más prolongada pueden causar fugas. Las clases ViewModel nunca deben apuntar a Vistas o Contextos que no sean de aplicación.

Este es mi código:

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

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

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

            return null;
        }
    }.execute();

¿Cómo corrijo esto?

Keyur Nimavat
fuente
2
leer este androiddesignpatterns.com/2013/01/… debería darle una pista de por qué debería ser estático
Raghunandan
Hasta ahora, siempre he podido reemplazar AsyncTask con un nuevo Thread (...). Statr () en combinación con runOnUiThread (...) si es necesario, por lo que ya no tengo que lidiar con esta advertencia.
Hong
1
¿Cuál es la solución en kotlin para este problema?
TapanHP
Por favor, reconsidere qué respuesta debe ser la aceptada. Ver las respuestas a continuación.
Ωmega
En mi caso, recibo esta advertencia de un Singleton que no tiene referencias directas a Activity (recibe el resultado myActivity.getApplication()en el constructor privado para Singleton, para inicializar las clases RoomDB y otras clases). Mis ViewModels obtienen la instancia Singleton como referencia privada para realizar algunas operaciones en la base de datos. Entonces, los ViewModels importan el paquete Singleton, así como también android.app.Applicationuno de ellos android.app.Activity. Dado que "the Singleton" no necesita importar esos ViewModels para que funcionen, aun así, ¿podrían ocurrir pérdidas de memoria?
SebasSBM

Respuestas:

65

Las clases internas no estáticas contienen una referencia a la clase que lo contiene. Cuando declarasAsyncTask como una clase interna, puede vivir más tiempo que la Activityclase que lo contiene . Esto se debe a la referencia implícita a la clase que lo contiene. Esto evitará que la actividad se recolecte basura, por lo tanto, la pérdida de memoria.

Para resolver su problema, use la clase anidada estática en lugar de la clase anónima, local e interna o use la clase de nivel superior.

Anand
fuente
1
La solución está en la advertencia misma. Utilice una clase anidada estática o una clase de nivel superior.
Anand
3
@KeyurNimavat Creo que puedes pasar una referencia débil a tu actividad
peterchaula
42
Entonces, ¿cuál es el punto de usar AsyncTask? si es más fácil ejecutar un nuevo Thread y handler.post o view.post (para actualizar la interfaz de usuario) al final en el método de ejecución de Thread. Si AsyncTask es una clase estática o de nivel superior, entonces es difícil acceder a las variables / métodos necesarios desde ella
usuario924
8
no se proporciona un código de cómo es la forma correcta de usarlo. Intenté poner estática allí, pero aparecerán más advertencias y errores
Kasnady
19
@Anand Elimine esta respuesta para que la respuesta más útil en stackoverflow.com/a/46166223/145119 pueda estar en la parte superior.
Mithaldu
557

Cómo usar una clase AsyncTask interna estática

Para evitar fugas, puede hacer que la clase interna sea estática. El problema con eso, sin embargo, es que ya no tiene acceso a las vistas de UI de la Actividad o las variables miembro. Puede pasar una referencia al Contextpero luego corre el mismo riesgo de una pérdida de memoria. (Android no puede recolectar basura de la Actividad una vez que se cierra si la clase AsyncTask tiene una fuerte referencia a ella). La solución es hacer una referencia débil a la Actividad (o lo Contextque sea que necesite).

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

Notas

  • Hasta donde sé, este tipo de peligro de pérdida de memoria siempre ha sido cierto, pero solo comencé a ver la advertencia en Android Studio 3.0. Muchos de los AsyncTasktutoriales principales aún no se tratan (ver aquí , aquí , aquí y aquí ).
  • También seguiría un procedimiento similar si su AsyncTask fuera una clase de nivel superior. Una clase interna estática es básicamente lo mismo que una clase de nivel superior en Java.
  • Si no necesita la Actividad en sí pero aún desea el Contexto (por ejemplo, para mostrar a Toast), puede pasar una referencia al contexto de la aplicación. En este caso, el AsyncTaskconstructor se vería así:

    private WeakReference<Application> appReference;
    
    MyTask(Application context) {
        appReference = new WeakReference<>(context);
    }
  • Existen algunos argumentos para ignorar esta advertencia y simplemente usar la clase no estática. Después de todo, AsyncTask está destinado a ser de muy corta duración (un par de segundos como máximo) y, de todos modos, liberará su referencia a la Actividad cuando termine. Ver esto y esto .
  • Excelente artículo: Cómo filtrar un contexto: controladores y clases internas

Kotlin

En Kotlin simplemente no incluya la innerpalabra clave para la clase interna. Esto lo hace estático por defecto.

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
fuente
1
@ManojFrekzz, No, en realidad se puede actualizar la interfaz de usuario, haciendo uso de los débiles referencia a la actividad que fue aprobada en. Mira mi onPostExecutemétodo de nuevo en el código de seguridad. Puedes ver que actualicé la interfaz de usuario TextViewallí. Solo use activity.findViewByIdpara obtener una referencia a cualquier elemento de la interfaz de usuario que necesite actualizar.
Suragch
77
+1. ¡Esta es la mejor y más limpia solución que he visto! Solo, en caso de que desee modificar la interfaz de usuario en el método onPostExecute, también debe verificar si la actividad se está destruyendo: activity.isFinishing ()
zapotec
1
¡Nota! Al usar esta respuesta, seguí encontrándome con excepciones de puntero nulo porque la operación doInBackground era intensiva en memoria, desencadenó la recolección de basura, recolectó la referencia débil y eliminé el asinctask. Es posible que desee utilizar una SoftReference en lugar de una débil si sabe que sus operaciones en segundo plano requieren más memoria.
PGMacDesign
2
@Sunny, pasa una referencia al Fragmento en lugar de la Actividad. Sacaría el activity.isFinishing()cheque y posiblemente lo reemplazaría con un fragment.isRemoving()cheque. Sin embargo, no he trabajado mucho con fragmentos recientemente.
Suragch
1
@bashan, (1) Si la clase externa no es una Actividad, entonces en tu AsyncTaskconstructor pasas una referencia a tu clase externa. Y en doInBackground()usted puede obtener una referencia a la clase externa con MyOuterClass ref = classReference.get(). Compruebe si hay null. (2) En onPostExecute()solo está actualizando la interfaz de usuario con los resultados de la tarea en segundo plano. Es como en cualquier otro momento que actualiza la interfaz de usuario. La comprobación activity.isFinishing()es solo para asegurarse de que la actividad aún no ha comenzado a finalizar, en cuyo caso no tendría sentido actualizar la interfaz de usuario.
Suragch
23

Esta AsyncTaskclase debe ser estática o se pueden producir fugas porque

  • Cuando Activityse destruye, AsyncTask(ambos staticonon-static ) siguen ejecutándose
  • Si la clase interna es la clase non-static( AsyncTask), tendrá referencia a la clase externa ( Activity).
  • Si un objeto no tiene referencias, apúntelo, Garbage Collectedlo liberará. Si un objeto no se usa y Garbage Collected no puede liberarlo => pérdida de memoria

=> Si AsyncTaskes así non-static, Activityno se lanzará el evento se destruye => fuga

Solución para actualizar la interfaz de usuario después de hacer AsyncTask como clase estática sin fugas

1) Usar WeakReferencecomo respuesta @Suragch
2) Enviar y eliminar Activityreferencias a (desde)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
fuente
55
@Suragch su enlace indica que, si bien no se garantiza que onDestroy sea llamado, la única situación en la que no lo es es cuando el sistema mata el proceso, por lo que todos los recursos se liberan de todos modos. Entonces, no guarde aquí, pero puede hacer un lanzamiento de recursos aquí.
Angelo Fuchs
2
En el caso de uso de AsyncTask no estático, ¿por qué no podemos establecer la variable de instancia AsyncTask en NULL, similar a esto? ¿No le dirá esto a GC que libere Actividad aunque se esté ejecutando AsyncTask?
Hanif
@Hanif establecer la variable de instancia AsyncTask en NUL no ayudará, porque la tarea todavía tiene la referencia a través del oyente.
Susanta