11

I add leak canary to my app and I find this memory leak: https://i.stack.imgur.com/fAtxe.png

I don't use at my MainActivity LinearLayout or Toast but I has this leak and I can't understand why and how. Maybe it because I use toasts and linear layout inside fragments that I use in MainActivity?

Here is my MainActivity xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    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:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/lost_connection_image_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/fullscreen_exit"/>

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimaryDark"
        android:minHeight="?attr/actionBarSize"
        android:theme="?attr/actionBarTheme"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/userPhoto"
            android:layout_width="36dp"
            android:layout_height="36dp"
            android:layout_alignParentEnd="true"
            android:layout_gravity="end"
            android:layout_margin="8dp"
            android:background="@android:color/transparent"
            android:src="@drawable/account_default"
            android:visibility="visible"
            tools:layout_editor_absoluteX="312dp"
            tools:layout_editor_absoluteY="4dp"/>

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_margin="8dp"
            app:cardBackgroundColor="@android:color/background_light"
            app:cardCornerRadius="1dp"
            tools:layout_editor_absoluteX="16dp"
            tools:layout_editor_absoluteY="28dp">

            <android.support.constraint.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:focusable="true"
                android:focusableInTouchMode="true">

                <EditText
                    android:id="@+id/search_field"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="8dp"
                    android:layout_marginEnd="8dp"
                    android:layout_marginStart="8dp"
                    android:layout_marginTop="8dp"
                    android:background="@null"

                    android:ems="10"
                    android:hint="@string/search_for"
                    android:imeOptions="actionSearch"
                    android:inputType="text"
                    android:singleLine="true"
                    android:textColorHint="@android:color/darker_gray"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toStartOf="@+id/search"
                    app:layout_constraintStart_toStartOf=" parent"
                    app:layout_constraintTop_toTopOf="parent"/>

                <ImageView
                    android:id="@+id/search"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="8dp"
                    android:layout_marginTop="8dp"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:srcCompat="@drawable/search"/>
            </android.support.constraint.ConstraintLayout>

        </android.support.v7.widget.CardView>
    </android.support.v7.widget.Toolbar>


    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        app:layout_constraintBottom_toTopOf="@+id/navigationView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar">

    </FrameLayout>

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigationView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimaryDark"
        app:itemIconTint="@color/bottom_nav_selected"
        app:itemTextColor="@color/bottom_nav_selected"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/menu_main"/>
</android.support.constraint.ConstraintLayout>

And one of fragments that I put in FrameLayout with id "container":

<RelativeLayout 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:layout_width="match_parent"
                android:layout_height="match_parent">

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/home_fragment_root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentBottom="true"
        android:layout_marginStart="0dp"
        android:layout_marginTop="0dp"
        android:layout_marginEnd="0dp"
        android:layout_marginBottom="0dp"
        android:visibility="invisible">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <android.support.constraint.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:focusable="false"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.0"
                app:layout_constraintStart_toStartOf="parent">


                <com.bondpm.bond.customViews.WrapContentViewPager
                    android:id="@+id/viewPager"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:background="@android:color/background_light"
                    android:focusable="true"
                    android:padding="0dp"
                    app:cardBackgroundColor="@android:color/background_light"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintHorizontal_bias="0.0"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent"/>

                <com.rd.PageIndicatorView
                    android:id="@+id/pageIndicatorView"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:layout_marginBottom="8dp"
                    app:layout_constraintBottom_toBottomOf="@id/viewPager"
                    app:layout_constraintEnd_toEndOf="@+id/viewPager"
                    app:layout_constraintStart_toStartOf="parent"
                    app:piv_animationType="scale"
                    app:piv_dynamicCount="true"
                    app:piv_interactiveAnimation="true"
                    app:piv_viewPager="@id/viewPager"/>

            </android.support.constraint.ConstraintLayout>

            <android.support.v7.widget.RecyclerView
                android:id="@+id/home_recycler_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:nestedScrollingEnabled="false"
                android:visibility="visible"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/pageIndicatorView"/>


        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:indeterminate="true"/>

</RelativeLayout> 

Here MainActivity code:

class  MainActivity : BaseActivity(), BottomNavigationView.OnNavigationItemSelectedListener, MainActivityView.View {
    private val presenter = MainActivityPresenter()
    private val TAG = this::class.java.simpleName

    private val homeFragment = HomeFragment()
    private val exploreFragment = ExploreFragment()
    private val feedsFragment = FeedsFragment()

    private var currentTabTag = "home"

