androidwebviewleakcanary

Android - Leakcanary spot memory leak in Activities using Android webview - Problem identified in Samsung Android 13 (One ui 5)


folks

Using leakcanary tool we have noticed recently memory leaks in activities that use webview. The issue just occurs in some models like Samsung devices with Android 13 (one ui 5). We were not able to reproduce the issue in Pixel 4 with Android 13. When the user closes the activity it still in memory even after onDestroy. We have noticed an increase in ANRs to the affected devices.

The Android code to reproduce the issue and leakcanary`s report are bellow. (We just have to inflate a layout that has webview and close the activity, so the memory leak happens)

AndroidManifest.xml

<activity
  android:name=".HomeActivity"
  android:exported="false"
  android:theme="@style/AppTheme.NoActionBar">
</activity> 

activity_home.xml (Here we have the tag for webview component)

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="br.com.gabba.myapp.presentation.WebViewActivity"
    tools:viewBindingIgnore="true">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay"
        android:background="@drawable/bg_header"
        android:gravity="bottom"
        app:elevation="0dp">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:paddingStart="8dp"
            app:contentInsetLeft="0dp"
            app:contentInsetStart="0dp"
            app:contentInsetStartWithNavigation="0dp"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:title="@string/app_name"
            app:titleTextAppearance="@style/Toolbar.TitleTextExterno" />

    </com.google.android.material.appbar.AppBarLayout>

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <WebView
            android:id="@+id/webview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />


    </FrameLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

HomeActivity.kt - All we need to have the issue is inflate the layout with the webview.

package br.com.gabba.myapp.presentation

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import br.com.gabba.myapp.R

class HomeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_home)
    }
}

app/build.gradle

(...)
implementation 'androidx.activity:activity-ktx:1.6.1'

(...)
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'

LeakCanary documentation

LeakCanary report with the issue:

                                                                                                │ GC Root: Global variable in native code
                                                                                                │
                                                                                                ├─ J1 instance
                                                                                                │    Leaking: UNKNOWN
                                                                                                │    Retaining 137,3 kB in 1971 objects
                                                                                                │    ↓ WindowAndroid.p
                                                                                                │                    ~
                                                                                                ├─ b30 instance
                                                                                                │    Leaking: UNKNOWN
                                                                                                │    Retaining 136,2 kB in 1930 objects
                                                                                                │    ↓ b30.b
                                                                                                │          ~
                                                                                                ├─ com.android.internal.policy.PhoneWindow instance
                                                                                                │    Leaking: YES (Window#mDestroyed is true)
                                                                                                │    Retaining 136,2 kB in 1929 objects
                                                                                                │    mContext instance of br.com.gabba.myapp.presentation.HomeActivity with mDestroyed = true
                                                                                                │    mOnWindowDismissedCallback instance of br.com.gabba.myapp.presentation.HomeActivity with mDestroyed = true
                                                                                                │    ↓ Window.mContext
                                                                                                ╰→ br.com.gabba.myapp.presentation.HomeActivity instance
                                                                                                ​     Leaking: YES (ObjectWatcher was watching this because br.com.gabba.myapp.presentation.HomeActivity received
                                                                                                ​     Activity#onDestroy() callback and Activity#mDestroyed is true)
                                                                                                ​     Retaining 6,8 kB in 245 objects
                                                                                                ​     key = afa4c248-0006-4b43-9660-d2131e74893a
                                                                                                ​     watchDurationMillis = 8817
                                                                                                ​     retainedDurationMillis = 3814
                                                                                                ​     mApplication instance of br.com.gabba.myapp.myappApplication
                                                                                                ​     mBase instance of androidx.appcompat.view.ContextThemeWrapper
                                                                                                
                                                                                                METADATA
                                                                                                
                                                                                                Build.VERSION.SDK_INT: 33
                                                                                                Build.MANUFACTURER: samsung
                                                                                                LeakCanary version: 2.10
                                                                                                App process name: br.com.gabba.myapp
                                                                                                Class count: 29960
                                                                                                Instance count: 230083
                                                                                                Primitive array count: 152647
                                                                                                Object array count: 33977
                                                                                                Thread count: 72
                                                                                                Heap total bytes: 32257183
                                                                                                Bitmap count: 7
                                                                                                Bitmap total bytes: 5674439
                                                                                                Large bitmap count: 0
                                                                                                Large bitmap total bytes: 0
                                                                                                Db 1: closed /data/user/0/br.com.gabba.myapp/databases/google_app_measurement_local.db
                                                                                                Db 2: open /data/user/0/br.com.gabba.myapp/databases/leaks.db
                                                                                                Db 3: open /data/user/0/br.com.gabba.myapp/databases/com.microsoft.appcenter.persistence
                                                                                                Db 4: open /data/user/0/br.com.gabba.myapp/no_backup/androidx.work.workdb
                                                                                                Db 5: open /data/user/0/br.com.gabba.myapp/databases/com.google.android.datatransport.events
                                                                                                Stats: LruCache[maxSize=3000,hits=119796,misses=220169,hitRate=35%]
                                                                                                RandomAccess[bytes=11094589,reads=220169,travel=116557590219,range=37155332,size=47075038]
                                                                                                Analysis duration: 10730 ms

                                                                  

Solution

  • Thanks to @PaolinoLAngeletti comment we figure out that putting this line - - webView.destroy(); - on activity ondestroy method seems to solve the issue.

          (...)
            
             this.webView = findViewById(R.id.webview); //get a reference to webview
            
         (...)
    
    @Override
     protected void onDestroy() {
        
        try {
                 
         if (webView != null) {
                        webView.setWebViewClient(null);
                        webView.setWebChromeClient(null);
                        webView.clearHistory();
                        webView.clearCache(true);
                        webView.destroy(); //this was added
                        webview = null;
                    }
                } catch (Exception e) {
                    Log.e(TAG, "onDestroy", e);
                }
        
                super.onDestroy();
            }