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 constructorAsyncTask
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
(tantostatic
comonon-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 yGarbage 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);
}
}
}