Ffimageloading: [Xamarin.Forms - Android] Java.Lang.OutOfMemoryError after loading multiple pages with multiple images

Created on 22 Jan 2019  路  10Comments  路  Source: luberda-molinet/FFImageLoading

Description

Multiple Java.Lang.OutOfMemoryError until the app crashes when loading multiple pages with multiple images.
It occurs only on Android, and it seems to concern "older" devices or those who less power, less memory.

Steps to Reproduce

Xamarin Forms app with a main tabbed page. There are 3 pages with one listview in each (each listview is paged, so I use an infinite scroll implementation and use a CacheStrategy = RecycleElement).
All listview items contain a small avatar pics loaded with FFImageLoading, and one of these listview displays also one big picture in each item, loaded with FFImageLoading.

All pages load correctly, and display pics. But after navigating between each page, and scroll on some, I got multiple Java.Lang.OutOfMemoryError until the app crashes.
If I don't use FFImageLoading, the problem does not occur but the app becomes slower.
It only occurs on Android devices.

Avatar size is approximately 300 to 500 ko. And big pics size is 500 to 1 Mo.

Expected Behavior

No error, and at least the memory and the GC be cleaned.

Actual Behavior

App crashes after loading approximately 50 pics between multiple pages.

XAML

Partial XAML of the ListView ViewCell with avatar and big pics:

<ViewCell BindingContextChanged="Handle_BindingContextChanged">
<!-- other XAML parts -->
<ffimageloading:CachedImage WidthRequest="55" HeightRequest="55" DownsampleToViewSize="true" DownsampleWidth="55" DownsampleHeight="55" ErrorPlaceholder="error.png" LoadingPlaceholder="loading.gif" Aspect="AspectFill" x:Name="PostCreatorImage" Grid.Column="0" HorizontalOptions="FillAndExpand" VerticalOptions="StartAndExpand">
                                            <ffimageloading:CachedImage.Transformations>
                                                <fftransformations:CircleTransformation />
                                            </ffimageloading:CachedImage.Transformations>
                                            <ffimageloading:CachedImage.GestureRecognizers>
                                                <TapGestureRecognizer Command="{Binding OpenUserProfileCommand}" />
                                            </ffimageloading:CachedImage.GestureRecognizers>
                                        </ffimageloading:CachedImage>
<!-- other XAML parts -->
<ffimageloading:CachedImage x:Name="PostAssetImage" Grid.Row="1" DownsampleToViewSize="true" DownsampleHeight="400" ErrorPlaceholder="error.png" LoadingPlaceholder="loading.gif" MinimumHeightRequest="300" HeightRequest="400" IsVisible="{Binding HasAssets}" Margin="-10, 0" Aspect="AspectFill">
                                                <ffimageloading:CachedImage.GestureRecognizers>
                                                    <TapGestureRecognizer Command="{Binding OpenAssetsCommand}" />
                                                </ffimageloading:CachedImage.GestureRecognizers>
                                            </ffimageloading:CachedImage>
<!-- other XAML parts -->
</ViewCell>

I tried to set only DownsampleToViewSize or only DownsampleHeight/DownsampleWidth ; I tried to add TransformPlaceholders="false" and FadeAnimations="false" but no change.

XAML behind code

As you suggested, I load pic with manual set during the OnBindingContextChanged of ViewCell

void Handle_BindingContextChanged(object sender, System.EventArgs e)
{
      var cell = sender as ViewCell;
      var postViewModel = cell?.BindingContext as PostViewModel;

      if (postViewModel == null)
           return;

      var creatorImage = cell.FindByName<CachedImage>("PostCreatorImage");
      if (creatorImage == null)
           return;

      creatorImage.Source = postViewModel.Post?.CreatorUser?.ThumbnailAvatarUrl;

      var cachedImage = cell.FindByName<CachedImage>("PostAssetImage");
      if (cachedImage == null)
           return;

      cachedImage.Source = postViewModel.Asset?.ThumbnailUrl;

      base.OnBindingContextChanged();
}

I tried to implement my own CacheKeyFactory. It seemed to be a little bit more efficient but the problem still occurs after.

Android MainActivity and MainApplication

In MainActivity, and in MainApplication, I override OnTrimMemory and OnLowMemory, but when I debug on physical device, it never stops on my breakpoint so it seems these methods are never call:

public override void OnTrimMemory(TrimMemory level)
{
      FFImageLoading.ImageService.Instance.InvalidateMemoryCache();
      GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
      base.OnTrimMemory(level);
}

public override void OnLowMemory()
{
       FFImageLoading.ImageService.Instance.InvalidateMemoryCache();
       GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
       base.OnLowMemory();
}

In the App.cs, I tried to set these properties (because I saw you did it in your samples, but I don't know what are use for):

FFImageLoading.Forms.CachedImage.FixedOnMeasureBehavior = true;
FFImageLoading.Forms.CachedImage.FixedAndroidMotionEventHandler = true;

Logs

First loads when everything is right:
[AbsListView] viewType is heaer or footer
[art] Starting a blocking GC Explicit
[Mono] GC_BRIDGE waiting for bridge processing to finish
[art] Explicit concurrent mark sweep GC freed 987(74KB) AllocSpace objects, 2(2MB) LOS objects, 22% free, 56MB/72MB, paused 683us total 27.404ms
[Mono] GC_TAR_BRIDGE bridges 294 objects 347 opaque 13 colors 294 colors-bridged 294 colors-visible 294 xref 10 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.12ms tarjan 0.42ms scc-setup 0.15ms gather-xref 0.02ms xref-setup 0.02ms cleanup 0.11ms
[Mono] GC_BRIDGE: Complete, was running for 33.09ms
[Mono] GC_MAJOR: (LOS overflow) time 37.74ms, stw 38.63ms los size: 4552K in use: 3436K
[Mono] GC_MAJOR_SWEEP: major size: 4368K in use: 3100K
Thread finished:  #12
[AbsListView] viewType is heaer or footer
[art] Starting a blocking GC Explicit
[art] Explicit concurrent mark sweep GC freed 18(1376B) AllocSpace objects, 3(7MB) LOS objects, 17% free, 73MB/89MB, paused 664us total 27.451ms
[Mono] GC_TAR_BRIDGE bridges 278 objects 332 opaque 15 colors 278 colors-bridged 278 colors-visible 278 xref 11 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.12ms tarjan 0.34ms scc-setup 0.17ms gather-xref 0.02ms xref-setup 0.02ms cleanup 0.08ms
[Mono] GC_BRIDGE: Complete, was running for 32.91ms
[Mono] GC_MAJOR: (LOS overflow) time 33.13ms, stw 33.89ms los size: 4552K in use: 3420K
[Mono] GC_MAJOR_SWEEP: major size: 4368K in use: 3085K
[ViewRootImpl] ViewPostImeInputStage processPointer 0
[TextView] setTypeface with style : 0
[TextView] setTypeface with style : 0
[TextView] setTypeface with style : 0
[ViewRootImpl] ViewPostImeInputStage processPointer 1
Thread started:  #14
[Mono] GC_BRIDGE waiting for bridge processing to finish
[art] Starting a blocking GC Explicit
[art] Explicit concurrent mark sweep GC freed 687(32KB) AllocSpace objects, 3(4MB) LOS objects, 13% free, 100MB/116MB, paused 778us total 29.272ms
[Mono] GC_TAR_BRIDGE bridges 377 objects 442 opaque 15 colors 377 colors-bridged 377 colors-visible 377 xref 11 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.12ms tarjan 0.52ms scc-setup 0.21ms gather-xref 0.02ms xref-setup 0.03ms cleanup 0.12ms
[Mono] GC_BRIDGE: Complete, was running for 36.25ms
[Mono] GC_MAJOR: (LOS overflow) time 44.17ms, stw 45.30ms los size: 4552K in use: 2687K
[Mono] GC_MAJOR_SWEEP: major size: 4496K in use: 3277K
[ViewRootImpl] ViewPostImeInputStage processPointer 0

And when errors occur:

[art] Alloc partial concurrent mark sweep GC freed 6(192B) AllocSpace objects, 0(0B) LOS objects, 0% free, 255MB/256MB, paused 761us total 25.514ms
[art] Starting a blocking GC Alloc
[art] Clamp target GC heap from 271MB to 256MB
[art] Alloc concurrent mark sweep GC freed 3(96B) AllocSpace objects, 0(0B) LOS objects, 0% free, 255MB/256MB, paused 769us total 33.270ms
[art] Forcing collection of SoftReferences for 2MB allocation
[art] Starting a blocking GC Alloc
[art] Clamp target GC heap from 271MB to 256MB
[art] Alloc concurrent mark sweep GC freed 3(96B) AllocSpace objects, 0(0B) LOS objects, 0% free, 255MB/256MB, paused 762us total 33.492ms
[art] Throwing OutOfMemoryError "Failed to allocate a 2560012 byte allocation with 728824 free bytes and 711KB until OOM"
Setting placeholder failed
Java.Lang.OutOfMemoryError: Failed to allocate a 2560012 byte allocation with 728824 free bytes and 711KB until OOM
  at Java.Interop.JniEnvironment+StaticMethods.CallStaticObjectMethod (Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x00069] in <da9f450baed342f3af31c42cec968688>:0 
  at Java.Interop.JniPeerMembers+JniStaticMethods.InvokeObjectMethod (System.String encodedMember, Java.Interop.JniArgumentValue* parameters) [0x00018] in <da9f450baed342f3af31c42cec968688>:0 
  at Android.Graphics.Bitmap.CreateBitmap (System.Int32[] colors, System.Int32 width, System.Int32 height, Android.Graphics.Bitmap+Config config) [0x00077] in <5f142c269d8a438c94480ac03744dec7>:0 
  at FFImageLoading.Decoders.GifDecoder+PlatformGifHelper.ToBitmapAsync (System.Int32[] data, System.Int32 width, System.Int32 height, System.Int32 downsampleWidth, System.Int32 downsampleHeight) [0x00000] in C:\projects\ffimageloading\source\FFImageLoading.Droid\Decoders\GifDecoder.cs:57 
  at FFImageLoading.GifHelperBase`1+<SetPixelsAsync>d__54[TNativeImageContainer].MoveNext () [0x00276] in C:\projects\ffimageloading\source\FFImageLoading.Common\Helpers\GifHelperBase.cs:186 
--- End of stack trace from previous location where exception was thrown ---
  at FFImageLoading.GifHelperBase`1+<ReadBitmapAsync>d__67[TNativeImageContainer].MoveNext () [0x00284] in C:\projects\ffimageloading\source\FFImageLoading.Common\Helpers\GifHelperBase.cs:626 
--- End of stack trace from previous location where exception was thrown ---
  at FFImageLoading.GifHelperBase`1+<ReadContentsAsync>d__64[TNativeImageContainer].MoveNext () [0x00083] in C:\projects\ffimageloading\source\FFImageLoading.Common\Helpers\GifHelperBase.cs:494 
--- End of stack trace from previous location where exception was thrown ---
  at FFImageLoading.GifHelperBase`1+<ReadGifAsync>d__56[TNativeImageContainer].MoveNext () [0x00140] in C:\projects\ffimageloading\source\FFImageLoading.Common\Helpers\GifHelperBase.cs:252 
--- End of stack trace from previous location where exception was thrown ---
  at FFImageLoading.Decoders.GifDecoder+<DecodeAsync>d__0.MoveNext () [0x0004a] in C:\projects\ffimageloading\source\FFImageLoading.Droid\Decoders\GifDecoder.cs:19 
--- End of stack trace from previous location where exception was thrown ---
  at FFImageLoading.Work.ImageLoaderTask`3+<GenerateImageAsync>d__104[TDecoderContainer,TImageContainer,TImageView].MoveNext () [0x00072] in C:\projects\ffimageloading\source\FFImageLoading.Common\Work\ImageLoaderTask.cs:280 
--- End of stack trace from previous location where exception was thrown ---
  at FFImageLoading.Work.ImageLoaderTask`3+<ShowPlaceholder>d__108[TDecoderContainer,TImageContainer,TImageView].MoveNext () [0x0037e] in C:\projects\ffimageloading\source\FFImageLoading.Common\Work\ImageLoaderTask.cs:486 
  --- End of managed Java.Lang.OutOfMemoryError stack trace ---
java.lang.OutOfMemoryError: Failed to allocate a 2560012 byte allocation with 728824 free bytes and 711KB until OOM
    at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
    at android.graphics.Bitmap.nativeCreate(Native Method)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:1048)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:1072)

