Android
Pérdidas de memoria
Buscar..
Fugas de memoria comunes y cómo solucionarlos.
1. Arregla tus contextos:
Intente usar el contexto apropiado: por ejemplo, dado que se puede ver un Toast en muchas actividades en lugar de en solo una, use getApplicationContext()
para getApplicationContext()
, y como los servicios pueden seguir ejecutándose aunque una actividad haya terminado, inicie un servicio con:
Intent myService = new Intent(getApplicationContext(), MyService.class);
Use esta tabla como una guía rápida para el contexto apropiado:
Artículo original sobre contexto aquí .
2. Referencia estática al contexto.
Un error grave de pérdida de memoria es mantener una referencia estática a View
. Cada View
tiene una referencia interna al Context
. Lo que significa que una actividad antigua con toda su jerarquía de vistas no se recogerá como basura hasta que la aplicación finalice. Tendrá su aplicación dos veces en la memoria cuando gire la pantalla.
Asegúrese de que no haya ninguna referencia estática a Vista, Contexto o cualquiera de sus descendientes.
3. Comprueba que realmente estás terminando tus servicios.
Por ejemplo, tengo un intentService que utiliza la API del servicio de ubicación de Google. Y me olvidé de llamar a googleApiClient.disconnect();
:
//Disconnect from API onDestroy()
if (googleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, GoogleLocationService.this);
googleApiClient.disconnect();
}
4. Comprobar el uso de la imagen y de los mapas de bits:
Si está utilizando la biblioteca de Square, Picasso , descubrí que estaba perdiendo memoria al no usar .fit()
, lo que redujo drásticamente mi huella de memoria de 50 MB en promedio a menos de 19 MB:
Picasso.with(ActivityExample.this) //Activity context
.load(object.getImageUrl())
.fit() //This avoided the OutOfMemoryError
.centerCrop() //makes image to not stretch
.into(imageView);
5. Si está utilizando receptores de difusión, anúltelos.
6. Si está utilizando java.util.Observer
(patrón de observador):
Asegúrese de utilizar deleteObserver(observer);
Evite las actividades con fugas con AsyncTask
Una advertencia : AsyncTask tiene muchos errores aparte de la pérdida de memoria que se describe aquí. Así que tenga cuidado con esta API, o evítela por completo si no comprende completamente las implicaciones. Hay muchas alternativas (Thread, EventBus, RxAndroid, etc).
Un error común con AsyncTask
es capturar una referencia fuerte a la Activity
del host (o Fragment
):
class MyActivity extends Activity {
private AsyncTask<Void, Void, Void> myTask = new AsyncTask<Void, Void, Void>() {
// Don't do this! Inner classes implicitly keep a pointer to their
// parent, which in this case is the Activity!
}
}
Esto es un problema porque AsyncTask
puede sobrevivir fácilmente a la Activity
principal, por ejemplo, si ocurre un cambio de configuración mientras se ejecuta la tarea.
La forma correcta de hacer esto es convertir su tarea en una clase static
, que no capture al padre, y manteniendo una referencia débil a la Activity
host:
class MyActivity extends Activity {
static class MyTask extends AsyncTask<Void, Void, Void> {
// Weak references will still allow the Activity to be garbage-collected
private final WeakReference<MyActivity> weakActivity;
MyTask(MyActivity myActivity) {
this.weakActivity = new WeakReference<>(myActivity);
}
@Override
public Void doInBackground(Void... params) {
// do async stuff here
}
@Override
public void onPostExecute(Void result) {
// Re-acquire a strong reference to the activity, and verify
// that it still exists and is active.
MyActivity activity = weakActivity.get();
if (activity == null
|| activity.isFinishing()
|| activity.isDestroyed()) {
// activity is no longer valid, don't do anything!
return;
}
// The activity is still valid, do main-thread stuff here
}
}
}
Callback anónimo en actividades
Cada vez que creas una clase anónima, conserva una referencia implícita a su clase principal. Así que cuando escribes:
public class LeakyActivity extends Activity
{
...
foo.registerCallback(new BarCallback()
{
@Override
public void onBar()
{
// do something
}
});
}
De hecho, está enviando una referencia a su instancia de LeakyActivity a foo. Cuando el usuario navega fuera de su LeakyActivity, esta referencia puede evitar que la instancia de LeakyActivity se recoja. Esta es una fuga grave ya que las actividades contienen una referencia a toda su jerarquía de vistas y, por lo tanto, son objetos bastante grandes en la memoria.
Cómo evitar esta fuga:
Por supuesto, puede evitar el uso de devoluciones de llamada anónimas en actividades por completo. También puede anular el registro de todas sus devoluciones de llamada con respecto al ciclo de vida de la actividad. al igual que:
public class NonLeakyActivity extends Activity
{
private final BarCallback mBarCallback = new BarCallback()
{
@Override
public void onBar()
{
// do something
}
});
@Override
protected void onResume()
{
super.onResume();
foo.registerCallback(mBarCallback);
}
@Override
protected void onPause()
{
super.onPause();
foo.unregisterCallback(mBarCallback);
}
}
Contexto de actividad en clases estáticas
A menudo querrá envolver algunas clases de Android en clases de utilidad más fáciles de usar. Esas clases de utilidad a menudo requieren un contexto para acceder al sistema operativo Android o los recursos de sus aplicaciones. Un ejemplo común de esto es un contenedor para la clase SharedPreferences. Para acceder a las preferencias compartidas de los androides se debe escribir:
context.getSharedPreferences(prefsName, mode);
Y así uno puede estar tentado de crear la siguiente clase:
public class LeakySharedPrefsWrapper
{
private static Context sContext;
public static void init(Context context)
{
sContext = context;
}
public int getInt(String name,int defValue)
{
return sContext.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
}
}
Ahora, si llama a init()
con su contexto de actividad, el LeakySharedPrefsWrapper conservará una referencia a su actividad, evitando que se recoja basura.
Como evitar:
Al llamar a las funciones de ayuda estática, puede enviar el contexto de la aplicación utilizando context.getApplicationContext();
Al crear funciones auxiliares estáticas, puede extraer el contexto de la aplicación del contexto que se le da (Al llamar a getApplicationContext () en el contexto de la aplicación, se devuelve el contexto de la aplicación). Así que la solución a nuestra envoltura es simple:
public static void init(Context context)
{
sContext = context.getApplicationContext();
}
Si el contexto de la aplicación no es apropiado para su caso de uso, puede incluir un parámetro de contexto en cada función de utilidad, debe evitar mantener referencias a estos parámetros de contexto. En este caso la solución se vería así:
public int getInt(Context context,String name,int defValue)
{
// do not keep a reference of context to avoid potential leaks.
return context.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
}
Detecta pérdidas de memoria con la biblioteca LeakCanary
LeakCanary es una biblioteca de código abierto de Java para detectar pérdidas de memoria en sus construcciones de depuración.
Solo agrega las dependencias en el build.gradle
:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
Luego en tu clase de Application
:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
Ahora LeakCanary mostrará automáticamente una notificación cuando se detecte una pérdida de memoria de actividad en su compilación de depuración .
NOTA: El código de publicación no contendrá ninguna referencia a LeakCanary más que las dos clases vacías que existen en la leakcanary-android-no-op
.
Evite las actividades de filtración con oyentes
Si implementa o crea un escucha en una actividad, siempre preste atención al ciclo de vida del objeto que tiene el escucha registrado.
Considere una aplicación en la que tengamos varias actividades / fragmentos diferentes interesados en cuando un usuario está conectado o desconectado. Una forma de hacer esto sería tener una instancia singleton de un UserController
que se pueda suscribir para recibir una notificación cuando cambie el estado del usuario:
public class UserController {
private static UserController instance;
private List<StateListener> listeners;
public static synchronized UserController getInstance() {
if (instance == null) {
instance = new UserController();
}
return instance;
}
private UserController() {
// Init
}
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void logout() {
for (StateListener listener : listeners) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listeners) {
listener.userLoggedIn();
}
}
public interface StateListener {
void userLoggedIn();
void userLoggedOut();
}
}
Luego hay dos actividades, SignInActivity
:
public class SignInActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
startMainActivity();
}
@Override
public void userLoggedOut() {
showLoginForm();
}
...
public void onLoginClicked(View v) {
userController.login();
}
}
Y MainActivity
:
public class MainActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
...
public void onLogoutClicked(View v) {
userController.logout();
}
}
Lo que sucede con este ejemplo es que cada vez que el usuario inicia sesión y luego vuelve a MainActivity
sesión, se MainActivity
una instancia de MainActivity
. La fuga se produce porque hay una referencia a la actividad en los UserController#listeners
.
Tenga en cuenta: incluso si utilizamos una clase interna anónima como un oyente, la actividad todavía se filtraría:
...
this.userController.registerUserStateChangeListener(new UserController.StateListener() {
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
});
...
La actividad todavía se filtraría, porque la clase interna anónima tiene una referencia implícita a la clase externa (en este caso, la actividad). Es por esto que es posible llamar a los métodos de instancia en la clase externa desde la clase interna. De hecho, el único tipo de clases internas que no tienen una referencia a la clase externa son las clases internas estáticas .
En resumen, todas las instancias de clases internas no estáticas contienen una referencia implícita a la instancia de la clase externa que las creó.
Hay dos enfoques principales para resolver esto, ya sea agregando un método para eliminar un oyente de los auditores de UserController#listeners
o usando una WeakReference
para mantener la referencia de los oyentes.
Alternativa 1: Eliminar oyentes
Comencemos por crear un nuevo método removeUserStateChangeListener(StateListener listener)
:
public class UserController {
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void removeUserStateChangeListener(StateListener listener) {
listeners.remove(listener);
}
...
}
Entonces llamemos a este método en el método onDestroy
la actividad:
public class MainActivity extends Activity implements UserController.StateListener{
...
@Override
protected void onDestroy() {
super.onDestroy();
userController.removeUserStateChangeListener(this);
}
}
Con esta modificación, las instancias de MainActivity
ya no se filtran cuando el usuario inicia y MainActivity
sesión. Sin embargo, si la documentación no está clara, es probable que el próximo desarrollador que comience a usar UserController
se pierda la obligación de anular el registro del oyente cuando se destruye la actividad, lo que nos lleva al segundo método de evitar este tipo de fugas.
Alternativa 2: Usar referencias débiles
En primer lugar, comencemos por explicar qué es una referencia débil. Una referencia débil, como su nombre indica, contiene una referencia débil a un objeto. En comparación con un campo de instancia normal, que es una referencia fuerte, una referencia débil no impide que el recolector de basura, GC, elimine los objetos. En el ejemplo anterior, esto permitiría que MainActivity
se recoja después de que se haya destruido si el UserController
usó WeakReference
para hacer referencia a los oyentes.
En resumen, una referencia débil le dice al GC que si nadie más tiene una referencia fuerte a este objeto, siga adelante y elimínelo.
Permítanos modificar el UserController
para usar una lista de WeakReference
para hacer un seguimiento de sus oyentes:
public class UserController {
...
private List<WeakReference<StateListener>> listeners;
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(new WeakReference<>(listener));
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
WeakReference referencesToRemove = null;
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null && listener == listenerToRemove) {
referencesToRemove = listenerRef;
break;
}
}
listeners.remove(referencesToRemove);
}
public void logout() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedOut();
} else {
referencesToRemove.add(listenerRef);
}
}
}
public void login() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedIn();
} else {
referencesToRemove.add(listenerRef);
}
}
}
...
}
Con esta modificación, no importa si se eliminan o no los escuchas, ya que UserController
no tiene referencias sólidas a ninguno de los oyentes. Sin embargo, escribir este código repetitivo cada vez es engorroso. Por lo tanto, vamos a crear una clase genérica llamada WeakCollection
:
public class WeakCollection<T> {
private LinkedList<WeakReference<T>> list;
public WeakCollection() {
this.list = new LinkedList<>();
}
public void put(T item){
//Make sure that we don't re add an item if we already have the reference.
List<T> currentList = get();
for(T oldItem : currentList){
if(item == oldItem){
return;
}
}
list.add(new WeakReference<T>(item));
}
public List<T> get() {
List<T> ret = new ArrayList<>(list.size());
List<WeakReference<T>> itemsToRemove = new LinkedList<>();
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == null) {
itemsToRemove.add(ref);
} else {
ret.add(item);
}
}
for (WeakReference ref : itemsToRemove) {
this.list.remove(ref);
}
return ret;
}
public void remove(T listener) {
WeakReference<T> refToRemove = null;
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == listener) {
refToRemove = ref;
}
}
if(refToRemove != null){
list.remove(refToRemove);
}
}
}
Ahora volvamos a escribir UserController
para utilizar WeakCollection<T>
lugar:
public class UserController {
...
private WeakCollection<StateListener> listenerRefs;
...
public void registerUserStateChangeListener(StateListener listener) {
listenerRefs.put(listener);
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
listenerRefs.remove(listenerToRemove);
}
public void logout() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedIn();
}
}
...
}
Como se muestra en el ejemplo de código anterior, WeakCollection<T>
elimina todo el código de WeakReference
necesario para usar WeakReference
lugar de una lista normal. Para colmo: si se pierde una llamada a UserController#removeUserStateChangeListener(StateListener)
, el oyente y todos los objetos a los que hace referencia no se perderán.
Evite las pérdidas de memoria con la clase anónima, el controlador, la tarea del temporizador y el hilo
En Android, todos los desarrolladores utilizan Anonymous Class
(Runnable) al menos una vez en un proyecto. Cualquier Anonymous Class
tiene una referencia a su padre (actividad). Si realizamos una tarea de larga duración, la actividad principal no se destruirá hasta que la tarea finalice.
El ejemplo usa el controlador y la clase Runnable
Anónimo. La memoria se perderá cuando abandonemos la actividad antes de que Runnable
.
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// do abc long 5s or so
}
}, 10000); // run "do abc" after 10s. It same as timer, thread...
¿Como lo resolvemos?
- No hagas operaciones largas con la
Anonymous Class
o necesitamos unaStatic class
para ello y le pasamosWeakReference
(como actividad, vista ...).Thread
es el mismo con laAnonymous Class
. - Cancelar el
Handler
,Timer
cuando se destruye la actividad.