android android-asynctask android-runonuithread

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



android-asynctask android-runonuithread (3)

Recibo una advertencia en mi código que dice:

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

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


Cómo usar una clase AsyncTask interna estática

Para evitar fugas, puede hacer que la clase interna sea estática. Sin embargo, el problema con eso es que ya no tiene acceso a las vistas de la IU de la Actividad o las variables miembro. Puede pasar una referencia al Context pero luego corre el mismo riesgo de una pérdida de memoria. (Android no puede recolectar basura de la Actividad después de que se cierre si la clase AsyncTask tiene una fuerte referencia a ella). La solución es hacer una referencia débil a la Actividad (o cualquier Context 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 principales tutoriales de AsyncTask aún no se tratan (ver here , here , here y here ).
  • 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 un Toast ), puede pasar una referencia al contexto de la aplicación. En este caso, el constructor AsyncTask 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 muy breve (un par de segundos como máximo), y liberará su referencia a la Actividad cuando finalice de todos modos. Mira this y this .
  • Excelente artículo: androiddesignpatterns.com/2013/01/…

Kotlin

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

Todavía no soy muy bueno en Kotlin, así que corrige mi código a continuación si se puede mejorar:

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


Las clases internas no estáticas contienen una referencia a la clase que lo contiene. Cuando declara AsyncTask como una clase interna, puede AsyncTask más que la clase de Activity 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 una clase anidada estática en lugar de una clase anónima, local e interna o use la clase de nivel superior.


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

  • Cuando se destruye la actividad, AsyncTask (tanto static como non-static ) sigue ejecutándose
  • Si la clase interna es una clase non-static ( AsyncTask ), tendrá referencia a la clase externa ( Activity ).
  • Si un objeto no tiene referencias, Garbage Collected lo liberará. Si un objeto no se utiliza y Garbage Collected no puede liberarlo => pérdida de memoria

=> Si AsyncTask non-static es non-static , la Activity no liberará 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) Use WeakReference como respuesta @Suragch
2) Enviar y eliminar la referencia de Activity 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); } } }