facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
117.12k stars 24.07k forks source link

ListView leaks memory and doesn't clean up (Android) #12512

Closed jasongrishkoff closed 6 years ago

jasongrishkoff commented 7 years ago

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

booboothefool commented 7 years ago

Same issue, except my app is actually in production and all my Android users are pissed giving me 1 star reviews, some in particular complaining about the memory usage. 😝 The more I scroll down the list, the slower the app becomes. List items include Image and TouchableOpacity, which like the OP said, makes it even worse.

brentvatne commented 7 years ago

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.

booboothefool commented 7 years ago

Hey @brentvatne,

@jasongrishkoff mentioned FlatList didn't help the memory issues. I just tried it myself as well, and got similar results. I can see the windowing happening, however the same issue applies where the more I scroll down my list (infinite scrolling) to display more rows, the slower my app becomes.

Anything else worth trying?

brentvatne commented 7 years ago

@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

sahrens commented 7 years ago

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.

sahrens commented 7 years ago

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

jasongrishkoff commented 7 years ago

Right, so I've got FlatList working using the very basic example provided in FlatListExample.js + ListExampleShared.js. No other modules or fancy things in my project -- it's a fresh install. The only thing I did was remove the thumbnails from the example.

EXAMPLE 1 WITH VIRTUALIZED = TRUE AND fixedHeight = TRUE Initial app load: 78MB Two swipes of scrolling (takes me to item #148): 108MB An additional two swipes (takes me to #245): 131MB Two more swipes (takes me to #385): 138MB Two more swipes (takes me to #545): 132MB (woo, something flatlined!)

Okay from there, it tends to hover around 132MB -- which is too high and will get killed by Android as soon as I started to use other apps (I'm on a Nexus 5X, so not a bad phone). Sure enough, I opened Gmail, then WhatsApp, and my AwesomeProject was cleaned in the background.

Also note that scrolling was a bit choppy. I'd move down the feed and it'd sorta take a while to gather its positioning.

EXAMPLE 2 WITH VIRTUALIZED = TRUE AND fixedHeight = FALSE Initial app load: 72MB Two swipes of scrolling (takes me to item #143): 100MB An additional two swipes (takes me to #280): 121MB Two more swipes (takes me to #370): 137MB Two more swipes (takes me to #457): 136MB (woo, something flatlined!)

So, in both cases it stopped hemorrhaging memory around the ~137MB mark. But again, that's way too high.

booboothefool commented 7 years ago

Does this look normal to you guys?

(before and after scrolling an infinite list of user pictures)

screen shot 2017-02-24 at 12 20 29 pm

If you guys would like, I could just tell you what the app is so you can download it off the Google Play Store and see for yourself how it just becomes slower the more you scroll (email me at meatyoaker001@gmail.com).

jasongrishkoff commented 7 years ago

Wow, yours is using an incredible amount of memory -- but still shows the same trend that I'm experiencing. One thing I see at an extreme level on yours is the # of Views.

sahrens commented 7 years ago

@booboothefool that's with FlatList? Something is definitely broken there. Sample code would be helpful.

booboothefool commented 7 years ago

The previous screenshot was with ListView and my full app. It took a while, but I stripped down the code a lot to make this using FlatList (love the simple new props btw!), which includes getting rid of my images and just rendering text. I did notice a minor performance improvement. It's like my app slows down to the point where it's annoying and isn't smooth, but it doesn't slow down to the point where it's completely unusable wtf worst app ever 1 star memory leaks Q.Q /uninstall lol.

How does this look? Here are the results stripping out a bunch of stuff, using FlatList, before and after scrolling to the end of my list (2000+ results):

screen shot 2017-02-25 at 1 51 08 am

Sample code:

import Config from 'react-native-config';
import config from './config';
import React, { Component } from 'react';
import {
  View,
  Dimensions,
  TouchableOpacity,
} from 'react-native';
import {
  Container,
  Content,
  Text,
} from 'native-base';
import { Actions } from 'react-native-router-flux';
import ScrollableTabView, { ScrollableTabBar } from 'react-native-scrollable-tab-view';
import FlatList from 'react-native/Libraries/Experimental/FlatList';
import myTheme from './myTheme';
import EStyleSheet from 'react-native-extended-stylesheet';

const deviceWidth = Dimensions.get('window').width;
const deviceHeight = Dimensions.get('window').height;
const styles = EStyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F1F0FF',
    paddingBottom: '3.4rem',
  },
  card: {
    backgroundColor: 'white',
    borderRadius: 22,
  },
  cardWidth: {
    width: deviceWidth / 2 - (2 * 5),  // padding = # per row * cardHeight margin
  },
  cardHeight: {
    height: '35%',
    alignItems: 'center',
    margin: 5,
  },
  cardShadow: {
    shadowColor: '#3F51B5',
    shadowOffset: { width: 0, height: 3 },
    shadowOpacity: 0.3,
    shadowRadius: 3,
    elevation: 3,
  },
});