    override fun onNavigationItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.home -> openFragment(homeFragment, "home")
            R.id.explore -> openFragment(exploreFragment, "explore")
            R.id.feeds -> openFragment(feedsFragment, "feeds")
        }

        return true
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        presenter.attachView(this)

        if (savedInstanceState != null || !Utils.isLogined) {
            if (savedInstanceState != null) {
                currentTabTag = savedInstanceState.getString("currentTab")
                if (Utils.userInfo != null && Utils.userInfo?.data?.user?.photo != null) Glide.with(this).load(Utils.userInfo!!.data.user.photo).into(userPhoto)
                else userPhoto.setImageResource(RBase.drawable.account_default)
            }

            userPhoto.setOnClickListener {
                next()
            }

            userInterfaceInit()

        } else presenter.getUserInfo()
    }

    private fun userInterfaceInit() {
        navigationView.setOnNavigationItemSelectedListener(this)
        navigationView.menu.getItem(0).isChecked = true

//        search_field.text = SpannableStringBuilder("")
        search.setOnClickListener{
            if(search_field.text.toString() != "") {
                val intent = Intent(this, SearchActivity::class.java)
                intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
                intent.putExtra("query", search_field.text.toString())
                startActivity(intent)
            }
        }

        search_field.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                if(search_field.text.toString() != "") {
                    val intent = Intent(this, SearchActivity::class.java)
                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
                    intent.putExtra("query", search_field.text.toString())
                    startActivity(intent)
                }
                return@OnEditorActionListener true
            }
            false
        })

        Log.d(TAG, currentTabTag)

        when (currentTabTag) {
            "home" -> openFragment(homeFragment, "home")
            "explore" -> openFragment(exploreFragment, "explore")
            "feeds" -> openFragment(feedsFragment, "feeds")
        }


        userPhoto.setOnClickListener {
            next()
        }
        setSupportActionBar(toolbar)
        supportActionBar!!.title = ""
    }

    private fun openFragment(fragment: Fragment, tag: String) {
        val transaction = fragmentManager.beginTransaction()
        transaction.replace(R.id.container, fragment, tag)
        currentTabTag = tag
        if (!isFinishing &&!isDestroyed) transaction.commitAllowingStateLoss()
    }

    override fun onSaveInstanceState(outState: Bundle?) {
        super.onSaveInstanceState(outState)
        outState!!.putString("currentTab", currentTabTag)
    }

    override fun showUserInfo() {
        if (Utils.userInfo != null && Utils.userInfo!!.data.user.photo != null) Glide.with(this).load(Utils.userInfo!!.data.user.photo).into(userPhoto)
        else userPhoto.setImageResource(RBase.drawable.account_default)
        userInterfaceInit()
    }

    override fun showMessage(resId: Int) {
        //Toast.makeText(applicationContext, resId, Toast.LENGTH_LONG).show()
    }

    override fun onDestroy() {
        super.onDestroy()
        presenter.detachView()
        presenter.destroy()
    }
Vlad Kramarenko
  • 266
  • 3
  • 18
  • is commented toast leaks or there's an other one i'm missing? – savepopulation Oct 19 '18 at 11:42
  • Commented toast leaks – Vlad Kramarenko Oct 19 '18 at 14:13
  • are you sure toast causes the leak? because there're no referecens to activity with toast. you're using application context – savepopulation Oct 19 '18 at 14:25
  • I know that I put application context not activity -- it's main purpose in asking this question. I think that android automatically put my activity as parent for toast – Vlad Kramarenko Oct 19 '18 at 15:03
  • LeakCanary found this same issue in the app I am working on. Did you determine what the issue was? – dazza5000 Dec 31 '18 at 17:31
  • Check that you're not using `applicatoinContext` on Toast you're displaying. It might get leaked, rather than use your **activity context**. – Jeel Vankhede May 13 '19 at 06:08
  • @JeelVankhede, Toast is in LinearLayout, that is never used in Activity. Where comes the LinearLayout from? – anatoli May 13 '19 at 08:18
  • Is it possible that you're calling showMessage() from a background operation callback? If so then maybe you're keeping an implicit reference to MainActivity in an anonymous callback class and you're calling it after onDestroy? – Wojciech Sadurski May 13 '19 at 09:19
  • the toast and linear layout leak comes from leak canary. Try to use android studio 3.4 with the latest leak-canary 2.0-alpha-1 version. – edhair May 13 '19 at 13:37
  • Where is the member variable `applicationContext` defined? I don't see it in the code you posted. Is this defined in `BaseActivity`? Please add the code where this is defined. – David Wasser May 15 '19 at 16:03
  • `BaseActivity` extends from `AppCompatActivity`, and as we all know `AppCompatActivity` has field `applicationContext` – Vlad Kramarenko May 15 '19 at 17:40

1 Answers1

5

Looks like your toast lived longer than parent activity. Try to use Application as a Context. Like in your commented code:

Toast.makeText(applicationContext, resId, Toast.LENGTH_LONG).show()
ckesc
  • 511
  • 4
  • 16