MVVMCross Nested Recyclerview Out of Memory Issue

2019-05-23 09:14发布

问题:

I have tried to create nested recyclerview in combination with the mvvmcross-framework which actually worked quite easily, but now I'm getting out of memory exceptions.

Scenario

I have a list of around 3000 entries with complex and long names, which are divided by 8 categories. The user can select the entries via a checkbox to assign them to a task later on.

Even if it might sound like there are too many entries, it is actually quite easily to navigate through them, if the user has some background knowledge, so I would rather not change it at this point.

Code

First the simplified data models for the categories and the entries (channels):

    public class ChannelCategory 
{
    public ChannelCategory(String sName)
    {
        Name = sName;
        Channels = new List<Channel>();
    }

    public string Name { get; set; }
    public List<Channel> Channels { get; set; }
}

    public class Channel
{
    public string Name { get; protected set; 
    public bool Selected { get; set; }

    public Channel(String sName)
    {
        Name = sName;
        Selected = false;
    }
}

And then the axml files which are just getting mapped by MVVMCross:

fragment_channels.axml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <SearchView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:iconifiedByDefault="false"
        local:MvxBind="Query SearchString" />
    <MvxRecyclerView
        android:id="@+id/lvChannelList"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:scrollbars="vertical"
        android:cacheColorHint="#00000000"
        android:choiceMode="singleChoice"
        local:MvxItemTemplate="@layout/listitem_channelcategory"
        local:MvxBind="ItemsSource Categories" />
</LinearLayout>

listitem_channelcategory.axml