var algoliasearch = require('algoliasearch/reactnative');
var algoliasearchHelper = require('algoliasearch-helper');
var client = algoliasearch(Config.ALGOLIASEARCH_APPLICATION_ID, Config.ALGOLIASEARCH_API_KEY_SEARCH);

const helper = algoliasearchHelper(client, 'users', {
  hitsPerPage: 12,
});

const Match = ({ hit }) => {
  return (
    <TouchableOpacity style={[styles.cardWidth]} onPress={() => Actions.profile({ hit })}>
      <View style={[{ marginTop: 8 }, styles.cardHeight]}>
        <Text>{hit.Birthday}</Text>
      </View>
    </TouchableOpacity>
  );
};

class Tab extends Component {
  constructor(props) {
    super(props);
  }

  renderItemComponent({ item, index }) {
    return (
      <View key={item.objectID} style={[styles.card, styles.cardWidth, styles.cardHeight, styles.cardShadow]}>
        <Match
          hit={item}
        />
      </View>
    );
  }

  render() {
    return (
      <FlatList
        data={this.props.hits}
        ItemComponent={(item, index) => this.renderItemComponent(item, index)}
        numColumns={2}
        onEndReached={() => helper.setPage(helper.getPage() + 1).search()}
        windowSize={(deviceHeight * 0.35) * 2}  // 2 rows of cards, I also tried the default props
      />
    );
  }
}

class Browse extends Component {
  constructor(props) {
    super();

    this.state = {
      currentTab: 'discover',
      hits: {
        discover: [],
      },
    };

    helper.on('result', (content) => {
      let currentHits = this.state.hits[this.state.currentTab];
      this.setState({
        hits: {
          [this.state.currentTab]: content.page === 0 ? content.hits : currentHits.concat(content.hits),
        },
      });
    });
  }

  componentWillMount() {
    helper.setQueryParameter('aroundLatLng', '38.8993277,-77.0846061');
    helper.search();
  }

  componentWillUnmount() {
    helper.removeAllListeners('result');
  }

  render() {
    return (
      <Container style={styles.container} theme={myTheme}>
        <Content scrollEnabled={false} contentContainerStyle={{ flex: 1 }}>

          <ScrollableTabView
            initialPage={0}
            renderTabBar={() => <ScrollableTabBar />}
          >

            <Tab key="discover"
              tabLabel="Discover"
              hits={this.state.hits.discover}
            />

          </ScrollableTabView>

        </Content>
      </Container>
    );
  }
}

export default Browse;

UPDATE: Here is another example of my full app using FlatList: screen shot 2017-02-26 at 2 12 35 am

sahrens commented 7 years ago

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.

booboothefool commented 7 years ago

@sahrens I hope I'm that stupid. XD Maybe I was just imagining using the default props? :) Either way I think something changed:

Using this code:

import Config from 'react-native-config';
import config from './config';
import React, { Component } from 'react';
import {
  View,
  Dimensions,
  TouchableOpacity,
} from 'react-native';
import {
  Container,
  Content,
  Text,
} from 'native-base';
import { Actions } from 'react-native-router-flux';
import ScrollableTabView, { ScrollableTabBar } from 'react-native-scrollable-tab-view';
import FlatList from 'react-native/Libraries/CustomComponents/Lists/FlatList';
import myTheme from './myTheme';
import EStyleSheet from 'react-native-extended-stylesheet';

const deviceWidth = Dimensions.get('window').width;
const deviceHeight = Dimensions.get('window').height;
const styles = EStyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F1F0FF',
    paddingBottom: '3.4rem',
  },
  card: {
    backgroundColor: 'white',
    borderRadius: 22,
  },
  cardWidth: {
    width: deviceWidth / 2 - (2 * 5),  // padding = # per row * cardHeight margin
  },
  cardHeight: {
    height: '35%',
    alignItems: 'center',
    margin: 5,
  },
  cardShadow: {
    shadowColor: '#3F51B5',
    shadowOffset: { width: 0, height: 3 },
    shadowOpacity: 0.3,
    shadowRadius: 3,
    elevation: 3,
  },
});

var algoliasearch = require('algoliasearch/reactnative');
var algoliasearchHelper = require('algoliasearch-helper');
var client = algoliasearch(Config.ALGOLIASEARCH_APPLICATION_ID, Config.ALGOLIASEARCH_API_KEY_SEARCH);

const helper = algoliasearchHelper(client, 'users', {
  hitsPerPage: 12,
});

const Match = ({ hit }) => {
  return (
    <TouchableOpacity style={[styles.cardWidth]} onPress={() => Actions.profile({ hit })}>
      <View style={[{ marginTop: 8 }, styles.cardHeight]}>
        <Text>{hit.Birthday}</Text>
      </View>
    </TouchableOpacity>
  );
};

class Tab extends Component {
  constructor(props) {
    super(props);
  }

  _renderItemComponent = ({ item }) => {
    return (
      <View key={item.objectID} style={[styles.card, styles.cardWidth, styles.cardHeight, styles.cardShadow]}>
        <Match
          hit={item}
        />
      </View>
    );
  }

  render() {
    return (
      <FlatList
        data={this.props.hits}
        renderItem={this._renderItemComponent}
        onEndReached={() => helper.setPage(helper.getPage() + 1).search()}
        numColumns={2}
        debug={this.props.hits.length > 0}
      />
    );
  }
}

class Browse extends Component {
  constructor(props) {
    super();

    this.state = {
      currentTab: 'discover',
      hits: {
        discover: [],
      },
    };

    helper.on('result', (content) => {
      let currentHits = this.state.hits[this.state.currentTab];
      this.setState({
        hits: {
          [this.state.currentTab]: content.page === 0 ? content.hits : currentHits.concat(content.hits),
        },
      });
    });
  }

  componentWillMount() {
    helper.setQueryParameter('aroundLatLng', '38.8993277,-77.0846061');
    helper.search();
  }

  componentWillUnmount() {
    helper.removeAllListeners('result');
  }

  render() {
    return (
      <Container style={styles.container} theme={myTheme}>
        <Content scrollEnabled={false} contentContainerStyle={{ flex: 1 }}>

          <ScrollableTabView
            initialPage={0}
            renderTabBar={() => <ScrollableTabBar />}
          >

            <Tab key="discover"
              tabLabel="Discover"
              hits={this.state.hits.discover}
            />

          </ScrollableTabView>

        </Content>
      </Container>
    );
  }
}

export default Browse;

I made a video of the debug={true}: https://www.youtube.com/watch?v=wlQmzICT3j0 after watching it, I assume the green box is the window?

and here are the results shown at the end of the video: screen shot 2017-03-02 at 12 40 17 am

I definitely noticed a performance improvement. The memory and views are going up, however my app isn't turning to complete mush anymore. Overall, I think I can work with this.

Does everything look right to you from my code / video / screenshot? What about you, @jasongrishkoff ?

jasongrishkoff commented 7 years ago

@booboothefool your memory usage went from 186mb to 359mb -- that's very high, and it's likely that as soon as you move to another app, Android will automatically garbage collect your app.

booboothefool commented 7 years ago

