Solution:http://stackoverflow.com/questions/6148812/android-admob-causes-memory-leak
--
---
You received this message because you are subscribed to a topic in the Google Groups "Google AdMob Ads Developers" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/google-admob-ads-sdk/9IyjqdmeumM/unsubscribe.
To unsubscribe from this group and all its topics, send an email to google-admob-ads...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
Hi Aryeh,
Not sure what you mean by "the memory didn't go down after garbage collection". Maybe AdView is a lightweight object, not consuming too much memory on its own and if you are running a "Sample" app, probably you will not notice any move in the memory "level". However, the problem appears when you cannot destroy a heavy activity because this adView is leaking. When using Memory Profiler, did you check the "Activity/Fragment Leaks" checkbox? This checkbox should appear right below your memory graph. Also, did you run it with "LeakCanary"?
Just add this line to the build.gradle to test with LeakCanary:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
Anyways, I tried all solutions discussed in stackoverflow forums and all other
Google forums. The memory leaks appear to be erratic (one day works,
another day does not work, one activity is fine, other activity produces
memory leaks, etc.). The only thing I know is that if I remove this line from the code, the memory leak disappears:
mAdView = findViewById(R.id.adView);
Believe me on this. I have been chasing memory leaks in my app for over a month now and I'm completely sure of this. Remove the adview and there is no memory leak. Add the ads, and there is memory leak.
Most probable cause: Is AdView defining an internal variable with STRONG reference holding onto the contexts (i.e. the Activity) causing a leak on the Activity instance??? <<< I strongly recommend to your team to review this possible cause. The line in the LeakCanary report seems to be pointing to this cause: " mContext instance of com.google.android.gms.ads.internal.webview.ax, not wrapping activity"
Remember that we are also working in a similar memory leak issue for RewardedAd in this other thread:
https://groups.google.com/forum/#!category-topic/google-admob-ads-sdk/android/lPGZq54z53g
The only thing (WORKAROUND, no SOLUTION) that is working for me is to contain the memory leak in one single activity (and I applied a similar workaround to contain memory leaks for RewardedAd) as described in the following steps:
First recommendation: Do NOT add the adview directly to the XML layout file. If you follow the instructions from the official documentation (https://developers.google.com/admob/android/banner) that will lead to memory leak FOR SURE. Instead, add the adview programatically:
Remove the adview from the XML file:
<RelativeLayout
xmlns:ads="http://schemas.android.com/apk/res-auto"
android:id="@+id/RLadViewContainer"
android:layout_width="match_parent"
android:layout_height="50dp"
>
<com.google.android.gms.ads.AdView <<< REMOVE IT
android:id="@+id/adView" <<< REMOVE IT
android:layout_width="wrap_content" <<< REMOVE IT
android:layout_height="wrap_content" <<< REMOVE IT
android:layout_centerHorizontal="true" <<< REMOVE IT
android:layout_alignParentBottom="true" <<< REMOVE IT
ads:adSize="BANNER" <<< REMOVE IT
ads:adUnitId="@string/banner_ad_unit_id"> <<< REMOVE IT
</com.google.android.gms.ads.AdView> <<< REMOVE IT
</RelativeLayout>
Then define a RelativeLayout variable (adscontainer) in your activity next to the mAdView:
private AdView mAdView;
private RelativeLayout adscontainer;
In OnCreate, remove your old mAdView assignment and replace it with the following:
adscontainer = findViewById(R.id.RLadViewContainer);
mAdView = new AdView(MainActivity.MemoryLeakContainerActivity);//THIS IS THE TRICK ;)
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); //Move this line to the right place, wherever you need.
Note: This will display the banner at the bottom with CENTER_HORIZONTAL and ALIGN_PARENT_BOTTOM options.
In onDestroy(), add the following:
if (mAdView != null){
mAdView.setAdListener(null);
adscontainer.removeAllViews();
adscontainer = null;
mAdView.destroy();
mAdView = null;
}
Now, lets talk about this "MainActivity.MemoryLeakContainerActivity": YES, you have to sacrifice one of your activities to contain the memory leak. I did not find any other way. I tried "getApplicationContext()" here and it did not work. So I have chosen my MainActivity for two reasons:
a- In my app (match4app) the MainActivity works as a simple menu with three buttons ("Play", "Review Decks" and "Create Decks"). It is not consuming too much memory, there is no need to destroy it as most of the onBackPressed() tasks in my app lead the flow to the menu and it will be in the memory forever anyways.
b- It is the very first Activity to be loaded. Hence, the adview will be always ready and available for the other activities.
In the MainActivity (or the activity that you choose to contain the memory leak), add the following at the bottom:
public static MainActivity MemoryLeakContainerActivity;
public MainActivity() {
super();
if (MemoryLeakContainerActivity != null) {
throw new IllegalStateException("MemoryLeakContainerActivity is already created");
}
MemoryLeakContainerActivity = this;
}
Thats it! No more memory leaks in my app! LeakCanary reports are clean.
mAdView.destroy()" is not doing its job and for RewardedAd there is not even a "Destroy" method.
Regards,
-Pablo.
I am trying the code provided in the sample app for Adaptive banners
this is the heap dump , from leak cannery
┬───
│ GC Root: System class
│
├─ com.google.android.gms.ads.nonagon.a class
│ Leaking: NO (a class is never leaking)
│ ↓ static a.T
│ ~
├─ com.google.android.gms.ads.nonagon. a instance
│ Leaking: UNKNOWN
│ ↓ a.M
│ ~
├─ anl instance
│ Leaking: UNKNOWN
│ ↓ anl.c
│ ~
├─ com.google.android.gms.ads.internal.js.function.j instance
│ Leaking: UNKNOWN
│ ↓ j.c
│ ~
├─ com.google.android.gms.ads.internal.js.au instance
│ Leaking: UNKNOWN
│ ↓ au.f
│ ~
├─ com.google.android.gms.ads.internal.js.at instance
│ Leaking: UNKNOWN
│ ↓ at.a
│ ~
├─ com.google.android.gms.ads.internal.util.future.i instance
│ Leaking: UNKNOWN
│ ↓ i.a
│ ~
├─ aaq instance
│ Leaking: UNKNOWN
│ ↓ aaq.value
│ ~~~~~
├─ com.google.android.gms.ads.internal.js.w instance
│ Leaking: UNKNOWN
│ ↓ w.a
│ ~
├─ com.google.android.gms.ads.internal.webview.x instance
│ Leaking: UNKNOWN
│ mContext instance of com.google.android.gms.ads.internal.webview.ax, not wrapping activity
│ View#mParent is null
│ View#mAttachInfo is null (view detached)
│ View.mWindowAttachCount = 0
│ ↓ x.a
│ ~
├─ com.google.android.gms.ads.internal.webview.ab instance
│ Leaking: UNKNOWN
│ mContext instance of com.google.android.gms.ads.internal.webview.ax, not wrapping activity
│ View#mParent is set
│ View#mAttachInfo is null (view detached)
│ View.mWindowAttachCount = 0
│ ↓ ab.n
│ ~
├─ com.google.android.gms.ads.internal.webview.am instance
│ Leaking: UNKNOWN
│ ↓ am.b
│ ~
├─ java.util.HashMap instance
│ Leaking: UNKNOWN
│ ↓ HashMap.table
│ ~~~~~
├─ java.util.HashMap$HashMapEntry[] array
│ Leaking: UNKNOWN
│ ↓ HashMap$HashMapEntry[].[0]
│ ~~~
├─ java.util.HashMap$HashMapEntry instance
│ Leaking: UNKNOWN
│ ↓ HashMap$HashMapEntry.value
│ ~~~~~
├─ java.util.concurrent.CopyOnWriteArrayList instance
│ Leaking: UNKNOWN
│ ↓ CopyOnWriteArrayList.elements
│ ~~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ ↓ Object[].[0]
│ ~~~
├─ com.google.android.gms.ads.internal.js.v instance
│ Leaking: UNKNOWN
│ ↓ v.a
│ ~
├─ com.google.android.gms.ads.nonagon.ad.activeview.b instance
│ Leaking: UNKNOWN
│ ↓ b.a
│ ~
├─ com.google.android.gms.ads.nonagon.ad.activeview.e instance
│ Leaking: UNKNOWN
│ ↓ e.c
│ ~
├─ com.google.android.gms.ads.nonagon.ad.activeview.j instance
│ Leaking: UNKNOWN
│ ↓ j.c
│ ~
├─ java.util.HashSet instance
│ Leaking: UNKNOWN
│ ↓ HashSet.backingMap
│ ~~~~~~~~~~
├─ java.util.HashMap instance
│ Leaking: UNKNOWN
│ ↓ HashMap.table
│ ~~~~~
├─ java.util.HashMap$HashMapEntry[] array
│ Leaking: UNKNOWN
│ ↓ HashMap$HashMapEntry[].[0]
│ ~~~
├─ java.util.HashMap$HashMapEntry instance
│ Leaking: UNKNOWN
│ ↓ HashMap$HashMapEntry.key
│ ~~~
├─ com.google.android.gms.ads.internal.webview.x instance
│ Leaking: UNKNOWN
│ mContext instance of com.google.android.gms.ads.internal.webview.ax, not wrapping activity
│ View#mParent is set
│ View#mAttachInfo is null (view detached)
│ View.mWindowAttachCount = 0
│ ↓ x.mContext
│ ~~~~~~~~
├─ com.google.android.gms.ads.internal.webview.ax instance
│ Leaking: UNKNOWN
│ ax does not wrap an activity context
│ ↓ ax.a
│ ~
╰→
com.sample.testapp.mainactivity instance
Leaking: YES (ObjectWatcher was watching this because com.sample.testapp.mainactivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
key = d72b533a-f164-4087-a878-68613e9eeada
watchDurationMillis = 5624
retainedDurationMillis = 595
METADATA
Build.VERSION.SDK_INT: 23
Build.MANUFACTURER: Micromax
LeakCanary version: 2.3
App process name: com.sample.testapp
Analysis duration: 160420 ms
The way to reproduce the code is to rotate the activity a few times or , open the activity from the previous activity a few times
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class FirstActivity extends AppCompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.firstact);
Button clickButton = (Button) findViewById(R.id.clickButton);
clickButton.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent myIntent = new Intent(FirstActivity.this, MyActivity.class);
FirstActivity.this.startActivity(myIntent);
}
});
}
}
public class MyActivity extends AppCompatActivity {
private static final String AD_UNIT_ID = "ca-app-pub-3940256099942544/9214589741";
private FrameLayout adContainerView;
private AdView adView;
private static final long GAME_LENGTH_MILLISECONDS = 3000;
private static final String AD_UNIT_ID1 = "ca-app-pub-3940256099942544/1033173712";
private InterstitialAd interstitialAd;
private CountDownTimer countDownTimer;
private Button retryButton;
private boolean gameIsInProgress;
private long timerMilliseconds;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
// Initialize the Mobile Ads SDK.
// Set your test devices. Check your logcat output for the hashed device ID to
// get test ads on a physical device. e.g.
// "Use RequestConfiguration.Builder().setTestDeviceIds(Arrays.asList("ABCDEF012345"))
// to get test ads on this device."
MobileAds.initialize(this, new OnInitializationCompleteListener() {
@Override
public void onInitializationComplete(InitializationStatus initializationStatus) {}
});
MobileAds.setRequestConfiguration(
new RequestConfiguration.Builder().setTestDeviceIds(Arrays.asList("ABCDEF012345")).build());
adContainerView = findViewById(R.id.ad_view_container);
// Since we're loading the banner based on the adContainerView size, we need to wait until this
// view is laid out before we can get the width.
adContainerView.post(new Runnable() {
@Override
public void run() {
loadBanner();
}
});
interstitialAd = new InterstitialAd(getApplicationContext());
// Defined in res/values/strings.xml
interstitialAd.setAdUnitId(AD_UNIT_ID1);
interstitialAd.setAdListener(new AdListener() {
@Override
public void onAdLoaded() {
Toast.makeText(MyActivity.this, "onAdLoaded()", Toast.LENGTH_SHORT).show();
}
@Override
public void onAdFailedToLoad(int errorCode) {
Toast.makeText(MyActivity.this,
"onAdFailedToLoad() with error code: " + errorCode,
Toast.LENGTH_SHORT).show();
}
@Override
public void onAdClosed() {
startGame();
}
});
// Create the "retry" button, which tries to show an interstitial between game plays.
retryButton = findViewById(R.id.retry_button);
retryButton.setVisibility(View.INVISIBLE);
retryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showInterstitial();
}
});
startGame();
}
/**
* Called when leaving the activity
*/
private void loadBanner() {
// Create an ad request.
adView = new AdView(getApplicationContext());
adView.setAdUnitId(AD_UNIT_ID);
adContainerView.removeAllViews();
adContainerView.addView(adView);
AdSize adSize = getAdSize();
adView.setAdSize(adSize);
AdRequest adRequest = new AdRequest.Builder().build();
// Start loading the ad in the background.
adView.loadAd(adRequest);
}
private AdSize getAdSize() {
// Determine the screen width (less decorations) to use for the ad width.
Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
float density = outMetrics.density;
float adWidthPixels = adContainerView.getWidth();
// If the ad hasn't been laid out, default to the full screen width.
if (adWidthPixels == 0) {
adWidthPixels = outMetrics.widthPixels;
}
int adWidth = (int) (adWidthPixels / density);
return AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(this, adWidth);
}
// Create the InterstitialAd and set the adUnitId.
private void createTimer(final long milliseconds) {
// Create the game timer, which counts down to the end of the level
// and shows the "retry" button.
if (countDownTimer != null) {
countDownTimer.cancel();
}
final TextView textView = findViewById(R.id.timer);
countDownTimer = new CountDownTimer(milliseconds, 50) {
@Override
public void onTick(long millisUnitFinished) {
timerMilliseconds = millisUnitFinished;
textView.setText("seconds remaining: " + ((millisUnitFinished / 1000) + 1));
}
@Override
public void onFinish() {
gameIsInProgress = false;
textView.setText("done!");
retryButton.setVisibility(View.VISIBLE);
}
};
}
private void showInterstitial() {
// Show the ad if it's ready. Otherwise toast and restart the game.
if (interstitialAd != null && interstitialAd.isLoaded()) {
interstitialAd.show();
} else {
Toast.makeText(this, "Ad did not load", Toast.LENGTH_SHORT).show();
startGame();
}
}
private void startGame() {
// Request a new ad if one isn't already loaded, hide the button, and kick off the timer.
if (!interstitialAd.isLoading() && !interstitialAd.isLoaded()) {
AdRequest adRequest = new AdRequest.Builder().build();
interstitialAd.loadAd(adRequest);
}
retryButton.setVisibility(View.INVISIBLE);
resumeGame(GAME_LENGTH_MILLISECONDS);
}
private void resumeGame(long milliseconds) {
// Create a new timer for the correct length and start it.
gameIsInProgress = true;
timerMilliseconds = milliseconds;
createTimer(milliseconds);
countDownTimer.start();
}
@Override
public void onPause() {
if (adView != null) {
adView.pause();
}
countDownTimer.cancel();
super.onPause();
}
/**
* Called when returning to the activity
*/
@Override
public void onResume() {
super.onResume();
if (adView != null) {
adView.resume();
}
if (gameIsInProgress) {
resumeGame(timerMilliseconds);
}
}
/**
* Called before the activity is destroyed
*/
@Override
public void onDestroy() {
if (adView != null) {
adView.destroy();
}
super.onDestroy();
}
}Note For Adaptive banners I have used test ad units ca-app-pub-3940256099942544/9214589741 provided in sample apphttps://github.com/googleads/googleads-mobile-android-examples/blob/master/java/admob/AdaptiveBannerExample/app/src/main/java/com/google/android/gms/example/adaptivebannerexample/MyActivity.javaand for Interstitial I have used ca-app-pub-3940256099942544/1033173712I have used the same code provided in sample apps except that I have merged both the Interstitial and Adaptive Activity
Build.VERSION.SDK_INT: 23
Build.MANUFACTURER: Micromax
LeakCanary version: 2.3
App process name: com.sample.testapp
After implementing the latest 4.0.4 SDK, I noticed that I started
leaking activities. I took HPROF dumps and analyzed with Eclipse
Memory Analyzer and found that the offending reference is the AdMob
weview.
My implementation is fairly straightforward, following the developer's
guide. I load an ad in onCreate() and I call
admobAdView.stopLoading() in onDestroy().
I thought maybe I had an implementation bug, so I took the demo app
from the docs (http://dl.google.com/googleadmobadssdk/examples/android-
banner-details.zip) and I found the same leak there. All I had to do
was load up the demo app with my adUnitId and then rotate the device a
bunch of times after each banner advert is loaded. Trigger a GC
through DDMS, take an HPROF dump and you will find that an instance of
the com.google.ads.example.BannerDetails activity has leaked for every
single rotation. Once again, it's the webview that causes it.
Appreciate any advice. Thanks!
private AdSize getAdSize() { // Determine the screen width (less decorations) to use for the ad width. // Display display = getWindowManager().getDefaultDisplay(); DisplayMetrics outMetrics = this.getResources().getDisplayMetrics(); // display.getMetrics(outMetrics);
and it seems to work if compiled and run on an
targetSdkVersion 29
machine. The ad gets slightly cut off on the right side though. When the targetSdkVersion is 30, ads failed to load with error code 0.
Since these beta errors are a different issue from the original issue in this thread I am creating a new thread in the forum here - https://groups.google.com/forum/#!searchin/google-admob-ads-sdk/api$2030%7Csort:date/google-admob-ads-sdk/dDZ8tNmyClc/TPvuSoatAwAJ. If you want to pursue this issue further please continue on that thread so that we can focus on the original concern in this thread.
Thanks,
Aryeh Baker
Mobile Ads SDK Team
Hi there,
Thank you for following-up on this thread.
My colleague (Aryeh) created a separate thread for this issue and I can also see that you've made a follow-up on that thread. Let's continue the discussion on that thread instead to better track this issue.
Regards,
Mark Albios
Mobile Ads SDK Team
getDomain()
and getCause()
methods to AdError
.LoadAdError
. E.g., onAdFailedToLoad(LoadAdError)
.Hi Omkar,
Thank you for providing an update on your concern.
I will be adding your comments to the bug report that we currently are monitoring regarding this issue, in order to help with the investigation. It would also be helpful if you could kindly provide an actual sample project (via Reply privately to author), replicating this issue, in order to further help on this.
With that said, you may adopt the "empty activity" workaround at your own risk if you wish to, as this is a community-contributed workaround that we will not be able to officially endorse at the moment. Rest assured that I've added that particular topic to the bug report as well, so that we may be able to glean new insights from it.
Regards,
Ziv Yves Sanchez
Caused by com.github.anrwatchdog.ANRError$$$_Thread: main (state = RUNNABLE)at com.google.android.gms.dynamic.RemoteCreator.getRemoteCreatorInstance(:4)
at com.google.android.gms.internal.ads.zzva.zza(com.google.android.gms:play-services-ads-lite@@19.3.0:5)
at com.google.android.gms.internal.ads.zzvz.zzpo(com.google.android.gms:play-services-ads-lite@@19.3.0:7)
at com.google.android.gms.internal.ads.zzwf.zzqb(com.google.android.gms:play-services-ads-lite@@19.3.0:29)
at com.google.android.gms.internal.ads.zzwf.zzd(com.google.android.gms:play-services-ads-lite@@19.3.0:52)
at com.google.android.gms.internal.ads.zzys.zza(com.google.android.gms:play-services-ads-lite@@19.3.0:40)
at com.google.android.gms.ads.InterstitialAd.loadAd(com.google.android.gms:play-services-ads-lite@@19.3.0:9)
at com.sample.testapp.onCreate(MyActivity.java:89)
at android.app.Activity.performCreate(Activity.java:6309)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1113)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2519)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2654)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1488)
at android.os.Handler.dispatchMessage(Handler.java:111)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5728)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:679)
implementation 'com.google.android.gms:play-services-ads:19.3.0'
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.detectAll() //for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.detectActivityLeaks()
.detectAll()
.penaltyLog()
.penaltyDeath()
.build());
}
Hi there,
Thank you for your patience on this. Our team is currently investigating this issue. I've also made a follow up for this.
We'll be updating this thread for any updates from the team.
Regards,
|
||||||
I hope you're doing well.
I just want to circle back on this. Can you privately provide the below information?
If the file(s) you are looking to share are less than 25mb in total you can attach them to this case on your next reply. If you are having trouble attaching your file to this case or if your file(s) are larger than 25mb, you can share your files with me by performing the following steps:
1. Navigate to https://docs.google.com/forms/d/e/1FAIpQLSfkAiXMeYP-fw1W3Z-tT9uwmATEKO5X6S-th0gR2ezdKaaqfg/viewform?usp=pp_url&entry.400550049=Mobile+Ads+SDK&entry.460850823=5004Q000022aE7qQAE&entry.80707362=00042710
2. Fill out all fields, and attach your file(s).
3. Please reply back on this thread when you have uploaded your file(s). Please do not share this link.
Regards,
|
||||||