Skip to content

Image not releasing memory when dynamically changing source URI #21902

@waqas19921

Description

@waqas19921

Environment

React Native Environment Info:
    System:
      OS: macOS 10.14
      CPU: x64 Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
      Memory: 9.73 GB / 16.00 GB
      Shell: 3.2.57 - /bin/bash
    Binaries:
      Node: 8.12.0 - /usr/local/opt/node@8/bin/node
      Yarn: 1.9.4 - /usr/local/bin/yarn
      npm: 6.4.1 - /usr/local/opt/node@8/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0
    IDEs:
      Android Studio: 3.1 AI-173.4907809
      Xcode: 10.0/10A255 - /usr/bin/xcodebuild
    npmPackages:
      @storybook/react-native: ^3.4.11 => 3.4.11 
      react: 16.5.2 => 16.5.2 
      react-native: 0.57.2 => 0.57.2 
    npmGlobalPackages:
      react-native-cli: 2.0.1

Description

  • Image tag takes too much memory when i change its source on some button action.
  • I have to show and replace about 40 images in a single component.
  • After showing about 30 images app memory goes above 1GB
  • All images are just a few KBs in size
  • And Also it restarts phone upon testing on real Device
  • Placing images in javascript code have no issue in debug mode but crashes with excessive memory usage in production mode.
  • While placing images in iOS side with manual image references also produces excessive memory usage in dev mode.

Here is the memory screen shots:
screenshot 2018-10-23 at 8 21 49 am

Reproducible Demo

// Colors.js

const colors = {
  background: '#1F0808',
  clear: 'rgba(0,0,0,0)',
  facebook: '#3b5998',
  themeColor: '#3b5998',
  transparent: 'rgba(0,0,0,0)',
  silver: '#F7F7F7',
  steel: '#CCCCCC',
  error: 'rgba(200, 0, 0, 0.8)',
  ricePaper: 'rgba(255,255,255, 0.75)',
  frost: '#D8D8D8',
  cloud: 'rgba(200,200,200, 0.35)',
  windowTint: 'rgba(0, 0, 0, 0.4)',
  panther: '#161616',
  charcoal: '#595959',
  coal: '#2d2d2d',
  bloodOrange: '#fb5f26',
  snow: 'white',
  ember: 'rgba(164, 0, 48, 0.5)',
  fire: '#e73536',
  drawer: 'rgba(30, 30, 29, 0.95)',
  eggplant: '#251a34',
  border: '#483F53',
  banner: '#5F3E63',
  text: '#E0D7E5'
}

export default colors

\ Metrics.js

import {Dimensions, Platform} from 'react-native'

const { width, height } = Dimensions.get('window')

// Used via Metrics.baseMargin
const metrics = {
  marginHorizontal: 10,
  marginVertical: 10,
  section: 25,
  baseMargin: 10,
  doubleBaseMargin: 20,
  smallMargin: 5,
  doubleSection: 50,
  horizontalLineHeight: 1,
  screenWidth: width < height ? width : height,
  screenHeight: width < height ? height : width,
  navBarHeight: (Platform.OS === 'ios') ? 64 : 54,
  buttonRadius: 4,
  icons: {
    tiny: 15,
    small: 20,
    medium: 30,
    large: 45,
    xl: 50
  },
  images: {
    small: 20,
    medium: 40,
    large: 60,
    logo: 200
  }
}

export default metrics

\ ScreenStyles.js

import {StyleSheet} from 'react-native'
import {Metrics, Colors} from '../../Themes'

export default StyleSheet.create({
  container: {
    flex: 1,
    marginTop: Metrics.section
  },
  scrollContainer: {
    flex: 1,
    padding: Metrics.baseMargin
  },
  cardViewStyle: {
    flexWrap: 'wrap',
    marginVertical: Metrics.section
  },
  headerView: {
    flexWrap: 'wrap',
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between'
  },
  headerPhone: {
    flexWrap: 'nowrap',
    flexDirection: 'column',
    justifyContent: 'flex-start',
    backgroundColor: Colors.background
  },
  headerNameView: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    padding: Metrics.baseMargin,
    margin: Metrics.baseMargin
  },
  buttonsView: {
    flexWrap: 'wrap',
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginHorizontal: Metrics.baseMargin
  },
  headingText: {
    flex: 1,
    minHeight: 55,
    color: Colors.background,
    padding: Metrics.smallMargin
  },
  lockButton: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: Colors.themeColor,
    paddingVertical: Metrics.baseMargin - 1,
    paddingHorizontal: Metrics.baseMargin,
    marginLeft: Metrics.baseMargin
  },
  lockIcon: {
    color: Colors.snow
  },
  lockText: {
    color: 'white',
    paddingVertical: Metrics.smallMargin,
    paddingHorizontal: Metrics.smallMargin
  },
  arrowText: {
    color: Colors.background
  },
  audioIcon: {
    color: Colors.black
  },
  imageView: {
    alignItems: 'center',
    justifyContent: 'center'
  },
  image: {
    height: 150,
    width: 150,
    alignItems: 'center',
    justifyContent: 'center'
  },
  roundButtonsView: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    padding: Metrics.baseMargin,
    marginVertical: Metrics.doubleBaseMargin
  },
  circleButton: {
    height: 60,
    width: 60,
    borderRadius: 30,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: Colors.greenButton,
    marginHorizontal: Metrics.doubleBaseMargin
  },
  progressView: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    backgroundColor: Colors.transparent,
    padding: Metrics.baseMargin
  },
  otherView: {
    marginVertical: Metrics.doubleBaseMargin,
    padding: Metrics.doubleBaseMargin
  },
  inputView: {
    borderBottomWidth: 1,
    borderBottomColor: Colors.background,
    marginVertical: Metrics.doubleBaseMargin,
    marginHorizontal: Metrics.baseMargin
  },
  otherTextInputStyle: {
    flex: 1,
    height: 40
  },
  returnText: {
    flex: 1,
    textAlign: 'center',
    alignSelf: 'center',
    color: Colors.background,
    padding: Metrics.smallMargin
  }

})