Yeah, seems like I spoke a bit too soon. Noticed the app slowed down a ton once I added everything else back (images, ads, calculations e.g. Birthday -> Age). (I'll make another video when I wake up. The slowdown is noticeable by just adding images )

sahrens commented 7 years ago

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?

booboothefool commented 7 years ago

@sahrens Hello friend. Thanks for your response. ^^

I am using a Samsung Galaxy S6 running Android 6.

I tried adding some of my pictures back and besides that, the only code change to FlatList was:

windowSize={3}

I noticed that using a smaller windowSize instead of the default definitely reduced the lag with the scrolling. It scrolls smoothly for the most part. https://www.youtube.com/watch?v=JgkeojJLmmA Here's the data: screen shot 2017-03-02 at 9 07 46 pm It looks like the number of views definitely stays low even after all that scrolling, however the memory is unaffected.

Another issue I'm seeing is that using SeparatorComponent, even with a simple text separator, causes a lot of stuttering.

  _renderSeparatorComponent = () => {
    return (
      <Text>{'yurrrppp!! >.<"'}</Text>
    );
  }

https://www.youtube.com/watch?v=y1HDH5wkFT0

On my live app, I am rendering an ad every 12 items using ListView. With FlatList, I am finding myself running into a lot more trouble doing this than anticipated. XD

sahrens commented 7 years ago

Does ListView not use more memory than FlatList? Or is FlatList better on memory, but more sluggish than ListView? Wth a gigantic windowSize, like you had before, or with enableVirtualization={false}, they should have pretty similar performance. Also note that debug is a massive perf hit, so make sure to turn that off (and also make sure you are not in __DEV__ mode) when you are evaluating performance.

Some more perf tips:

jasongrishkoff commented 7 years ago

You're right -- FlatList is performing a heck of a lot better with DEV mode disabled. I created a release variant and am testing that on my Android device -- it's behaving a lot better, and the memory is 1) staying reasonable; 2) cleaning up a bit when activity stops (eg, app is backgrounded or I stop scrolling).

My React Native app is still hanging around 120mb, which puts my app as the highest non-Android user of memory -- a prime candidate for memory cleanup when necessary. Twitter opens up at 141mb and after scrolling a bit jumps to 160mb. After closing it, it drops down to 100mb. But... in the process of taking those memory snapshots, I noticed that Android cleaned up / closed my React Native app, which is the crux of the problem I'm having --- a music app shouldn't close in the background :(

From here, my focus will be on reducing the overall memory footprint of each item in my list. I'll keep an eye on general performance with the lists, but otherwise, this seems to be performing significantly better with regards to stemming leaks!

On Fri, Mar 3, 2017 at 6:44 AM, Spencer Ahrens notifications@github.com wrote:

Does ListView not use more memory than FlatList? Or is FlatList better on memory, but more sluggish than ListView? Wth a gigantic windowSize, like you had before, or with enableVirtualization={false}, they should have pretty similar performance. Also note that debug is a massive perf hit, so make sure to turn that off (and also make sure you are not in DEV mode) when you are evaluating performance.

Some more perf tips:

  • Try implementing shouldItemUpdate. I don't know what your helper thing is doing, but ideally you can do === reference checks on the results it gives back to tell if they have changed, otherwise you might want to compare the object ids if the results themselves are unlikely to change? Depends on your product logic.
  • Shadows can be quite expensive to render - does it help to get rid of them? If it helps a lot, then you should use a shadow image asset instead of generating them programatically.
  • What does it mean for your card height to be 35% when they are inside an infinite list? That seems a little weird and might affect perf?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/facebook/react-native/issues/12512#issuecomment-283864137, or mute the thread https://github.com/notifications/unsubscribe-auth/AAW6U7XU4YziKnZ_QF5jrMmWgbwMyDpwks5rh5qcgaJpZM4MIds6 .

-- Jason Grishkoff Founder of Indie Shuffle https://www.indieshuffle.com/ and SubmitHub https://www.submithub.com/

Twitter: @indieshuffle http://www.twitter.com/indieshuffle Facebook: facebook.com/indieshuffle http://www.facebook.com/indieshuffle Why we don't take premieres http://www.indieshuffle.com/news/premiering-songs-and-why-we-ve-stopped/

sahrens commented 7 years ago

For a music app there may be special APIs and maybe some native code you need to write to make it a truly great experience. For example, you could save state to disk and then completely toss the JS context and all your views on background and/or when you receive a memory warning and the music could keep playing with a native module.

On Mar 2, 2017, at 10:36 PM, Jason notifications@github.com<mailto:notifications@github.com> wrote:

You're right -- FlatList is performing a heck of a lot better with DEV mode disabled. I created a release variant and am testing that on my Android device -- it's behaving a lot better, and the memory is 1) staying reasonable; 2) cleaning up a bit when activity stops (eg, app is backgrounded or I stop scrolling).