Basic Information

  • Version with issue: 2.4.5.860-pre
  • Last known good version: I don't know, I discovered this error after publishing the app for the first time, and I didn't get this on test devices who are recent and powerful (Samsung Galaxy S8)
  • Platform: Xamarin Forms 3.4.0.1009999 / Android (5.1.1, 6.0.1, 7.0 and 8.0)
  • Devices : Samsung Galaxy J3 (2016), A3 (2016), Honor 5C (2016), Motorola One Power (2017)

Your plugin is very good. And it works perfectly on iOS. This is my only problem to get a stable app. Hope someone will get an idea.

Thanks for your help !

All 10 comments

I've got the same issue here, on a production app, since I've upgraded X.F to 3.4.0.1009999...
The problem is I can't reproduce the bug on any of my test phones, it only occurs on some of my client's phone (Samsung A3 2015). Very annoying...
Any clue ?

Hi @plsgard, did you tried with a older version of X.F ?

Hi @plsgard, did you tried with a older version of X.F ?

Not exactly. I didn't try recently with older version. I encountered a similar error few weeks ago on an older version of XF, but it appears on only one device, so I didn't work on it at that moment.
I'm gonna take a look when I can.

I could not try on previous version of XF, but I tried by removing FFImageLoading on most of the pages and I did not get OOM anymore. I saw that the memory free space changes but it does not go to OOM.

