react-native: ListView leaks memory and doesn't clean up (Android)

Hi there, I’m using RN 0.41.2 on an Android device (not emulator). I’ve created a really simple ListView component. As I scroll down this list, the memory used by the app increases linearly. If I background the app, this memory doesn’t get cleaned up at all.

While the code below is simple, you can imagine how out-of-hand this gets on a ListView that renders more-detailed rows (including Image and TouchableOpacity). The problem is that the app I’m building is a music streaming app. If I scroll through ~3 pages of tracks, it bring the app’s total consumption up to ~150MB of ram (from an initial boot of 90MB), which makes it the #1 candidate for Android to garbage collect if another app takes the foreground and requires more memory (eg, you want to browse Chrome while listening to the music app in the background). Without the ListView, my app uses up ~55MB – a number similar to the one generated by the simple component below.

I’ve tried this with the new/experimental FlatList component, and I get similar - if not worse - results as far as memory consumption. This is the main thing holding me back from rolling out my React Native version to production (on an app with 500,000+ installs).

import React, { Component } from 'react';
import {
  AppRegistry,
  Text,
  ListView
} from 'react-native';

var data = []
for (var ii = 0; ii < 1000; ii++) {
    data.push('row - ' + ii)
}

export default class AwesomeProject extends Component {
  constructor() {
    super();
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      dataSource: ds.cloneWithRows(data),
    };
  }

  render() {
    return (
      <ListView
        dataSource={this.state.dataSource}
        initialListSize={50}
        pageSize={50}
        renderRow={(rowData) => <Text>{rowData}</Text>}
      />
    );
  }
}

AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);

Screenshots below created using “adb shell dumpsys meminfo -a com.awesomeproject”

snapshot1 snapshot2 snapshot3 snapshot4

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 30 (14 by maintainers)

Commits related to this issue

Most upvoted comments

Yeah, 400MB is still a little high - what device are you running on? The heap and cache sizes will vary depending on the device memory. The view count is much more reasonable, but also a bit high. You can try dialing down the windowSize too. The default is 21 which is actually quite large.

But note that the live memory number is going to be much higher than the background number. That 359mb includes GPU texture buffers and other caches that should be cleared when the app is backgrounded. It also might take a while for the JS Garbage collector to trigger a cleanup and release the memory back to the operating system.

Memory is a very complex issue. Have you tried doing similar analysis with other apps? What happens if you scroll through the same amount of content in the other apps, like Twitter?

Hello! What you are describing here is (sadly) the intended behavior of react-native’s ListView component on iOS and Android – it does not recycle views or unmount anything that goes out of view, with the exception of images if you use removeClippedSubviews. I see you looked into FlatList @jasongrishkoff – that should help with this problem because it uses windowing – only the contents you can currently see and some buffer around the contents are actually rendered.

So windowSize is in units of visible length, typically screens, so where you set windowSize={(deviceHeight * 0.35) * 2} you’re setting the render window to hundreds of screens worth of content, which is over 10x the default value and basically zero windowing at all and would perfectly explain the behavior you’re seeing.

You have a comment that says you tried the default value - were the memory and view numbers exactly the same in that case? I would expect the default to work much better and save your memory. You can also set debug={true} and get a visualization overlay that should help show what’s going on.

As for slowness, I agree with @brentvatne that there is probably something else going on.

Your memory usage low enough that the garbage collector might not even be kicking in, depending on the device you’re running on. Try rendering more like 10000 rows and see if the memory continues to increase linearly or if it ever actually runs out of memory and crashes.

@booboothefool - is there anything else in your app that would make it slower? like maybe each row is subscribing to redux events? I’d try profiling your app to see where the slowness is coming from