material-components-android
material-components-android copied to clipboard
MaterialShapeDrawable caused OOM
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
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?