Admob 内存泄漏 - 通过使用空活动来避免

Posted

技术标签:

【中文标题】Admob 内存泄漏 - 通过使用空活动来避免【英文标题】:Admob Memory Leak - avoiding by using empty activity 【发布时间】:2012-03-22 10:56:59 【问题描述】:

我们的应用受到内存泄漏的严重影响。我发现根本原因是 AdMob AdView 保留对旧活动的引用。这个问题在问题android AdMob causes memory leak? 和 cmets/answers 中的子链接中有很好的记录。我注意到这个问题在 ICS 中并不明显,因为 GC 最终会使用对活动的引用来清理 WebView。但是,我的 HTC EVO 3D running stockgingerbread 从不收集活动,考虑到由于 OOM 错误导致的强制关闭报告的数量,这个问题对于我们的应用来说非常普遍。

我想按照 TacB0sS 提供的解决方案, https://***.com/a/8364820/684893。他建议创建一个空活动并为每个 AdMob AdView 使用相同的活动。泄漏将得到控制,因为 AdView 只会保持一个空活动。他提供了活动本身的代码以及如何引用它,但我不知道如何将它实际集成到我们的应用程序中。据我所知,他的代码从未从 AdMob SDK 调用任何东西。

我们目前在 XML 布局中使用 AdView,因此我们不会在代码中动态地对广告执行任何操作,例如调用 loadAd()。我们所有的广告布局都依赖于 XML 中的广告,因为它们是相对于它进行布局的。因此,我的两个问题是,如何实现 TacB0sS 代码以及如果我们必须切换到在代码中创建 XML 布局,我如何保留我的 XML 布局关系?

3/6 更新:

感谢 Adam (TacB0sS) 的回复!切换到在代码中创建广告没有问题,但在创建广告时使用您的虚拟活动仍然有困难。我目前的代码是:

AdMobActivity adActivity = new AdMobActivity();
adActivity.startAdMobActivity(this);

// Create an ad with the activity reference pointing to dummy activity
AdView adView = new AdView(adActivity.AdMobMemoryLeakWorkAroundActivity, AdSize.IAB_BANNER, "myAdUnitID");

// Create an ad request.
AdRequest adRequest = new AdRequest();

// add the ad to the layout and request it to be filled
RelativeLayout root_main = (RelativeLayout) findViewById(R.id.root_main);
root_main.addView(adView);
adView.loadAd(adRequest);

我已将此代码放在我初始活动的 onCreate 方法中。我在创建 AdView 的行“AdView adView = new AdView(...)”上得到了强制关闭。 Stacktrace sn-p:

03-06 00:34:28.098 E/AndroidRuntime(16602): java.lang.RuntimeException: Unable to start activity ComponentInfoorg.udroid.wordgame/org.udroid.wordgame.MainMenu: java.lang.NullPointerException
03-06 00:34:28.098 E/AndroidRuntime(16602):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1830)
(...)
03-06 00:34:28.098 E/AndroidRuntime(16602): Caused by: java.lang.NullPointerException
03-06 00:34:28.098 E/AndroidRuntime(16602):     at android.content.ContextWrapper.getApplicationContext(ContextWrapper.java:100)
03-06 00:34:28.098 E/AndroidRuntime(16602):     at com.google.ads.AdView.<init>(SourceFile:78)
03-06 00:34:28.098 E/AndroidRuntime(16602):     at org.udroid.wordgame.MainMenu.onCreate**(MainMenu.java:71)**  <- Line that creates the new AdView

初始化 AdMobActivity 并在创建 AdView 时引用它的正确方法是什么?再次感谢!

2 3/6 更新:

我发现了创建活动的问题。我已经完全实现了您的解决方案,最好的部分是它实际上解决了我的内存泄漏问题。在这个问题上花了两个星期之后,我很高兴它得到了解决。以下是我使用的完整步骤:

创建一个名为 AdMobActivity 的新活动:

public final class AdMobActivity extends Activity 

    public static AdMobActivity AdMobMemoryLeakWorkAroundActivity;

    public AdMobActivity() 
        super();
        if (AdMobMemoryLeakWorkAroundActivity != null) 
            throw new IllegalStateException("This activity should be created only once during the entire application life");
        
        AdMobMemoryLeakWorkAroundActivity = this;
    

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        Log.i("CHAT", "in onCreate - AdMobActivity");
        finish();
    

    public static final void startAdMobActivity(Activity activity) 
        Log.i("CHAT", "in startAdMobActivity");
        Intent i = new Intent();
        i.setComponent(new ComponentName(activity.getApplicationContext(), AdMobActivity.class));
        activity.startActivity(i);
    

将以下内容添加到您的 AndroidManifest.xml

<activity android:name="org.udroid.wordgame.AdMobActivity"
    android:launchMode="singleInstance" />

在尝试加载任何广告之前,您需要初始化虚拟 AdMobActivity。此活动不包含任何内容。它将显示片刻然后关闭,返回到您调用它的活动。您不能在要加载广告的同一活动中创建它,因为它必须在使用前及时完全初始化。在包含广告的主要活动开始之前,我在初始加载屏幕活动的 onCreate 中对其进行了初始化:

// Start the dummy admob activity.  Don't try to start it twice or an exception will be thrown
if (AdMobActivity.AdMobMemoryLeakWorkAroundActivity == null) 
    Log.i("CHAT", "starting the AdMobActivity");
    AdMobActivity.startAdMobActivity(this);

现在您可以在代码中创建广告了。将以下 LinearLayout 添加到您的 XML 活动布局中。围绕此布局对齐所有其他必要的视图。我们在代码中创建的 AdView 将被放置在这个视图中。

<LinearLayout
android:id="@+id/adviewLayout"
android:layout_
android:layout_
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />

在您要加载广告的活动中,为 AdView 创建一个全局变量:

AdView adView;

在我们的应用中,我们会在手机旋转时加载不同的布局。因此,我在每次旋转时调用以下代码。如有必要,它会创建 adView 并将其添加到 adviewLayout。

    // DYNAMICALLY CREATE AD START
    LinearLayout adviewLayout = (LinearLayout) findViewById(R.id.adviewLayout);
    // Create an ad.
    if (adView == null) 
        adView = new AdView(AdMobActivity.AdMobMemoryLeakWorkAroundActivity, AdSize.BANNER, "<ADUNITID>");
        // Create an ad request.
        AdRequest adRequest = new AdRequest();
        // Start loading the ad in the background.
        adView.loadAd(adRequest);
        // Add the AdView to the view hierarchy. The view will have no size until the ad is loaded.
        adviewLayout.addView(adView);
    
    else 
        ((LinearLayout) adView.getParent()).removeAllViews();
        adviewLayout.addView(adView);
        // Reload Ad if necessary.  Loaded ads are lost when the activity is paused.
        if (!adView.isReady() || !adView.isRefreshing()) 
            AdRequest adRequest = new AdRequest();
            // Start loading the ad in the background.
            adView.loadAd(adRequest);
        
    
    // DYNAMICALLY CREATE AD END

最后,确保在活动的 onDestroy() 方法中调用 adView.destroy():

@Override
protected void onDestroy() 
    adView.destroy();
super.onDestroy();

任何读过这篇文章的人,请记住这是亚当的解决方案 (TacB0sS),而不是我的。我只是想提供完整的实现细节,以便其他人更容易实现。这个 AdMob 错误对于运行 pre-honeycomb 的应用程序来说是一个巨大的问题,而 Adam 的解决方案是我能找到的最好的解决方法。 而且有效!

【问题讨论】:

像魅力一样工作!谢谢@ravishi 和@TacB0sS! 嗨 ravishi... 我在使用这个解决方案时遇到了问题。你能帮我么。这是我的问题***.com/questions/15583994/… 是否有完整代码@ravishi 的链接? 解决这个问题的另一种方法,即使这样做很痛苦,也就是从 sharedPreferences 或任何其他类型的存储持久性中取消持久化所需的所有数据,擦除所有您的应用程序数据,然后重新坚持一切。您可以在您的应用程序关闭时启动一项服务来执行此操作。这将防止数据由于内存泄漏而变得非常大。 2015:我不得不求助于这个来解决我的应用程序由于 admob 导致的内存问题。我的应用程序现在就像一个魅力。非常感谢 【参考方案1】:

拉维希,

你的问题很中肯,我没有在我的解决方案中解决这个问题。据我所知,我发现的解决方案只能动态工作,您可以在调用 sdk 时选择您的活动...

我的代码没有使用示例的原因是因为我的解决方案比我提出的解决方案要复杂一些,涉及我围绕 Android 框架构建的整个包装框架,其中 AdMob 与应用程序的关系是通过一个中间模块,它使用单个活动实例动态放置广告。

我真的怀疑您是否可以仅使用 Android XML 来避免内存泄漏。

无论如何,如果您从事内存泄漏业务,不妨检查一下您的 AsyncTask 使用情况……它也有自己的内存泄漏行为……所以这是我的solution

祝你好运……

-- 更新 -- 2014 年 7 月 10 日

有人刚刚对我的回答投了赞成票,这个问题仍然存在,这很荒谬,距离我最初的回答已经***年了,而且人们仍然在他们的应用程序中出现内存泄漏,因为 AdMob ......来自谷歌......安卓....

无论如何,我只是想补充一点,您应该将 AdMobActivity 的主题设置为透明,这样可以防止闪烁。

-- 更新 -- 28/02/16

四年...

-- 更新 -- 2017 年 9 月 3 日

五年... 谷歌的人请醒醒,雇佣真正的猴子来做这项工作:)

亚当。

【讨论】:

感谢亚当!请参阅我编辑的帖子以获取更直接的问题。感谢您还指出 AsyncTask 问题。幸好我还没有遇到它,或者如果我遇到了,它并没有像 AdMob 那样造成巨大的泄漏。 您使用哪个 AdMob 版本?您是在 super.onCreate(...) 之前还是之后调用它?您在哪个 Activity 中实例化 AdView、Dummy Activity 或真正的 UI Activity。 我搞定了。有关详细信息,请参阅原始问题中的编辑。非常感谢! Google Play Services 7.0 AdMob SDK 的问题仍然存在,这个解决方案对我不起作用。如果您最近发现任何其他解决方案,请分享:| ***.com/questions/29252545/… 你有跟进@ravishi问答吗?同样,这个解决方案并不是要消除 admob 活动......我们使用它来只允许它的一个实例!【参考方案2】:

我正在使用“play-services-ads:7.5.0”,并且不需要创建 de AdMobActivity。 它的工作原理是:

动态创建 adView

mAdView = new AdView(getApplicationContext(), AdSize.BANNER, banner_ad_unit_id); mAdsContainer.addView(mAdView);

在销毁和销毁 adView 时从 linearLayout 中删除所有视图

            mAdView.setAdListener(null);
            mAdsContainer.removeAllViews();
            mAdView.destroy();

不幸的是,插页式广告仍然泄漏

【讨论】:

非常感谢。我有一个LeakCanary 泄漏引用MainActivity -&gt; references ht.a -&gt; static hk.o。这帮助我解决了这个问题,这是我的GridLayoutManager 上的NativeExpressAdView 泄漏。我没有从我的GridLayoutManager 中删除所有视图或将我的setAdListener 设置为空。 Google 在他们的 NativeExpressAdView 教程中完全忽略了这一点。【参考方案3】:

我在 6.1.0 SDK 中看到了同样的泄漏,但能够通过在我的 Activity 的 onDestroy 中调用有问题的 AdView 上的 destroy() 来解决它。我认为他们已经解决了。 destroy() 似乎摆脱了 AdView 的 WebView 对我的 Activity 的 PhantomReferences,它使 Activity 无法成为 GC。

【讨论】:

以上是关于Admob 内存泄漏 - 通过使用空活动来避免的主要内容,如果未能解决你的问题,请参考以下文章

将 AdMediator 与 pubCenter 和 AdMob 一起使用的内存泄漏

通过weakHashMap避免过期引用导致的内存泄漏

记一次内存溢出查找分析文档

我在 Android 中使用 ListActivity 时出现内存泄漏

Kotlin是如何帮助你避免内存泄漏的?

避免、发现和消除 Cocoa 中的内存泄漏