material-components-android icon indicating copy to clipboard operation
material-components-android copied to clipboard

MaterialShapeDrawable caused OOM

Open ArcherEmiya05 opened this issue 1 year ago • 1 comments
trafficstars

Description: OOM with RecyclerView with MaterialCardView.

Expected behavior: No OOM

Source code:

XML layout

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="200dp"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_margin="@dimen/space_2"
    app:cardCornerRadius="5dp">

        <androidx.appcompat.widget.LinearLayoutCompat
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/postImage"
                android:layout_width="match_parent"
                android:layout_height="120dp"
                android:adjustViewBounds="true"
                android:src="@drawable/logo_placeholder"
                android:background="@color/colorWhiteDark_Primary" />

            <androidx.appcompat.widget.LinearLayoutCompat
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:padding="@dimen/space_8">

                <com.google.android.material.textview.MaterialTextView
                    android:id="@+id/postTitle"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:paddingBottom="@dimen/space_4"
                    android:textColor="@color/colorPrimaryDark_Accent"
                    android:textSize="@dimen/textSize16"
                    android:maxLines="2"
                    android:textStyle="bold"
                    android:ellipsize="end"
                    tools:text="@tools:sample/full_names" />

                <com.google.android.material.textview.MaterialTextView
                    android:id="@+id/postTime"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical"
                    android:ellipsize="end"
                    android:maxLines="1"
                    android:textSize="@dimen/textSize12"
                    tools:text="@tools:sample/cities"/>

                <com.google.android.material.textview.MaterialTextView
                    android:id="@+id/postDescription"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textSize="@dimen/textSize12"
                    android:maxLines="2"
                    android:ellipsize="end"
                    android:paddingVertical="@dimen/space_4"
                    tools:text="@tools:sample/lorem/random" />

            </androidx.appcompat.widget.LinearLayoutCompat>

        </androidx.appcompat.widget.LinearLayoutCompat>

</com.google.android.material.card.MaterialCardView>

RecyclerView Adapter

