Android AdMob会导致内存泄漏吗?

23

我已经将AdMob v4.1.0集成到我的应用程序中,但似乎导致了一个严重的内存泄漏问题(很确定这在4.0.4上也已经发生过)。

为了分离问题,我创建了一个带有空白线性布局的新项目,并将AdView添加到其中(这实际上是从AdMob提供的示例代码中复制粘贴的)。请查看我的main.xml、MainActivity.java和清单内容:

main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/linearLayout">
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
</LinearLayout>

MainActivity.java:

package AdsTry.main;

import com.google.ads.AdRequest;
import com.google.ads.AdSize;
import com.google.ads.AdView;

import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;

public class MainActivity extends Activity {

    private final int AD_VIEW_ID = 1000000; 

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

         // Lookup R.layout.main
        LinearLayout layout = (LinearLayout)findViewById(R.id.linearLayout);

        // Create the adView
        // Please replace MY_BANNER_UNIT_ID with your AdMob Publisher ID
        AdView adView = new AdView(this, AdSize.BANNER, "MY_BANNER_UNIT_ID");
        adView.setId(AD_VIEW_ID);

        // Add the adView to it
        layout.addView(adView);

        // Initiate a generic request to load it with an ad
        AdRequest request = new AdRequest();

        adView.loadAd(request);           
    }

    @Override
    protected void onPause() {
        Log.i("AdsTry", "onPause");

        getAdView().stopLoading();

        super.onPause();
    }

    @Override
    protected void onDestroy() {
        Log.i("AdsTry", "onDestroy");

        getAdView().destroy();

        super.onDestroy();
    }

    private AdView getAdView()
    {
        return (AdView) findViewById(AD_VIEW_ID);
    }
}

清单文件:

<application android:icon="@drawable/icon" android:label="@string/app_name">
    <activity android:name=".MainActivity"
              android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <!-- AdMobActivity definition -->
    <activity android:name="com.google.ads.AdActivity"
        android:configChanges="orientation|keyboard|keyboardHidden" />
</application>

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

这就是我所有的代码了。

现在,当运行此应用程序时,我可以看到调用了onPause和onDestory并终止了Activity,但问题是由于导致InputMethodManager持有对Activity的引用(请参见从销毁Activity后Hprof输出的图像),它永远不会可用于GC:

MainActivity Merge shortest path to GC Roots

一旦我删除与广告视图相关的代码(而且,这确实是此应用程序的唯一代码),问题就解决了:

Same HPROF output without using AdView

编辑: 还尝试从onCreate中删除所有代码并更新main.xml以包含以下内容(仍然获得相同的结果):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ads="http://schemas.android.com/apk/lib/com.google.ads"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/linearLayout">
    <TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
    <com.google.ads.AdView
    android:id="@+id/Ad"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    ads:adUnitId="MY_ID"
    ads:adSize="BANNER"
    ads:loadAdOnCreate="true"/>
</LinearLayout>

有任何想法吗?


1
更多信息请参阅以下链接:https://groups.google.com/forum/#!topic/google-admob-ads-sdk/XnU2K2Q18aA/discussion 和 https://groups.google.com/group/google-admob-ads-sdk/browse_thread/thread/f48ca3a9d99eba63?pli=1 - Muzikant
2
我一直在我的项目中尝试使用4.1.1版本,但仍然出现Activity泄漏的问题。如果我移除Admob,则泄漏问题消失。如果我回退到旧版本的Admob,仍然存在问题。如果我在onDestroy方法中调用adView.onDestroy()也没有帮助。如果我等待半个小时并按下GC按钮数百次也没有帮助。你找到任何解决方案或变通方法了吗?我完全被卡住了 :( - timothyjc
1
没有任何解决方案适用于我。最终只能删除广告。这根本不值得... - Muzikant
我不确定这是否有帮助,但我在Android支持网站上创建了一个错误报告。Admob目前由Google控制,所以他们应该采取一些行动。我很惊讶这个错误之前居然没有被发现。[问题59627](http://code.google.com/p/android/issues/detail?id=59627&thanks=59627&ts=1378046413) - Mark
请检查我的答案,看看是否解决了这个问题。谢谢! - SavageKing
显示剩余2条评论
9个回答

7
我正在使用“play-services-ads:7.5.0”,不需要创建AdMobActivity。它可以通过以下方式工作:
动态创建adView。
mAdView = new AdView(getApplicationContext(), AdSize.BANNER, banner_ad_unit_id); mAdsContainer.addView(mAdView);

在销毁时从linearLayout中移除所有视图并销毁adView。
mAdView.setAdListener(null);
mAdsContainer.removeAllViews();
mAdView.destroy();

很遗憾,插页式广告仍然存在泄漏问题。


5
以下是我针对这个问题的解决方法:
我通过使用相同的空活动实例来限制内存泄漏:
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);
        finish();
    }

    public static final void startAdMobActivity(Activity activity) {
        Intent i = new Intent();
        i.setComponent(new ComponentName(activity.getApplicationContext(), AdMobActivity.class));
        activity.startActivity(i);
    }
}

