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

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

person toobsco42    schedule 09.05.2015    source источник
comment
Мне было бы любопытно узнать, утекают ли только фрагменты, или утечка активности тоже. Что, если вы отключите отслеживание утечки фрагментов, вы все равно получите уведомление об утечке активности?   -  person Pierre-Yves Ricau    schedule 09.05.2015
comment
но пробовали ли вы вручную использовать коврик или какой-либо другой инструмент?   -  person Elltz    schedule 09.05.2015
comment
@Elltz Я не пробовал мат или какой-либо другой инструмент.   -  person toobsco42    schedule 10.05.2015
comment
Похоже, вам нужно вызвать методы GoogleApiClient .unregisterConnectionCallbacks и GoogleApiClient .unregisterConnectionFailedListener из-за добавления слушателя с GoogleApiClient.Builder. developer.android.com/reference/com/google/android/gms/common/.   -  person baroqueworksdev    schedule 10.05.2015
comment
@baroqueworksdev Я отменил регистрацию обратного вызова и слушателя и обнуил GoogleApiClient в onDestroy(). Но у активности все еще есть та же утечка памяти.   -  person toobsco42    schedule 11.05.2015
comment
Можете ли вы предоставить ссылку на дамп кучи после этого обновления (где происходит утечка активности)? \   -  person Pierre-Yves Ricau    schedule 12.05.2015
comment
Когда я нажимаю Share heap dump в дополнительном меню, любое приложение, в котором я пытаюсь поделиться им, терпит неудачу. Например, в GMail написано Permission denied for attachment.   -  person toobsco42    schedule 12.05.2015
comment
Подумайте о добавлении ведения журнала, чтобы узнать, вызывается ли ваш onDestroy() метод. Если это не так, как предлагает Дмид ниже, вы должны поместить свои отмененные вызовы в onStop() или onPause()   -  person TBridges42    schedule 12.05.2015
comment
У меня точно такая же проблема. Я отменил регистрацию обратных вызовов в функции onStop действия. Но у меня все еще есть проблема. Пожалуйста, поделитесь им, если вы его решили. Спасибо.   -  person tasomaniac    schedule 02.08.2015
comment
Подтверждено, что прослушиватель местоположения v8.1.0 службы Google Play пропускает активность или службу, к которой он привязан github.com/googlesamples/android-play-location/issues/26, может быть, это похоже?   -  person Marian Paździoch    schedule 29.10.2015


Ответы (4)


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

// 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();
person David Wasser    schedule 15.05.2015

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

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

person Pirdad Sakhizada    schedule 27.05.2015

Вы не должны полагаться на выполнение обратного вызова onDestroy(), в некоторых случаях это может не называться. Более надежное решение - поместить код регистрации / отмены регистрации внутри _2 _ / _ 3_.

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

person Dmide    schedule 12.05.2015
comment
Это не тот случай. onDestroy не может быть вызван, потому что единственная причина: процесс был убит из-за нехватки памяти. Я считаю, что не имеет значения, отказались ли вы от подписки на Сервисы Google Play до смерти процесса. - person pepyakin; 13.05.2015
comment
Я не подразумеваю, что это именно причина проблемы OP, но эти особенности SDK - это то, о чем следует знать, и, возможно, это может быть связано с проблемой OP. - person Dmide; 17.05.2015
comment
@Dmide Вы ошибаетесь, единственная причуда - это документация SDK, в которой говорится, что не соответствует действительности, см. commonsware.com/blog/2011/10/03/. И не слушайте все, что приносит SO, например это фигня stackoverflow.com/questions/18361719/. - person Marian Paździoch; 29.10.2015

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

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 подключается / отключается:

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);
    }
}
person Mateus Gondim    schedule 19.01.2017