class HorizontalNewsAdapter(
    private val glide: RequestManager,
    private val itemListener: ItemListener
) : FilterableListAdapter<WordPressPostDataDomain, HorizontalNewsAdapter.ItemView>(DiffUtilNews()) {

    inner class ItemView(itemView: TrendingCardBinding) : RecyclerView.ViewHolder(itemView.root) {
        private val titleTxtV: MaterialTextView = itemView.postTitle
        private val dateTxtV: MaterialTextView = itemView.postTime
        private val descriptionTxtV: MaterialTextView = itemView.postDescription
        private val featuredImage: AppCompatImageView = itemView.postImage

        // Full update/binding
        fun bindFull(domain: WordPressPostDataDomain) {

            with(domain) {

                bindTextData(titleDomain.formattedTitle, formattedDate, contentDomain.strippedImageTagContent)

                glide
                    .load(featuredMediaUrl)
                    .placeholder(itemView.context.resToDrawable(R.drawable.logo_placeholder))
                    .centerCrop()
                    .into(featuredImage)

                // It is advisable to always use interface when working with `setOnClickListener`
                // in adapter to avoid outdated binding and data reference.
                // Passing position and using `adapter.currentList[position]` in the Activity/Fragment
                // is better than declaring `getItem(position)` inside `setOnClickListener`
                // Reference: https://stackoverflow.com/q/77308368/12204620
                itemView.setOnClickListener {
                    itemListener.onItemSelected(bindingAdapterPosition)
                }

            }

        }

        // Partial update/binding
        fun bindPartial(bundle: Bundle) {
            bindTextData(
                bundle.getString(DiffUtilNews.ARG_NEWS_TITLE),
                bundle.getString(DiffUtilNews.ARG_NEWS_DATE),
                bundle.getString(DiffUtilNews.ARG_NEWS_STRIPPED_CONTENT)
            )
        }

        private fun bindTextData(title: String?, date: String?, description: String?) {

            titleTxtV.text = title
            dateTxtV.text = date
            descriptionTxtV.text = description

        }

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemView {
        return ItemView(
            TrendingCardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        )
    }

    override fun onBindViewHolder(holder: ItemView, position: Int) {
        // Reference: https://forums.raywenderlich.com/t/speed-up-your-android-recyclerview-using-diffutil-raywenderlich-com/141338/8?u=archeremiya
        onBindViewHolder(holder, holder.bindingAdapterPosition, emptyList())
    }

    override fun onBindViewHolder(holder: ItemView, position: Int, payloads: List<Any>) {

        holder.apply {

            // For most cases `getBindingAdapterPosition()` is use as it provides the most up-to-date position considering pending changes.
            // It is also ideal for getting data from `getCurrentList()` as it returns the only position under this specific adapter and
            // not the entire adapters of a ConcatAdapter.
            // Reference: https://stackoverflow.com/a/63148812
            val domain = getItem(bindingAdapterPosition)

            if (payloads.isEmpty() || payloads.first() !is Bundle) {
                holder.bindFull(domain) // Full update/binding
            }
            else {
                val bundle = payloads.first() as Bundle
                holder.bindPartial(bundle) // Partial update/binding
            }

        }

    }

    // Required when setHasStableIds is set to true
    override fun getItemId(position: Int): Long {
        return currentList[position].id.toLong()
    }

    override fun onFilter(list: List<WordPressPostDataDomain>, constraint: String): List<WordPressPostDataDomain> {
        TODO("Not yet implemented")
    }

    fun interface ItemListener {

        fun onItemSelected(position: Int)

    }

}

OOM

05:47:18.336  W  Throwing OutOfMemoryError "Failed to allocate a 28 byte allocation with 3984 free bytes and 3KB until OOM" (recursive case)
05:47:18.338  W  "main" prio=5 tid=1 Runnable
05:47:18.338  W    | group="main" sCount=0 dsCount=0 obj=0x73c04710 self=0x7bce33ba00
05:47:18.338  W    | sysTid=3946 nice=-6 cgrp=default sched=0/0 handle=0x7bd184ffe8
05:47:18.338  W    | state=R schedstat=( 2083268596 213661445 6688 ) utm=183 stm=25 core=2 HZ=100
05:47:18.338  W    | stack=0x7fd8420000-0x7fd8422000 stackSize=8MB
05:47:18.338  W    | held mutexes= "mutator lock"(shared held)
05:47:18.338  W    at com.google.android.material.shape.MaterialShapeDrawable.<init>(MaterialShapeDrawable.java:126)
05:47:18.338  W    at com.google.android.material.shape.MaterialShapeDrawable.<init>(MaterialShapeDrawable.java:222)
05:47:18.338  W    at com.google.android.material.shape.MaterialShapeDrawable.<init>(MaterialShapeDrawable.java:213)
05:47:18.338  W    at com.google.android.material.card.MaterialCardViewHelper.<init>(MaterialCardViewHelper.java:143)
05:47:18.338  W    at com.google.android.material.card.MaterialCardView.<init>(MaterialCardView.java:174)
05:47:18.338  W    at com.google.android.material.card.MaterialCardView.<init>(MaterialCardView.java:160)
05:47:18.338  W    at java.lang.reflect.Constructor.newInstance!(Native method)
05:47:18.338  W    at android.view.LayoutInflater.createView(LayoutInflater.java:619)
05:47:18.338  W    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:764)
05:47:18.338  W    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704)
05:47:18.338  W    at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
05:47:18.338  W    at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
05:47:18.338  W    at com.sample.app.databinding.TrendingCardBinding.inflate(TrendingCardBinding.java:59)
05:47:18.338  W    at com.sample.app.presentation.adapters.HorizontalNewsAdapter.onCreateViewHolder(HorizontalNewsAdapter.kt:98)
05:47:18.338  W    at com.sample.app.presentation.adapters.HorizontalNewsAdapter.onCreateViewHolder(HorizontalNewsAdapter.kt:40)
05:47:18.338  W    at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7788)
05:47:18.338  W    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6873)
05:47:18.338  W    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6757)
05:47:18.338  W    at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6753)
05:47:18.338  W    at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2362)
05:47:18.338  W    at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1662)
05:47:18.338  W    at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1622)
05:47:18.338  W    at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:687)
05:47:18.338  W    at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4645)
05:47:18.338  W    at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:4022)
05:47:18.338  W    at android.view.View.measure(View.java:18788)
05:47:18.338  W    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
05:47:18.338  W    at androidx.appcompat.widget.LinearLayoutCompat.measureChildBeforeLayout(LinearLayoutCompat.java:1401)
05:47:18.338  W    at androidx.appcompat.widget.LinearLayoutCompat.measureVertical(LinearLayoutCompat.java:685)
05:47:18.338  W    at androidx.appcompat.widget.LinearLayoutCompat.onMeasure(LinearLayoutCompat.java:575)
05:47:18.338  W    at android.view.View.measure(View.java:18788)
05:47:18.338  W    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
05:47:18.338  W    at androidx.appcompat.widget.LinearLayoutCompat.measureChildBeforeLayout(LinearLayoutCompat.java:1401)
05:47:18.338  W    at androidx.appcompat.widget.LinearLayoutCompat.measureVertical(LinearLayoutCompat.java:685)
05:47:18.338  W    at androidx.appcompat.widget.LinearLayoutCompat.onMeasure(LinearLayoutCompat.java:575)
05:47:18.338  W    at android.view.View.measure(View.java:18788)
05:47:18.338  W    at androidx.core.widget.NestedScrollView.measureChildWithMargins(NestedScrollView.java:1921)
05:47:18.338  W    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
05:47:18.338  W    at androidx.core.widget.NestedScrollView.onMeasure(NestedScrollView.java:640)
05:47:18.338  W    at android.view.View.measure(View.java:18788)
05:47:18.338  W    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
05:47:18.338  W    at androidx.coordinatorlayout.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:760)
05:47:18.338  W    at com.google.android.material.appbar.HeaderScrollingViewBehavior.onMeasureChild(HeaderScrollingViewBehavior.java:100)
05:47:18.338  W    at com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior.onMeasureChild(AppBarLayout.java:2348)
05:47:18.338  W    at androidx.coordinatorlayout.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:831)
05:47:18.338  W    at android.view.View.measure(View.java:18788)
05:47:18.338  W    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
05:47:18.338  W    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
05:47:18.338  W    at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:145)
05:47:18.338  W    at android.view.View.measure(View.java:18788)
05:47:18.338  W    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
05:47:18.338  W    at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1465)
05:47:18.338  W    at android.widget.LinearLayout.measureVertical(LinearLayout.java:748)
05:47:18.338  W    at android.widget.LinearLayout.onMeasure(LinearLayout.java:630)
05:47:18.338  W    at android.view.View.measure(View.java:18788)
05:47:18.338  W    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
05:47:18.338  W    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
05:47:18.338  W    at android.view.View.measure(View.java:18788)
05:47:18.338  W    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
05:47:18.338  W    at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1465)
05:47:18.338  W    at android.widget.LinearLayout.measureVertical(LinearLayout.java:748)
05:47:18.338  W    at android.widget.LinearLayout.onMeasure(LinearLayout.java:630)
05:47:18.338  W    at android.view.View.measure(View.java:18788)
05:47:18.338  W    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5951)
05:47:18.338  W    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
05:47:18.338  W    at com.android.internal.policy.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2643)
05:47:18.338  W    at android.view.View.measure(View.java:18788)
05:47:18.338  W    at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2100)
05:47:18.338  W    at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1216)
05:47:18.338  W    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1452)
05:47:18.338  W    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)
05:47:18.338  W    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013)
05:47:18.338  W    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
05:47:18.338  W    at android.view.Choreographer.doCallbacks(Choreographer.java:670)
05:47:18.338  W    at android.view.Choreographer.doFrame(Choreographer.java:606)
05:47:18.338  W    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
05:47:18.338  W    at android.os.Handler.handleCallback(Handler.java:739)
05:47:18.338  W    at android.os.Handler.dispatchMessage(Handler.java:95)
05:47:18.338  W    at android.os.Looper.loop(Looper.java:148)
05:47:18.338  W    at android.app.ActivityThread.main(ActivityThread.java:5417)
05:47:18.338  W    at java.lang.reflect.Method.invoke!(Native method)
05:47:18.338  W    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
05:47:18.338  W    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
05:47:18.338  W  

Minimal sample app repro: N/A

Android API version: N/A

Material Library version: N/A (Sorry can't remember when this crash due to OOM happen, I kept this log long ago to submit a report but forgot to do so but surely before or during v1.10.0)

Device: N/A

ArcherEmiya05 avatar Mar 30 '24 18:03 ArcherEmiya05

Hi @ArcherEmiya05. Thanks for filing an issue!

In this case, we'd really need a sample that reproduces the issue. Are you able to create a simple app that reproduces the crash and confirm that MaterialCardView is the one allocating an unreasonable amount of memory?

hunterstich avatar Apr 01 '24 13:04 hunterstich