 <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/roundborder">
    <CheckBox
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:layout_gravity="center_horizontal"
        android:layout_marginRight="5dp"
        android:padding="10dp"
        android:textColor="@color/black"
        android:textStyle="bold"
        local:MvxBind="Text (Name+Channels.Count)" />
  <MvxRecyclerView
      android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:scrollbars="vertical"
        android:cacheColorHint="#00000000"
        android:choiceMode="singleChoice"
        local:MvxItemTemplate="@layout/listitem_channel"
        local:MvxBind="ItemsSource Channels" />
</LinearLayout>

listitem_channel.axml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/roundborder">
    <CheckBox
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:layout_gravity="center_horizontal"
        android:layout_marginRight="5dp"
        android:padding="10dp"
        android:textColor="@color/black"
        local:MvxBind="Text Name; Checked Selected" />
</LinearLayout>

Issue

So basically I have a ViewModel, which is assigned to a view (fragment_channels) displays a list of categories (listitem_categories), where each categorie displays a list of channels (listitem_channels). Everything handled with the MVVMCross-Framework. It works fine, until I scroll scroll down to another categorie, then it just freezes for a while and after a few times it crashes with an out of memory exception. Atm I can delay it by giving the inner recyclerviews a fixed size.

Does anyone have an idea what I could change or what am I doing wrong? Maybe I shouldn't just a nested recyclerview, but the handling to this point was really easy and intuitive.

MVVMCross Android Support Version and Co is 4.1.5, Xamarin Android Support Library is 23.3.0

StackTrace

04-22 12:17:23.207 I/MonoDroid( 7556): UNHANDLED EXCEPTION:
04-22 12:17:23.217 I/MonoDroid( 7556): Java.Lang.OutOfMemoryError: Failed to allocate a 36876 byte allocation with 14092 free bytes and 13KB until OOM
04-22 12:17:23.227 I/MonoDroid( 7556):   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Users/builder/data/lanes/3053/a94a03b5/source/mono/external/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143 
04-22 12:17:23.227 I/MonoDroid( 7556):   at Android.Runtime.JNIEnv.CallObjectMethod (IntPtr jobject, IntPtr jmethod, Android.Runtime.JValue* parms) [0x00064] in /Users/builder/data/lanes/3053/a94a03b5/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs:195 
04-22 12:17:23.227 I/MonoDroid( 7556):   at Android.Views.LayoutInflater+IFactory2Invoker.OnCreateView (Android.Views.View parent, System.String name, Android.Content.Context context, IAttributeSet attrs) [0x0008c] in /Users/builder/data/lanes/3053/a94a03b5/source/monodroid/src/Mono.Android/platforms/android-23/src/generated/Android.Views.LayoutInflater.cs:185 
04-22 12:17:23.227 I/MonoDroid( 7556):   at MvvmCross.Binding.Droid.Views.MvxLayoutInflater+DelegateFactory2.OnCreateView (Android.Views.View parent, System.String name, Android.Content.Context context, IAttributeSet attrs) [0x00020] in <filename unknown>:0 
04-22 12:17:23.227 I/MonoDroid( 7556):   at MvvmCross.Binding.Droid.Binders.MvxLayoutInflaterCompat+FactoryWrapper2.OnCreateView (Android.Views.View parent, System.String name, Android.Content.Context context, IAttributeSet attrs) [0x00000] in <filename unknown>:0 
04-22 12:17:23.227 I/MonoDroid( 7556):   at Android.Views.LayoutInflater+IFactory2Invoker.n_OnCreateView_Landroid_view_View_Ljava_lang_String_Landroid_content_Context_Landroid_util_AttributeSet_ (IntPtr jnienv, IntPtr native__this, IntPtr native_parent, IntPtr native_name, IntPtr native_context, IntPtr native_attrs) [0x0002c] in /Users/builder/data/lanes/3053/a94a03b5/source/monodroid/src/Mono.Android/platforms/android-23/src/generated/Android.Views.LayoutInflater.cs:169 
04-22 12:17:23.227 I/MonoDroid( 7556):   at (wrapper dynamic-method) System.Object:bafdfe11-a146-4499-92d6-ece4428264a4 (intptr,intptr,intptr,intptr,intptr,intptr)
04-22 12:17:23.237 I/MonoDroid( 7556):   --- End of managed exception stack trace ---
04-22 12:17:23.237 I/MonoDroid( 7556): java.lang.OutOfMemoryError: Failed to allocate a 36876 byte allocation with 14092 free bytes and 13KB until OOM
04-22 12:17:23.237 I/MonoDroid( 7556):  at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:726)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:547)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:575)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:605)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.drawable.BitmapDrawable.updateStateFromTypedArray(BitmapDrawable.java:759)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.drawable.BitmapDrawable.inflate(BitmapDrawable.java:726)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java:1150)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.drawable.AnimatedStateListDrawable.parseItem(AnimatedStateListDrawable.java:493)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.drawable.AnimatedStateListDrawable.inflate(AnimatedStateListDrawable.java:396)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java:1150)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.graphics.drawable.Drawable.createFromXml(Drawable.java:1063)
04-22 12:17:23.237 I/MonoDroid( 7556):  at android.content.res.Resources.loadDrawableForCookie(Resources.java:3719)
04-22 12:17:23.247 I/MonoDroid( 7556):  at android.content.res.Resources.loadDrawable(Resources.java:3603)
04-22 12:17:23.247 I/MonoDroid( 7556):  at android.content.res.TypedArray.getDrawable(TypedArray.java:762)
04-22 12:17:23.247 I/MonoDroid( 7556):  at android.widget.CompoundButton.<init>(CompoundButton.java:89)
04-22 12:17:23.247 I/MonoDroid( 7556):  at android.widget.CheckBox.<init>(CheckBox.java:72)
04-22 12:17:23.247 I/MonoDroid( 7556):  at android.widget.CheckBox.<init>(CheckBox.java:68)
04-22 12:17:23.247 I/MonoDroid( 7556):  at android.support.v7.widget.AppCompatCheckBox.<init>(AppCompatCheckBox.java:58)
04-22 12:17:23.247 I/MonoDroid( 7556):  at android.support.v7.widget.AppCompatCheckBox.<init>(AppCompatCheckBox.java:54)
04-22 12:17:23.247 I/MonoDroid( 7556):  at android.support.v7.app.AppCompatViewInflater.createView(AppCompatViewInflater.java:117)
04-22 12:17:23.247 I/MonoDroid( 7556):  at android.support.v7.app.AppCompatDelegateImplV7.createView(AppCompatDelegateImplV7.java:972)
04-22 12:17:23.247 I/MonoDroid( 7556):  at android.support.v7.app.AppCompatDelegateImplV7.onCreateView(AppCompatDelegateImplV7.java:1030)
04-22 12:17:23.257 I/MonoDroid( 7556):  at android.support.v4.view.LayoutInflaterCompatHC$FactoryWrapperHC.onCreateView(LayoutInflaterCompatHC.java:44)
04-22 12:17:23.257 I/MonoDroid( 7556):  at md59292f1b7dcad9ff99bb047144087b6e8.MvxLayoutInflaterCompat_FactoryWrapper2.n_onCreateView(Native Method)
04-22 12:17:23.257 I/MonoDroid( 7556):  at md59292f1b7dcad9ff99bb047144087b6e8.MvxLayoutInflaterCompat_FactoryWrapper2.onCreateView(MvxLayoutInflaterCompat_FactoryWrapper2.java:31)
04-22 12:17:23.257 I/MonoDroid( 7556):  at android.view.LayoutInflater$FactoryMerger.onCreateView(LayoutInflater.java:181)
04-22 12:17:23.257 I/MonoDroid( 7556):  at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:732)
04-22 12:17:23.257 I/MonoDroid( 7556):  at android.view.LayoutInflater.rInflate(LayoutInflater.java:813)
04-22 12:17:23.257 I/MonoDroid( 7556):  at android.view.LayoutInflater.inflate(LayoutInflater.java:511)
04-22 12:17:23.267 I/MonoDroid( 7556):  at android.view.LayoutInflater.inflate(LayoutInflater.java:415)
04-22 12:17:23.267 I/MonoDroid( 7556):  at md53ffd798107a36773482afea9a9f07756.MvxRecyclerAdapter.n_onCreateViewHolder(Native Method)
04-22 12:17:23.267 I/MonoDroid( 7556):  at md53ffd798107a36773482afea9a9f07756.MvxRecyclerAdapter.onCreateViewHolder(MvxRecyclerAdapter.java:49)
04-22 12:17:23.267 I/MonoDroid( 7556):  at android.support.v7.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:5482)
04-22 12:17:23.267 I/MonoDroid( 7556):  at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4707)
04-22 12:17:23.267 I/MonoDroid( 7556):  at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4617)
04-22 12:17:23.267 I/MonoDroid( 7556):  at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1994)
04-22 12:17:23.267 I/MonoDroid( 7556):  at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1390)
04-22 12:17:23.267 I/MonoDroid( 7556):  at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1353)
04-22 12:17:23.267 I/MonoDroid( 7556):  at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:574)
04-22 12:17:23.267 I/MonoDroid( 7556):  at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3028)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:2625)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.view.View.measure(View.java:18596)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5827)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1435)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.widget.LinearLayout.measureVertical(LinearLayout.java:721)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.widget.LinearLayout.onMeasure(LinearLayout.java:612)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.view.View.measure(View.java:18596)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.support.v7.widget.RecyclerView$LayoutManager.measureChildWithMargins(RecyclerView.java:7487)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1416)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1353)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.support.v7.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1180)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:1031)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.support.v7.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:4061)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.view.Choreographer$CallbackRecord.run(Choreographer.java:777)
04-22 12:17:23.277 I/MonoDroid( 7556):  at android.view.Choreographer.doCallbacks(Choreographer.java:590)
04-22 12:17:23.287 I/MonoDroid( 7556):  at android.view.Choreographer.doFrame(Choreographer.java:559)
04-22 12:17:23.287 I/MonoDroid( 7556):  at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:763)
04-22 12:17:23.287 I/MonoDroid( 7556):  at android.os.Handler.handleCallback(Handler.java:739)
04-22 12:17:23.287 I/MonoDroid( 7556):  at android.os.Handler.dispatchMessage(Handler.java:95)
04-22 12:17:23.287 I/MonoDroid( 7556):  at android.os.Looper.loop(Looper.java:145)
04-22 12:17:23.287 I/MonoDroid( 7556):  at android.app.ActivityThread.main(ActivityThread.java:5944)
04-22 12:17:23.287 I/MonoDroid( 7556):  at java.lang.reflect.Method.invoke(Native Method)
04-22 12:17:23.287 I/MonoDroid( 7556):  at java.lang.reflect.Method.invoke(Method.java:372)
04-22 12:17:23.287 I/MonoDroid( 7556):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1389)
04-22 12:17:23.287 I/MonoDroid( 7556):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1184)

TraceView

It doesn't seem to be connected with WRAP_CONTENT, it just seems to appear a lot quicker that way. So might be an issue with the RecyclerView itself or maybe MVVMCross, but I'm really not sure atm. Will try to isolate the issue later on.

It seems not directly related to the RecyclerView, because it happens with the ListView aswell, though I don't know how much those two are related since I'm kind of new to Android. So I guess it is either an Android/Monodroid Issue or MVVMCross.

回答1:

I used the solution "MvxItemTemplateSelector" which recently got added by "thefex" to MVVMCross, though it kinda means i have to flatten my data structure by hand (Categories and Channels to one layer).

https://github.com/MvvmCross/MvvmCross-AndroidSupport/pull/200

Thanks for this feature! Just wish the other way would work aswell, but maybe i can try to solve it when i got more time to spare.

Greetings Cyriac



回答2:

The problem is that when nesting a ListView or RecyclerView, it can't measure the height of the nested View.

Hence, it will attempt to layout every view in the nested RecyclerView. This means, if that nested item has a lot of items, then your app will run into severe issues.

You should really avoid nesting such types of views unless you provide more information for the Android system to possibly know how to measure the children.