AdView导致内存泄漏

19

我正在使用AdView和LeakCanary。托管adView的Fragment在onDestroy中调用adView.destroy(),但LeakCanary显示托管此Fragment的Activity泄漏了com.google.android.gms.common.api.a.a.a.i。堆转储还显示有内存泄漏。当我删除AdView.loadAd()并且不在Fragment中加载广告时-就没有泄漏了。有什么想法或建议吗?谢谢。

5个回答

15

同样的问题,AdView有一个内部变量(强引用)持有上下文,也就是我的Activity,导致Activity实例泄漏。

我的依赖项是com.google.android.gms:play-services-ads:8.3.0

一个解决方法是在创建AdView实例时提供应用程序上下文。

AdView adview = new AdView(getApplicationContext());

3
但是如果我们在XML中使用adView,则无法完成此操作。我尝试在onDestroy中销毁adView,但没有帮助。 - Kshitij Aggarwal
@Funkyidol,你的布局填充器很可能将活动作为上下文提供给了布局XML中的AdView。编程方式向viewgroup添加视图并不那么可怕。 - dvd
这是唯一修复它的方法。遗憾的是,现在我无法将其添加到XML布局中... - eliasbagley
我在我的片段的onStop()方法中以编程方式添加AdView,并从父视图中删除它,就像这里提到的那样: https://groups.google.com/forum/#!topic/google-admob-ads-sdk/DnICRC67ASI 我还使用应用程序上下文来创建AdView实例。 - Camino2007
如果我们正确传递了applicationContext,泄漏就会消失。 在传递上下文时,我们必须观察从那里传递上下文的生命周期,否则applicationContext将在引起泄漏之前被销毁。无需处理AdView。 - AllanRibas
显示剩余2条评论

4

我认为将应用上下文传递给AdView并不是真正的解决方案。因为问题在于AdView没有释放Context对象。因此,如果您传递它,它将不会释放App上下文。

因此,以下可作为防止泄漏的解决方法。

@Override
protected void onDestroyView() {
    super.onDestroy();
    if (adview != null && adview.getParent() != null) // inflated by XML and remove here from parent
        ((ViewGroup) adview.getParent()).removeView(adview);
    adview.destroy(); 
    adview = null;
}

1. 在onDestroyView中销毁Adview

  • Fragment的生命周期有一个onDestroyView方法,在视图被销毁时调用,因此您应该确切地在这个位置销毁AdView。
  • 在您的情况下,您是在onDestroy在onDestroyView之后)销毁AdView,所以这是一个泄漏。因为AdView仍然存在于Fragment View被销毁之后。
  • Activity没有onDestroyView方法,在Activity中,视图在onDestroy中被销毁。所以我们在onDestroy中清除对象。

2. 以编程方式从视图中删除AdView。

但如果我们在XML中使用adView,则无法完成此操作。

因为您希望从XML中填充AdView,所以在onDestroy中删除View将为您完成工作。

3. 在onDestroyView中使AdView为NULL

onDestroy中将AdView设置为null。因此,AdView对象将不再被引用,并且将由垃圾收集器清理。

我希望这些信息对您有用。 :)


1
谢谢你的回答。我尝试了来自Google的最新广告SDK并应用了你的代码,但仍然有泄漏。LeakCanary显示包含adView的活动被com.google.android.gms.internal.ads.zzml持有。Google真丢人,从第一个广告SDK版本发布到现在已经9年了,但他们仍未解决此问题。 - Son Truong
你试过我建议的三个选项了吗? - Khemraj Sharma
我已经尽力了,现在只有 Google 能解决这个问题 :), 也许他们在应用程序层面上保留了一些值。 - Khemraj Sharma
1
2020年6月更新:每次使用AdView时,它仍然会生成内存泄漏。 即使在最简单的活动中(在Activity OnDestroy()中执行adview.destroy()后),我也能看到它。我的最后一个测试用例是在Android 9(Pie)上的BluView1上执行的。 - Pablo Alfonso
更新自2023年.. 这个解决方案很好,我尝试了几次,一开始没有任何泄漏,但之后又出现了泄漏.. 无论广告是否加载,都会泄漏。 - China fox

2

您可以尝试以下操作:

  • move your logic in onDestroyView()
  • first remove your adView from its container and then call destroy(), i.e.

    ViewParent parent = adView.getParent();
    if (parent != null && parent instanceof ViewGroup) {
      ((ViewGroup) parent).removeView(adView);
    }
    
    adView.destroy();
    adView = null;
    

