Утечка памяти в фрагменте

Я использую библиотеку LeakCanary для мониторинга утечек памяти в моем приложении. Я получил эту утечку памяти и не уверен, как отслеживать, что вызывает ее.

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 

Это мое репо: https://github.com/lawloretienne/Minesweeper

Это кажется неуловимым. Я установил Interface для взаимодействия между Fragment и Activity . Я установил эту переменную Interface mCoordinator в onAttach() тогда я понял, что не исключал ее в onDetach() . Я исправил эту проблему, но все же получаю утечку памяти. Есть идеи?

Обновить

Я отключил наблюдение за Fragment , и я все еще получаю уведомление об активности, протекающей со следующей трассировкой утечки:

 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 

Solutions Collecting From Web of "Утечка памяти в фрагменте"

Документация указывает, что безопасно вызывать connect() даже если состояние «подключено» или «подключено». Он также указывает, что вы можете безопасно вызвать disconnect() независимо от состояния подключения. Поэтому я удалял бы инструкции if if вокруг вызовов connect() и disconnect() . Однако я сомневаюсь, что это заставит эту «утечку» уйти.

Понятно, что GamesClientImpl хранит ссылку на вашу Activity как Context . Я предполагаю, что это происходит при построении GoogleApiClient который происходит, когда вы вызываете GoogleApiClient.Builder.build() . Мне кажется, что ошибка в том, что экземпляр GoogleApiClient по-прежнему находится после завершения вашей Activity . Однако, если вы должны вызвать connect() в onStart() и disconnect() в onStop() это означает, что вы можете повторно использовать соединение (поскольку onStart() и onStop() могут быть вызваны повторно). Чтобы это сработало, GoogleApiClient должен сохранять ссылку на ваш Context даже после того, как вы вызвали disconnect() .

Вы можете попробовать использовать глобальный контекст приложения вместо контекста Activity при создании GoogleApiClient , поскольку глобальный контекст приложения живет вечно (пока процесс не будет убит). Это должно заставить вашу «утечку» уйти:

 // 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(); 
 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.qa * references com.google.android.gms.ads.internal.request.md * references com.google.android.gms.ads.internal.request.ca * references com.google.android.gms.ads.internal.jb * 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 

Я использовал gms-библиотеку объявлений, и была аналогичная утечка. Поэтому я исправил вышеупомянутый случай, обработав его onDestroyView () моего фрагмента.

 @Override public void onDestroyView() { if (mAdView != null) { ViewParent parent = mAdView.getParent(); if (parent != null && parent instanceof ViewGroup) { ((ViewGroup) parent).removeView(mAdView); } } super.onDestroyView(); } 

Поэтому я в основном удаляю свой PublisherAdView из его родителя onDestroyView ().

Кроме того, обратите внимание, что я должен был использовать контекст приложения, когда я создал PublisherAdView, иначе я получил бы следующую утечку:

 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.qa * references com.google.android.gms.ads.internal.request.mb * 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 

Не уверен, что он будет решать вопрос выше, но надеюсь, что это поможет.

Вы не должны полагаться на onDestroy() обратного вызова onDestroy() , в некоторых случаях он может не вызываться . Более твердое решение состоит в том, чтобы поместить ваш регистр / незарегистрированный код внутри onResume() / onPause() .

То же самое происходит (по другой причине, конечно) для Fragment onDetach() , переместите свой разумный код на onStop() или onPause() .

В моем случае у меня был следующий код:

 googleApiClient = new GoogleApiClient.Builder(activity.getApplicationContext()) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(Games.API).addScope(Games.SCOPE_GAMES) .build(); 

Проблема заключалась в вызовах addConnectionCallbacks(this) и addConnectionCallbacks(this) . Класс, на который ссылается this , ссылается на активность, и поскольку GoogleApiClient не отпустит ссылки на обратные вызовы / прослушиватели соединения, это привело к утечке памяти.

Моим решением было зарегистрировать / googleApiClient регистрацию обратных вызовов, поскольку googleApiClient подключается / отключается:

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