I also realized that my avatar thumbnail size is big (1000x1000px) while I downsample to 55px or 45px on most of the pages. Maybe it requires to much memory to downsample, even if I also specify the WidthRequest and HeightRequest on the component.

@daniel-luberda, did I do something wrong ?

Thanks for your help

@plsgard Have you checked out the section titled "Clear cache and memory considerations" in the wiki

Just came across it while trying to figure out another issue, not sure it is helps

@yonkahlon I did and try some implementations (take a look at Android MainActivity and MainApplication section in my first post).

I didn't try to implement my custom image view because I really want to avoid - and I don't have enough time to - implementing specific platform only to display images.

Unfortunately, I finally chose not to use this plugin for Android, despite the fact that it works perfectly on iOS.

Does it happen on latest prerelease? There are a lot of fixes / optimizations in it.

I'll close this one as there were many changes regarding memory cache in 2.4.5 (see release notes). Cheers.

Ok, I understand. I gonna try with the last release when I can. Actually it's working without the plugin, so unfortunately this is not a priority for now anymore.

I have exactly the same problem, I am using version 2.4.11.982.

<CollectionView x:Name="CollectionView" ItemsSource="{Binding Projects}" ItemsLayout="VerticalGrid, 2">
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <Grid Padding="10">
                        <Grid.GestureRecognizers>
                            <TapGestureRecognizer Command="{Binding Path=BindingContext.ProjectTapCommandAsync, Source={x:Reference CollectionView}}"
                                                  CommandParameter="{Binding Id}"/>
                        </Grid.GestureRecognizers>
                        <ffimageloading:CachedImage Source="{Binding Image}" HeightRequest="200"
                                                            IsOpaque="True"
                                                            FadeAnimationEnabled="False" 
                                                            TransformPlaceholders="False"/>
                    </Grid>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>

I also tested all the configurations, as described in the topic above and I was unable to solve the problem, as I understand it, garbate is not eliminating from memory the objects that have already been destroyed by the component.

@daniel-luberda

Was this page helpful?
0 / 5 - 0 ratings

Related issues

renzska picture renzska  路  15Comments

softsan picture softsan  路  32Comments

ericruelas picture ericruelas  路  15Comments

LeoJHarris picture LeoJHarris  路  22Comments

SemyonL picture SemyonL  路  20Comments