android - marte - fragmento de memoria perdida
Pérdida de memoria en el fragmento (4)
Estoy usando la biblioteca LeakCanary para monitorear las fugas de memoria en mi aplicación. Recibí esta pérdida de memoria y no estoy seguro de cómo rastrear qué lo está causando.
05-09 09:32:14.731 28497-31220/? D/LeakCanary﹕ In com.etiennelawlor.minesweeper:0.0.21:21.
* com.etiennelawlor.minesweeper.fragments.MinesweeperFragment has leaked:
* GC ROOT com.google.android.gms.games.internal.GamesClientImpl$PopupLocationInfoBinderCallbacks.zzahO
* references com.google.android.gms.games.internal.PopupManager$PopupManagerHCMR1.zzajo
* references com.google.android.gms.games.internal.GamesClientImpl.mContext
* references com.etiennelawlor.minesweeper.activities.MinesweeperActivity.mFragments
* references android.app.FragmentManagerImpl.mAdded
* references java.util.ArrayList.array
* references array java.lang.Object[].[0]
* leaks com.etiennelawlor.minesweeper.fragments.MinesweeperFragment instance
* Reference Key: 2f367393-6dfd-4797-8d85-7ac52c431d07
* Device: LGE google Nexus 5 hammerhead
* Android Version: 5.1 API: 22
* Durations: watch=5015ms, gc=141ms, heap dump=1978ms, analysis=23484ms
Este es mi repositorio: https://github.com/lawloretienne/Minesweeper
Esto parece ser un esquivo. Configuré una Interface
para comunicarme entre un Fragment
y una Activity
. Configuré esta variable de Interface
de onAttach()
en onAttach()
luego me di cuenta de que no la estaba anulando en onDetach()
. Arreglé ese problema pero todavía tengo una pérdida de memoria. ¿Algunas ideas?
Actualizar
Deshabilité la observación de fugas de Fragment
, y aún recibo una notificación sobre la fuga de actividad con el siguiente rastreo de fugas:
05-09 17:07:33.074 12934-14824/? D/LeakCanary﹕ In com.etiennelawlor.minesweeper:0.0.21:21.
* com.etiennelawlor.minesweeper.activities.MinesweeperActivity has leaked:
* GC ROOT com.google.android.gms.games.internal.GamesClientImpl$PopupLocationInfoBinderCallbacks.zzahO
* references com.google.android.gms.games.internal.PopupManager$PopupManagerHCMR1.zzajo
* references com.google.android.gms.games.internal.GamesClientImpl.mContext
* leaks com.etiennelawlor.minesweeper.activities.MinesweeperActivity instance
* Reference Key: f4d06830-0e16-43a2-9750-7e2cb77ae24d
* Device: LGE google Nexus 5 hammerhead
* Android Version: 5.1 API: 22
* Durations: watch=5016ms, gc=164ms, heap dump=3430ms, analysis=39535ms
En mi caso, tuve el siguiente código:
googleApiClient = new GoogleApiClient.Builder(activity.getApplicationContext())
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Games.API).addScope(Games.SCOPE_GAMES)
.build();
El problema fueron las llamadas addConnectionCallbacks(this)
y addConnectionCallbacks(this)
. La clase a la que hace referencia this
era mantener una referencia a una actividad, y como GoogleApiClient no deja de lado las referencias a las devoluciones de llamada / escucha de la conexión, esto resultó en una pérdida de memoria.
Mi solución fue registrar / anular el registro de las devoluciones de llamada a medida que el googleApiClient
conecta / desconecta:
public void connect() {
mGoogleApiClient.registerConnectionCallbacks(this);
mGoogleApiClient.registerConnectionFailedListener(this);
mGoogleApiClient.connect();
}
public void disconnect() {
if (mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
mGoogleApiClient.unregisterConnectionCallbacks(this);
mGoogleApiClient.unregisterConnectionFailedListener(this);
}
}
La documentation indica que es seguro llamar a connect()
incluso si el estado es "conectado" o "conectado". También indica que puede llamar de forma segura a disconnect()
sin importar cuál sea el estado de la conexión. Por lo tanto, eliminaría las declaraciones "if" alrededor de las llamadas para connect()
y disconnect()
. Sin embargo, dudo que eso haga que esta "fuga" desaparezca.
Está claro que GamesClientImpl
está almacenando una referencia a su Activity
como Context
. Me imagino que esto está ocurriendo en la construcción del GoogleApiClient
que sucede cuando llama a GoogleApiClient.Builder.build()
. Me parece un error que la instancia de GoogleApiClient
siga existiendo después de que su Activity
haya terminado. Sin embargo, si se supone que debes llamar a connect()
en onStart()
y a disconnect()
en onStop()
eso parece dar a entender que puedes reutilizar la conexión (ya que onStart()
y onStop()
pueden llamarse repetidamente). Para que esto funcione, GoogleApiClient
debe mantener la referencia a su Context
incluso después de haber llamado a disconnect()
.
Puede intentar usar el contexto de la aplicación global en lugar del contexto de la Activity
cuando cree el GoogleApiClient
, ya que el contexto de la aplicación global perdura (hasta que GoogleApiClient
el proceso). Esto debería hacer que su "fuga" desaparezca:
// Create the Google Api Client with access to Plus and Games
mGoogleApiClient = new GoogleApiClient.Builder(getApplicationContext())
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API).addScope(Plus.SCOPE_PLUS_LOGIN)
.addApi(Games.API).addScope(Games.SCOPE_GAMES)
.build();
No debe confiar en la ejecución de devolución de llamada onDestroy()
, en algunos casos es posible que no se llame . Una solución más sólida es poner su código de registro / onResume()
registro dentro de onResume()
/ onPause()
.
Lo mismo ocurre (por otra razón, por supuesto) para Fragment''s onDetach()
, mueva su código sensible a onStop()
o onPause()
.
05-27 13:15:04.478 24415-25236/com.package D/LeakCanary﹕ In com.package:0.0.52-dev:202.
* com.package.launcher.LauncherActivity has leaked:
* GC ROOT com.google.android.gms.ads.internal.request.q.a
* references com.google.android.gms.ads.internal.request.m.d
* references com.google.android.gms.ads.internal.request.c.a
* references com.google.android.gms.ads.internal.j.b
* references com.google.android.gms.ads.internal.aa.f
* references com.google.android.gms.ads.internal.ab.mParent
* references com.google.android.gms.ads.doubleclick.PublisherAdView.mParent
* references android.widget.FrameLayout.mContext
* leaks com.package.launcher.LauncherActivity instance
* Reference Key: 9ba3c5ea-2888-4677-9cfa-ebf38444c994
* Device: LGE google Nexus 5 hammerhead
* Android Version: 5.1.1 API: 22
* Durations: watch=5128ms, gc=150ms, heap dump=5149ms, analysis=29741ms
Estaba usando la biblioteca de anuncios de GMS y hubo una fuga similar. Así que arreglé el caso anterior manejando onDestroyView () de mi fragmento.
@Override
public void onDestroyView() {
if (mAdView != null) {
ViewParent parent = mAdView.getParent();
if (parent != null && parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(mAdView);
}
}
super.onDestroyView();
}
Así que aquí básicamente estoy eliminando mi PublisherAdView de su padre en onDestroyView ().
Además, tenga en cuenta que tuve que usar el contexto de la aplicación cuando creé PublisherAdView, de lo contrario obtendría la siguiente fuga:
05-27 13:59:23.684 10041-11496/com.package D/LeakCanary﹕ In com.package:0.0.52-dev:202.
* com.package.launcher.LauncherActivity has leaked:
* GC ROOT com.google.android.gms.ads.internal.request.q.a
* references com.google.android.gms.ads.internal.request.m.b
* leaks com.package.launcher.LauncherActivity instance
* Reference Key: 5acaa61a-ea04-430a-b405-b734216e7e80
* Device: LGE google Nexus 5 hammerhead
* Android Version: 5.1.1 API: 22
* Durations: watch=7275ms, gc=138ms, heap dump=5260ms, analysis=22447ms
No estoy seguro de si va a resolver la pregunta anterior directamente, pero espero que ayude.