【问题标题】:Memory leak in Fragment片段中的内存泄漏
【发布时间】:2015-07-20 14:03:31
【问题描述】:

我正在使用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 之间进行通信。我在onAttach() 中设置了这个mCoordinator Interface 变量,然后我意识到我没有在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

【问题讨论】:

  • 我很想知道是否只有片段泄漏,或者活动是否也泄漏。如果您禁用片段泄漏监视,您是否仍会收到有关活动泄漏的通知?
  • 但是您是否手动尝试过使用 mat 或任何其他工具?
  • @Elltz 我没有尝试过 mat 或任何其他工具。
  • 看来您需要调用 GoogleApiClient .unregisterConnectionCallbacks 和 GoogleApiClient .unregisterConnectionFailedListener 方法,因为使用 GoogleApiClient.Builder 添加监听器。 developer.android.com/reference/com/google/android/gms/common/…
  • 考虑添加日志以查看您的 onDestroy() 方法是否被调用。如果不是,正如 Dmide 下面建议的那样,您应该将您的取消注册呼叫放入 onStop() 或 onPause()

标签: android memory-leaks leakcanary


【解决方案1】:

documentation 表示调用connect() 是安全的,即使状态是“已连接”或“正在连接”。它还表明无论连接状态如何,您都可以安全地调用disconnect()。因此,我将删除调用connect() 和disconnect() 周围的“if”语句。但是,我怀疑这会让这个“泄漏”消失。

很明显,GamesClientImpl 正在将您的Activity 的引用存储为Context。我想这发生在GoogleApiClient 的构造中,当您调用GoogleApiClient.Builder.build() 时会发生这种情况。在你的Activity 完成后,GoogleApiClient 的实例仍然存在,这对我来说似乎是一个错误。但是,如果您应该在onStart() 中调用connect() 和在onStop() 中调用disconnect(),这似乎意味着您可以重用连接(因为可以重复调用onStart() 和onStop())。为此,GoogleApiClient 必须保留对您的 Context 的引用,即使在您调用了 disconnect() 之后也是如此。

在创建GoogleApiClient 时,您可以尝试使用全局应用程序上下文而不是您的Activity 上下文,因为全局应用程序上下文永远存在(直到进程被终止)。这应该会让你的“泄漏”消失:

// 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();

【讨论】:

    【解决方案2】:
    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();
    }
    

    所以在这里我基本上是在 onDestroyView() 上从它的父级中删除我的 PublisherAdView。

    另外,请注意,我在创建 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
    

    不确定是否会直接解决上述问题,但希望对您有所帮助。

    【讨论】:

      【解决方案3】:

      您不应该依赖onDestroy() 回调执行,在某些情况下是it might not be called。更可靠的解决方案是将您的注册/注销代码放入onResume()/onPause()。

      Same goes(当然是另一个原因)对于 Fragment 的onDetach(),将您的合理代码移至onStop() 或onPause()。

      【讨论】:

      • 事实并非如此。 onDestroy 不能被调用,因为唯一的原因:进程被低内存杀手杀死。我相信您是否在进程死亡之前退订 Google Play 服务并不重要。
      • 我并不暗示这就是 OP 问题背后的确切原因,但 SDK 的这些“怪癖”是需要注意的,可能与 OP 的问题有关。
      • @Dmide 你错了,唯一的怪癖是 SDK 文档说明了什么是不正确的,请参阅commonsware.com/blog/2011/10/03/…。并且不要听 SO 带来的所有东西,例如这是废话stackoverflow.com/questions/18361719/…。
      【解决方案4】:

      就我而言,我有以下代码:

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

      【讨论】:

        猜你喜欢
        • 2013-11-30
        • 2018-03-24
        • 1970-01-01
        • 2020-01-22
        • 1970-01-01
        • 2019-12-22
        • 1970-01-01
        • 1970-01-01
        • 2020-02-15
        相关资源
        最近更新 更多