使用AdMobActivity.AdMobMemoryLeakWorkAroundActivity可以创建更多的广告。

当然,您还需要将该活动添加到清单中:

<activity
    android:launchMode="singleInstance"
    android:name="com.nu.art.software.android.modules.admob.AdMobActivity" />

这个实现方式与我对静态引用的理解不同,它们并不是常量,但这种实现方式可以防止内存泄漏,因为只使用一个活动实例来创建所有广告,所以不会有更多的活动泄漏,而且活动完全为空。
注意:你应该在应用程序主活动的onCreate方法中调用startAdMobActivity方法。
Adam.
更新:
此解决方案仅适用于动态创建广告并使用代码将其添加到布局中的情况... 不要忘记在Activity.onDestroy()中销毁它。

你的帖子看起来非常有用,但我在使用它时遇到了一些问题。我的项目目前在每个活动的布局XML中声明com.google.ads.AdView,因此我不会在代码中动态生成任何广告或广告视图。我不明白你的活动如何与AdMob代码相关联。你能发布一些示例,说明如何实例化该活动并加载广告吗?谢谢! - ravishi
1
如果有人像我一样遇到了困难,无法按照TacB0sS的指示操作,请参考https://dev59.com/6eo6XIcBkEYKwwoYQiTQ。我已经为我们这些初学者分解了它。 - ravishi
我尝试了这个解决方案。现在它不再泄漏我的活动,但是当我点击任何广告时,它不会打开那个链接。 然而,如果我将当前活动实例传递给AdView构造函数,它就可以正常工作。 - Ashish Pathak
我很惊讶,经过这么长时间(一年半),他们仍然没有解决这个可恶的问题...是的,你需要实例化AdActivity,这样就不会再有泄漏了,但是为了显示广告,你需要传递当前活动! - TacB0sS
我不认为有什么问题,只要您动态添加广告视图即可,参见 @ravishi 的答案。 - TacB0sS

3

解决这个问题的一种方法是使用您应用程序的基本活动创建一个单一实例的广告视图。将该广告视图添加到您想要的任何活动中,但请记住在活动的on destroy方法中删除它。

这样,广告视图只会泄漏基本活动,你几乎永远不应该有超过一个实例。

例如:

@Override
public void onCreate(final Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   this.setContentView(R.layout.venue);
   mainLayout = (LinearLayout) findViewById(R.id.venueLayout);
   adview = StaticStateClass.getAdview();

   AdRequest request = new AdRequest();
   request.addKeyword(name);
   mainLayout.addView(adview);
   adview.loadAd(request);
}

@Override
public void onDestroy() {
   mainLayout.removeView(adview);
   super.onDestroy();
}

1
如果你有时间,你可以使用反射手动拆分adview对象,并将你的上下文的设置和引用设置为null。除非我别无选择,否则我通常不这样做。 - Xiaodi Huang

2
我发现调用以下命令:-
mAdView.destroy(); 

在离开活动之前,它为我解决了泄漏问题。

以下是我如何在xml中声明横幅广告:

  <com.google.android.gms.ads.AdView
            android:id="@+id/adView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal|bottom"
            ads:adSize="BANNER"
            ads:adUnitId="@string/banner_ad_unit_id_2" />

在onCreate()中获取Admob横幅的引用:
mAdView = (AdView) findViewById(R.id.adView2);
AdRequest banneradRequest = new AdRequest.Builder().build();

mAdView.loadAd(banneradRequest);

在活动的ondestroy()方法中销毁它:

@Override
protected void onDestroy() {
    super.onDestroy();
    mAdView.destroy();
}

super.onDestroy();应该在adView.destroy()之后调用,对吗? - Duna
是的,super.onDestroy() 应该在之后调用。 - Harsh Kothari

1

我也遇到了同样的问题。唯一能帮助我的是 System.exit(0)。虽然我不喜欢这种方式,但这是我找到的唯一方法。
AdMob 会一直占用内存,不让我的应用程序正常结束。当我重新启动应用程序时,操作系统会重新加载该不死应用程序,很快就会导致内存溢出异常。
我的 AdMob 支持论坛主题 - 目前还没有得到支持。看起来似乎没有人关心。


0

另一个解决此问题的答案,尽管这样做很麻烦,但是可以按照以下步骤进行。

  • 从sharedPreferences或任何其他类型的存储持久性中取消所有需要的数据,
    将这些值存储在本地变量/对象中

  • 清除您的应用程序数据

  • 重新持久化所有内容

您可以在应用程序关闭时启动服务来执行此操作。这将防止由于内存泄漏而导致数据变得非常大。这是一种解决方法,虽然不太好,但我已经尝试过它并且它有效。


0
动态创建AdView并将其传递给任何ViewGroup,如下所示:
MobileAds.initialize(applicationContext)
adView = AdView(applicationContext)
binding.adViewContainer.addView(adView)
adView.adSize = getAdSize(binding.adViewContainer)
adView.adUnitId = "ca-app-pub-3940256099942544/6300978111"
val adRequest = AdRequest.Builder().build()
adView.loadAd(adRequest)

注意: 使用applicationContext而不是activity或fragment context以防止内存泄漏。
我已经使用了view binding、测试广告、帧布局和自适应横幅。我在这里获得了获取最大宽度显示的算法:https://developers.google.com/admob/android/banner/adaptive?hl=en-US#sample_code
override fun onDestroy() {
    adView.destroy()
    super.onDestroy()
    binding.adViewContainer.removeView(adView)
}

-1

不要将您的UI与Admob合并,您可以运行另一个线程从Admob获取广告。此外,您可以在特定时间间隔内刷新广告。这将更容易让您调试可能存在的内存问题。

谢谢 Deepak


谢谢你的回答。你说的在不同线程上使用AdMob是什么意思?你是指上面从onCreate开始的代码应该在不同的线程中运行吗?那会怎样工作(难道不应该在UI线程上运行吗?) - Muzikant
我想说创建一个单独的线程,在其中的run()方法中编写从Admob获取广告的代码。从这个主类开始启动该线程。将您的UI与Admob线程分开。 - Sunil Kumar Sahoo
我无法从UI线程外部访问AdView视图(我收到了一个异常)。我还尝试从onCreate中删除所有代码并更新main.xml(请参见问题的编辑)。仍然得到相同的结果。 - Muzikant

-2

基本上,这不是一个真正的问题。发生的情况是您的活动接收到onDestroy,进行清理,然后等待GC运行。当GC发生时,它看到这个旧的上下文环境并清理所有底层垃圾,但在那一轮中似乎没有清除该Activity - 这就是为什么您的“泄漏”Activity具有如此小的占用空间:它基本上只是一个外壳。在下一次GC通过时,它将被清理。

您可以自己查看这一点,方法是启动您的活动,退出(以触发onDestroy),重新进入进程,触发GC和hprof转储,等待一段时间,然后再次触发GC和hprof转储。在第一个转储中 - 在我的测试中至少 - 我看到了与您类似的结果:额外的Activity占用很小的内存空间。在第二个转储中,我只看到当前正在运行的Activity。


3
我已经尝试多次执行垃圾回收,等待几分钟后再次运行,但结果仍然相同。 - Muzikant

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