My React Native app is still hanging around 120mb, which puts my app as the highest non-Android user of memory -- a prime candidate for memory cleanup when necessary. Twitter opens up at 141mb and after scrolling a bit jumps to 160mb. After closing it, it drops down to 100mb. But... in the process of taking those memory snapshots, I noticed that Android cleaned up / closed my React Native app, which is the crux of the problem I'm having --- a music app shouldn't close in the background :(

From here, my focus will be on reducing the overall memory footprint of each item in my list. I'll keep an eye on general performance with the lists, but otherwise, this seems to be performing significantly better with regards to stemming leaks!

On Fri, Mar 3, 2017 at 6:44 AM, Spencer Ahrens notifications@github.com<mailto:notifications@github.com> wrote:

Does ListView not use more memory than FlatList? Or is FlatList better on memory, but more sluggish than ListView? Wth a gigantic windowSize, like you had before, or with enableVirtualization={false}, they should have pretty similar performance. Also note that debug is a massive perf hit, so make sure to turn that off (and also make sure you are not in DEV mode) when you are evaluating performance.

Some more perf tips:

  • Try implementing shouldItemUpdate. I don't know what your helper thing is doing, but ideally you can do === reference checks on the results it gives back to tell if they have changed, otherwise you might want to compare the object ids if the results themselves are unlikely to change? Depends on your product logic.
  • Shadows can be quite expensive to render - does it help to get rid of them? If it helps a lot, then you should use a shadow image asset instead of generating them programatically.
  • What does it mean for your card height to be 35% when they are inside an infinite list? That seems a little weird and might affect perf?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/facebook/react-native/issues/12512#issuecomment-283864137, or mute the thread https://github.com/notifications/unsubscribe-auth/AAW6U7XU4YziKnZ_QF5jrMmWgbwMyDpwks5rh5qcgaJpZM4MIds6 .

-- Jason Grishkoff Founder of Indie Shuffle https://www.indieshuffle.com/ and SubmitHub https://www.submithub.com/

Twitter: @indieshuffle http://www.twitter.com/indieshuffle Facebook: facebook.com/indieshufflehttp://facebook.com/indieshuffle http://www.facebook.com/indieshuffle Why we don't take premieres http://www.indieshuffle.com/news/premiering-songs-and-why-we-ve-stopped/

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/facebook/react-native/issues/12512#issuecomment-283877501, or mute the threadhttps://github.com/notifications/unsubscribe-auth/ABcJx0BkpihFIdY6czPgFN0QPCCrXOYUks5rh7TAgaJpZM4MIds6.

jasongrishkoff commented 7 years ago

We're getting into unrelated territory, but how does one toss views and context on background? Just monitor AppState and set the main component to be null when state is background? Right now I'm using mobx + react-native-music-control to handle the playlist component, and it works just fine when the app is backgrounded. It's just that the app doesn't shed any memory -- it hangs around 100mb+, and I reckon I'd need to get it down to ~75mb to convince Android not to kill it quickly.

sahrens commented 7 years ago

That might help, yeah. You also might want to do some research on native Android development and might have to write some Java to get a world-class music player experience. Have you built anything similar with something like Cordova? How does it fair memory-wise, especially in the background?

sahrens commented 7 years ago

I think music playback should also keep your app prioritized and less likely to get killed by the OS in the background. Are you seeing otherwise? Might want to reach out to the react-native-music-control maintainers, or ask on stack overflow.

jasongrishkoff commented 7 years ago

Edit Going to play around with this in release variant a bit more - results below were with dev.

The music player experience is pretty solid -- they've done a great job with the aforementioned library. My only problem right now is that my React Native app is quite lean (122 views on fully-loaded home screen according to meminfo), and yet still uses more memory than any other app on my Android device -- even when it's just a basic project (~70MB) with no FlatList or navigation or anything imported at all. A straight-up Hello is all. WhatsApp for comparison idles at 25MB and when active tends to peak around 75MB.

If my baseline for a nothing-app is 70mb, it's problematic because anything I add from there is going to increase it.

I also observe that when I've backgrounded my app, the amount of memory being used is barely reduced -- maybe 5% over the course of ~20 seconds. If I was using 120MB in the foreground, this means a background app ends up holding onto ~114MB, which puts it at the top of the stackrank of apps open on the Android device, and therefore #1 to be killed if the OS needs to free up memory for another app.

Setting the main component to = null brings the # of Views displayed in meminfo from 122 down to 7... and yet the memory still only goes down 5%.

Sorry for going so off-tangent given this was originally a ListView conversation. My testing so far indicates that the memory leak issue has been solved by FlatList, which eliminates a huuuge bottleneck. Just need to get the baseline down, or figure out how to shed memory when backgrounded.

sahrens commented 7 years ago

Also make sure you're doing an optimized release build of the native code when making comparisons.

On Mar 3, 2017, at 10:32 AM, Jason notifications@github.com<mailto:notifications@github.com> wrote:

The music player experience is pretty solid -- they've done a great job with the aforementioned library. My only problem right now is that my React Native app is quite lean (122 views on fully-loaded home screen according to meminfo), and yet still uses more memory than any other app on my Android device -- even when it's just a basic project (~70MB) with no FlatList or navigation or anything imported at all. A straight-up Hello is all. WhatsApp for comparison idles at 25MB and when active tends to peak around 75MB.

If my baseline for a nothing-app is 70mb, it's problematic because anything I add from there is going to increase it.

I also observe that when I've backgrounded my app, the amount of memory being used is barely reduced -- maybe 5% over the course of ~20 seconds. If I was using 120MB in the foreground, this means a background app ends up holding onto ~114MB, which puts it at the top of the stackrank of apps open on the Android device, and therefore #1https://github.com/facebook/react-native/pull/1 to be killed if the OS needs to free up memory for another app.

Setting the main component to = null brings the # of Views displayed in meminfo from 122 down to 7... and yet the memory still only goes down 5%.

Sorry for going so off-tangent given this was originally a ListView conversation. My testing so far indicates that the memory leak issue has been solved by FlatList, which eliminates a huuuge bottleneck. Just need to get the baseline down, or figure out how to shed memory when backgrounded.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/facebook/react-native/issues/12512#issuecomment-284032515, or mute the threadhttps://github.com/notifications/unsubscribe-auth/ABcJx8H8N4efr9sYqa-L3DAXK1HmtdmFks5riFyCgaJpZM4MIds6.

RichardLindhout commented 7 years ago

Maybe related to https://github.com/facebook/react-native/issues/11809?

joshjhargreaves commented 7 years ago

I played around with FlatList on iOS with 'full-screen' images in a list and a small windowSize of 3. Memory usage climbs linearly until it hits a threshold where it no longer increases. From enabling debug it it would appear it initially renders a few pages worth of views before the 'windowing' behavior kicks in (nice to see this behavior working :)); the debug visuals would suggest these initial views are never removed.

Video of the behavior: https://www.youtube.com/watch?v=s_YUMfb__ms

@sahrens I'm not sure if you've any thoughts? :)

edit: my mistake, it looks like I just described the VirtualizedList prop 'initialNumToRender' exactly!

sahrens commented 7 years ago

Sounds like you're still using the default initialNumToRender of 10 - try setting initialNumToRender={2} to reduce peak memory consumption. It's documented in the underlying VirtualizedListView, but I should probably add it to the FlatList documentation since it's a pretty important prop.

I should also add to the docs that the initialNumToRender items are never removed. This improves the perceived perf of scrolling back to the top of the content because you don't have to wait for it to render.

joshjhargreaves commented 7 years ago

Created a PR: https://github.com/facebook/react-native/pull/13167/files

hramos commented 6 years ago

Hi there! This issue is being closed because it has been inactive for a while. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. Either way, we're automatically closing issues after a period of inactivity. Please do not take it personally!

If you think this issue should definitely remain open, please let us know. The following information is helpful when it comes to determining if the issue should be re-opened:

If you would like to work on a patch to fix the issue, contributions are very welcome! Read through the contribution guide, and feel free to hop into #react-native if you need help planning your contribution.