// SelectionScreen.js

// @flow
import React, {Component} from 'react'
import Icon from 'react-native-vector-icons/MaterialIcons'
import {ScrollView, Text, View, TouchableOpacity, Dimensions, Image, TextInput} from 'react-native'

// Styles
import styles from './Styles/ScreenStyles'
import Metrics from '../Themes/Metrics'
import Colors from '../Themes/Colors'

let SYMPTOMS_ITEMS = [
  'fevers.jpg',
  'rigors.jpg',
  'fatigue.jpg',
  'weight_loss.jpg',
  'weight_gain.jpg',
  'swelling.jpg',
  'night_sweats.jpg',
  'headache.jpg',
  'dizziness.jpg',
  'neck_pain.jpg',
  'changes_vision.jpg',
  'hearing_loss.jpg',
  'ringing_ears.jpg',
  'swollen_glands.jpg',
  'painful_swallowing.jpg',
  'chest_pain.jpg',
  'cough.jpg',
  'sputum_production.jpg',
  'hemoptysis.jpg',
  'shortness_of_breath.jpg',
  'painful_breathing.jpg',
  'wheezing.jpg',
  'nausea.jpg',
  'vomiting.jpg',
  'loss_of_appetite.jpg',
  'bloating.jpg',
  'diarrhea.jpg',
  'jaundice.jpg',
  'confusion.jpg',
  'anxiety.jpg',
  'depression.jpg',
  'psychosis.jpg',
  'tingling_hands.jpg',
  'difficulty_sleeping.jpg',
  'muscle_aches.jpg',
  'joint_pain.jpg',
  'itching.jpg',
  'rash.jpg',
  'skin_dryness.jpg',
  'skin_changes.jpg',
  'hair_changes.jpg',
  'weakness.jpg',
  'seizure.jpg'
]

export default class SelectionScreen extends Component {
  constructor (props) {
    super(props)
    const {width} = Dimensions.get('window')
    this.state = {
      index: 0
    }
  }

  handleNextPress = () => {
    const {index} = this.state
    if (index < SYMPTOMS_ITEMS.length - 1) {
      this.setState({index: index + 1})
    }
  }

  handleBackPress =() => {
    const {index} = this.state
    this.setState({index: index - 1})
  }

  renderIconButton = (disable, icon, action, text) => {
    const style = [styles.lockButton, {backgroundColor: Colors.transparent}]
    return (
      <TouchableOpacity
        activeOpacity={0.8}
        style={style}
        disabled={disable}
        onPress={() => action()} >
        {text !== 'Next' && <Icon name={icon} size={20} color={Colors.blackText} />}
        <Text style={styles.arrowText}> {text.toUpperCase()}</Text>
        {text === 'Next' && <Icon name={icon} size={20} color={Colors.blackText} />}
      </TouchableOpacity>
    )
  }

  render () {
    const {index} = this.state

    return (
      <View style={styles.container}>
        <ScrollView style={styles.scrollContainer}>
          <View style={[styles.cardViewStyle]}>
            <View>

              <View style={styles.imageView}>
                <Image
                  source={{uri: SYMPTOMS_ITEMS[index]}}
                  style={styles.image} />
              </View>
            </View>
            <View style={styles.progressView}>
              {this.renderIconButton((index === 0), 'keyboard-arrow-left', this.handleBackPress, 'Back')}
              {this.renderIconButton(false, 'keyboard-arrow-right', this.handleNextPress, 'Next')}
            </View>
          </View>
        </ScrollView>
      </View>
    )
  }
}

Here above is the image tag used.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions