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




memory-leaks leakcanary (3)

Я использую библиотеку 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 и действием. Я установил эту переменную 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

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

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

Вы можете попытаться использовать глобальный контекст приложения вместо контекста Activity при создании GoogleApiClient , поскольку глобальный контекст приложения живет вечно (до тех пор, пока процесс не будет 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();

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

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

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

Я использовал библиотеку объявлений 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.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

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





leakcanary