Éste artículo es una traducción del blog de Alex Lockwood. Gracias por el permiso.

Seamos directos, consideremos el siguiente código:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }
}

Aunque no es obvio de inmediato, éste código puede causar fugas de memoria (memory leak).



Android Lint mostrará la siguiente advertencia:

In Android, Handler classes should be static or leaks might occur.

Es decir, las clases de tipo Handler deben ser estáticas, de lo contrario pueden ocurrir fugas de memoria (memory leak).

Algunos conceptos a entender

Pero, ¿dónde ocurre ésta pérdida o filtración de memoria (memory leak) y cómo se produce? Determinemos el origen del problema fijándonos en lo que sabemos:

¿Dónde se produce la fuga de memoria? (memory leak)

Entonces, ¿Dónte está exáctamente la pérdida de memoria (memory leak)?, es muy sutil, pero consideremos ahora el siguiente fragmento de código:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Publicar un mensaje y retrasar su ejecución 10 minutos
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { }
    }, 60 * 10 * 1000);

    // Regresar a la activity anterior
    finish();
  }
}

Cuando la actividad finaliza al llamar a finish, el mensaje que hemos retrasado seguirá pendiente de ejecución en la cola de mensajes del hilo principal durante 10 minutos antes de ser procesado. El mensaje mantiene una referencia al Handler del Activity, y el Handler mantiene una referencia implícita a su clase externa (SampleActivity, en éste caso). Dicha referencia persistirá hasta que el mensaje sea procesado, lo cual impide al contexto de la Activity ser recolectado por el recolector de basura (Garbage collector). Ésto causa la perdida de los recursos de la aplicación. Nótese que ocurre lo mismo con la clase anónima Runnable mostrada en el código. Instancias no estáticas de clases anónimas mantienen una referencia implícita a su clase externa, causando así pérdida o filtración del contexto (Clase Context), y por tanto, un (memory leak).

Posibles soluciones para evitar la fuga

Para corregir el problema, podemos crear una subclase de Handler en un nuevo fichero o crear una clase interna estática. Las clases estáticas internas no mantienen una referencia implícita a su clase externa, de modo que la Activity no tendrá fugas de memoria (memory leak). Si se necesita invocar métodos de la clase externa desde el Handler, basta con que el Handler mantenga una referencia débil (WeakReference) a la Activity, así no habrá fugas de memoria accidentales. Para corregir la otra fuga existente al instanciar la clase anónima Runnable, basta con crear una variable estática de la clase (Ya que, como hemos dicho, instancias estáticas de clases anónimas no mantienen una referencia implícita a su clase externa):

public class SampleActivity extends Activity {

  /**
  * Clase interna estática
  */
  private static class MyHandler extends Handler {
    private final WeakReference<sampleactivity> mActivity;

    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference</sampleactivity><sampleactivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }

  private final MyHandler mHandler = new MyHandler(this);

  /**
  * Instancia de clase anónima estática, no mantiene referencia
  */
  private static final Runnable sRunnable = new Runnable() {
    @Override
    public void run() { }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Programar mensaje para 10 min
    mHandler.postDelayed(sRunnable, 60 * 10 * 1000);

    // Salir a la activity anterior.
    finish();
  }
}

Conclusión

La diferencia entre clases internas estáticas y no-estáticas es sutil, pero es una sutileza que todo desarrollador Android debería comprender. ¿Cual es la conclusión? Evitar a toda costa usar clases internas no estáticas en una Activity si las instancias de la clase interna pueden seguir ejecutándose aún cuando el ciclo de vida de la Activity acabe. En su lugar, usar clases internas estáticas que mantengan una referencia débil a la Activity.

Referencias

How to Leak a Context: Handlers & Inner Classes »» androiddesignpatterns.com

photo credit: nyuhuhuu via photopin cc

Índice

</sampleactivity>