Reputation: 11
I am super new to android coding, please tell me if this memory leak is significant enough for me to get worried. And if so please suggest something i can do about it.
I am trying to use admob to show rewarded ads in my android app, but after implementing the standard rewarded ads. My app started suffering from performance issues when I investigated the problem it turn out that and RewardedAdCallback is referencing my MainActivity causing a memory leak. How can I fix this after searching for 3 three days I am completely lost
this is my MainActivity
package com.example.ads
import android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.annotation.NonNull
import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.MobileAds
import com.google.android.gms.ads.rewarded.RewardItem
import com.google.android.gms.ads.rewarded.RewardedAd
import com.google.android.gms.ads.rewarded.RewardedAdCallback
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback
class MainActivity : AppCompatActivity() {
lateinit var rewardedAd: RewardedAd
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MobileAds.initialize(applicationContext)
rewardedAd = RewardedAd(this,
"ca-app-pub-3940256099942544/5224354917")
val adLoadCallback = object: RewardedAdLoadCallback() {
override fun onRewardedAdLoaded() {
// Ad successfully loaded.
}
override fun onRewardedAdFailedToLoad(adError: LoadAdError) {
// Ad failed to load.
}
}
rewardedAd.loadAd(AdRequest.Builder().build(), adLoadCallback)
}
fun showads(view:View){
if (rewardedAd.isLoaded) {
val activityContext: Activity = this@MainActivity
val adCallback = object : RewardedAdCallback() {
override fun onRewardedAdOpened() {
// Ad opened.
}
override fun onRewardedAdClosed() {
// Ad closed.
rewardedAd = createAndLoadRewardedAd()
}
override fun onUserEarnedReward(@NonNull reward: RewardItem) {
// User earned reward.
}
override fun onRewardedAdFailedToShow(adError: AdError) {
// Ad failed to display.
}
}
rewardedAd.show(activityContext, adCallback)
} else {
Log.d("TAG", "The rewarded ad wasn't loaded yet.")
}
}
fun createAndLoadRewardedAd(): RewardedAd {
val rewardedAd = RewardedAd(this, "ca-app-pub-3940256099942544/5224354917")
val adLoadCallback = object: RewardedAdLoadCallback() {
override fun onRewardedAdLoaded() {
// Ad successfully loaded.
}
override fun onRewardedAdFailedToLoad(adError: LoadAdError) {
// Ad failed to load.
}
}
rewardedAd.loadAd(AdRequest.Builder().build(), adLoadCallback)
return rewardedAd
}
fun l(view:View){
val intent = Intent(this,MainActivity2::class.java)
startActivity(intent)
}
}
this is my MainActivity2
package com.example.ads
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
}
fun goToSecond(view: View){
val intent = Intent(this,MainActivity::class.java)
startActivity(intent)
}
}
the MainActivty has 2 buttons one for showing ads (onClick:showads()) another for starting the second activity(onClick:goToSecond()) and the MainActivity2 has 1 button that starts the first activity
steps to recreate the memory leak are :-
Below is the leaky canary report
My question is why is Google ads causing a memory leak
┬───
│ GC Root: Global variable in native code
│
├─ ns instance
│ Leaking: UNKNOWN
│ ↓ ns.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 = 1
│ ↓ x.a
│ ~
├─ com.google.android.gms.ads.internal.webview.ab instance
│ Leaking: YES (View detached and has parent)
│ 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 = 1
│ ↓ ab.mListenerInfo
├─ android.view.View$ListenerInfo instance
│ Leaking: YES (ab↑ is leaking)
│ ↓ View$ListenerInfo.mOnClickListener
├─ com.google.android.gms.ads.nonagon.ad.webview.f instance
│ Leaking: YES (ab↑ is leaking)
│ ↓ f.a
├─ com.google.android.gms.ads.nonagon.ad.webview.l instance
│ Leaking: YES (ab↑ is leaking)
│ ↓ l.d
├─ com.google.android.gms.ads.nonagon.ad.event.cz instance
│ Leaking: YES (ab↑ is leaking)
│ ↓ cz.b
├─ java.util.HashMap instance
│ Leaking: YES (ab↑ is leaking)
│ ↓ HashMap.table
├─ java.util.HashMap$Node[] array
│ Leaking: YES (ab↑ is leaking)
│ ↓ HashMap$Node[].[2]
├─ java.util.HashMap$Node instance
│ Leaking: YES (ab↑ is leaking)
│ ↓ HashMap$Node.key
├─ com.google.android.gms.ads.nonagon.ad.event.cd instance
│ Leaking: YES (ab↑ is leaking)
│ ↓ cd.g
├─ com.google.android.gms.ads.nonagon.slot.rewarded.u instance
│ Leaking: YES (ab↑ is leaking)
│ ↓ u.c
├─ java.util.concurrent.atomic.AtomicReference instance
│ Leaking: YES (ab↑ is leaking)
│ ↓ AtomicReference.value
├─ com.google.android.gms.ads.internal.rewarded.client.f instance
│ Leaking: YES (ab↑ is leaking)
│ ↓ f.a
├─ com.google.android.gms.internal.ads.zzauy instance
│ Leaking: YES (ab↑ is leaking)
│ ↓ zzauy.zzdvi
├─ com.example.logicpuzzle.MainActivity$giveHint$adCallback$1 instance
│ Leaking: YES (ab↑ is leaking)
│ Anonymous subclass of com.google.android.gms.ads.rewarded.RewardedAdCallback
│ ↓ MainActivity$giveHint$adCallback$1.this$0
╰→ com.example.logicpuzzle.MainActivity instance
Leaking: YES (ObjectWatcher was watching this because com.example.logicpuzzle.MainActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
key = ab343657-b3f0-47fc-a4cf-281413e4765d
watchDurationMillis = 5652
retainedDurationMillis = 650
METADATA
Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: samsung
LeakCanary version: 2.4
App process name: com.example.app
Analysis duration: 18616
Upvotes: 1
Views: 1537
Reputation: 21
You can use destroy
function to destroy the ad view.
In Activity
, the code would be as follows:
override fun onDestroy() {
binding.adView.destroy()
super.onDestroy()
}
In Fragment
, the code would be like this:
override fun onDestroyView() {
binding.adView.destroy()
super.onDestroyView()
}
I hope, it would be helpful!
Upvotes: 0
Reputation: 489
I had the same issue. The simplest solution I found is to equate everything to Null.
override fun onDestroy() {
super.onDestroy()
adLoadCallback = null
mRewardedAd = null
}
Upvotes: 0