2
很不幸,仍然存在内存泄漏问题。看起来adView在持有Activity的引用。 - Alex Perevozchykov
我从来没有在使用AdView时遇到过任何内存泄漏问题,而且我们正在全面使用DFP。你能确保你正在使用最新的Play服务吗?我相信这是其他问题,但如果没有看到一些源代码,我无法确定。 - Dimitar Genov
你使用过LeakCanary或堆转储测试应用程序吗?我正在使用最新的8.1.0 Play服务。 - Alex Perevozchykov
1
是的,我使用LeakCanary。不过这里可能有几个(潜在的)区别:1)我们使用DFP,即com.google.android.gms.ads.doubleclick.PublisherAdView;2)我没有在广告中使用片段,而是直接添加和处理活动视图树。 - Dimitar Genov
我有同样的问题,你解决了吗? - Omar Hassan

1
在我的情况下,这是由于在 lambda 作用域中使用 MobileAds 初始化代码时出现了 this。将 this 更改为 applicationContext 后,问题得到解决。

更改前:

MobileAds.initialize(this, "ca-app-pub-0000000000000000~0000000000")

之后:

MobileAds.initialize(applicationContext, "ca-app-pub-0000000000000000~0000000000")

1

我尝试了stackoverflow论坛和所有其他Google论坛中讨论的所有解决方案。内存泄漏似乎是不稳定的:如果用户在显示广告之前离开活动,则出现内存泄漏的机会更高。

唯一有效的方法是将内存泄漏限制在一个单独的活动中...

第一个建议: 不要直接将adview添加到XML布局文件中。 如果按照官方文档(https://developers.google.com/admob/android/banner)中的说明操作,那么一定会导致内存泄漏。 相反,请以编程方式添加adview:

从XML文件中删除adview:
``` <!-- REMOVE IT --> <!-- <com.google.android.gms.ads.AdView android:id="@+id/adView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_alignParentBottom="true" ads:adSize="BANNER" ads:adUnitId="@string/banner_ad_unit_id"> </com.google.android.gms.ads.AdView> --> ```
然后,在mAdView旁边的活动中定义一个RelativeLayout变量(adscontainer):
``` private AdView mAdView; private RelativeLayout adscontainer; ```
在OnCreate中,删除旧的mAdView赋值,并使用以下内容替换它:
``` adscontainer = findViewById(R.id.RLadViewContainer); mAdView = new AdView(MainActivity.MemoryLeakContainerActivity); mAdView.setAdSize(AdSize.BANNER); mAdView.setAdUnitId(getResources().getString(R.string.banner_ad_unit_id)); adscontainer.addView(mAdView); RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mAdView.getLayoutParams(); lp.addRule(RelativeLayout.CENTER_HORIZONTAL); lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); mAdView.setLayoutParams(lp); RequestConfiguration requestConfiguration = new RequestConfiguration.Builder() .setTestDeviceIds(Constants.testDevices) .build(); MobileAds.setRequestConfiguration(requestConfiguration); AdRequest adRequest = new AdRequest.Builder().build(); mAdView.loadAd(adRequest); ```
在onDestroy()中,添加以下内容:
``` if (mAdView != null){ mAdView.setAdListener(null); adscontainer.removeAllViews(); adscontainer = null; mAdView.destroy(); mAdView = null; } ```
现在,让我们谈谈“MainActivity.MemoryLeakContainerActivity”:
是的,您必须牺牲其中一个活动以包含内存泄漏。我没有找到其他方法。我在这里尝试了“getApplicationContext()”,但它不起作用。所以我选择了我的MainActivity有两个原因:
a- 在我的应用程序中,MainActivity作为一个带有三个按钮(“Play”,“Review Decks”和“Create Decks”)的简单菜单工作。它不占用太多内存,没有必要销毁它,因为在我的应用程序中大多数onBackPressed()任务都会将流程导向菜单,它将永远留在内存中。
b- 这是加载的第一个Activity。因此,adview将始终准备好并可用于其他活动。
在MainActivity(或您选择包含内存泄漏的活动)中,在底部添加以下内容:
``` public static MainActivity MemoryLeakContainerActivity; public MainActivity() { super(); if (MemoryLeakContainerActivity != null) { throw new IllegalStateException("MemoryLeakContainerActivity is already created"); } MemoryLeakContainerActivity = this; } ```
就这样!我的应用程序中没有更多的内存泄漏!LeakCanary报告是干净的。

注意:这是一个解决方法,而不是解决方案。解决方案应该只来自谷歌:像“mAdView.destroy()”这样的简单命令就可以解决问题,对吧?

此问题正在Google论坛上讨论:https://groups.google.com/forum/#!topic/google-admob-ads-sdk/9IyjqdmeumM


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接