简体   繁体   中英

How to Improve performance for React Native geolocation running screen

Hi I'm currently trying to build a running app similar to the UnderArmor running app in React Native. The features work but after a couple of minutes of using the app on the simulator the computer starts to overheat and the app's performance significantly decreases, after a bit it no longer becomes responsive. Am I making too many calculations per frame? Thank for for your help

const { width, height } = Dimensions.get('window')
const LATITUDE_DELTA = 0.007;
const LONGITUDE_DELTA = 0.007;
const LATITUDE = 37.78825;
const LONGITUDE = -122.4324;
var email = "";

const RunMapScreen = () => {
  const paperTheme = useTheme();
  const [state, setState] = useState({
    isActive: false,
    close: true,
    routeCoordinates: [],
    distanceTravelled: 0,
    prevLatLng: {},
    latitude: LATITUDE,
    longitude: LONGITUDE,
    seconds: 0,
    now: moment(),
    then: moment(),
    timeElapsed: "00:00:00",
    startCounter: 0,
    speedCounter: 1,
    speed: 0,
    averageSpeed: 0,
    isModalVisible: false,
    email: "grt",
  });
  const [isModalVisible, setModalVisible] = useState(false);

  useEffect(() => {
    this.watchID = navigator.geolocation.watchPosition((position) => {

      handleUpdates(position, position.coords.latitude, position.coords.longitude, position.coords.speed);
    });

    return () => {
      navigator.geolocation.clearWatch(this.watchID);
    }
  }, [state]);

  const handleUpdates = (position, lat, long, speedVal) => {
    const newLatLngs = { latitude: position.coords.latitude, longitude: position.coords.longitude }
    const positionLatLngs = _.pick(position.coords, ['latitude', 'longitude']);
    setState(state => ({ ...state, latitude: lat, longitude: long }));
    if (state.isActive) {
      setState(state => ({
        ...state,
        routeCoordinates: state.routeCoordinates.concat(positionLatLngs),
        distanceTravelled: state.distanceTravelled + calcDistance(newLatLngs),
        prevLatLng: newLatLngs,
        now: moment(),
        timeElapsed: moment.utc(moment(state.now, "DD/MM/YYYY HH:mm:ss").diff(moment(state.then, "DD/MM/YYYY HH:mm:ss"))).format("HH:mm:ss"),
        speedCounter: state.speedCounter + 1,
        speed: speedVal,
        averageSpeed: ((state.averageSpeed * (state.speedCounter - 1) + state.speed) / state.speedCounter),
      }));
    }
  };

  const calcDistance = (newLatLng) => {
    const prevLatLng = state.prevLatLng;
    return (haversine(prevLatLng, newLatLng) || 0);
  };

  const openIsActive = () => {
    var now;
    if (!state.isActive && state.startCounter === 0) {
      setState(state => ({
        ...state,
        timeElapsed: moment.duration(state.now.diff(state.then)),
        then: moment(),
        startCounter: 1
      }));
    } else if (state.isActive && state.startCounter === 1) {
      now = { ...state.now };
    } else if (!state.isActive && state.startCounter === 1) {
      var then = { ...state.then };
      var diff = -state.now.diff(now);
      setState(state => ({ ...state, then: moment(then).add(diff) }));
    }
    setState(state => ({ ...state, isActive: !state.isActive }));
  }

  const saveData = () => {
    firebase.auth().onAuthStateChanged((user) => {
      var ref = firebase.database().ref(user.email.replace('.', ''));
      var key = firebase.database().ref(ref).push().key;
      firebase.database().ref(ref).child(key).set({
        email: user.email,
        distance: state.distanceTravelled,
        time: state.timeElapsed,
        speed: state.speed,
        averageSpeed: state.averageSpeed
      });
    });

    setState(state => ({
      ...state,
      isActive: false,
      routeCoordinates: [],
      distanceTravelled: 0,
      prevLatLng: {},
      seconds: 0,
      now: moment(),
      then: moment(),
      timeElapsed: "00:00:00",
      startCounter: 0,
      speedCounter: 1,
      speed: 0,
      averageSpeed: 0,
    }));
    navigator.geolocation.clearWatch(watchID);
    }));
  }

  const endRun = () => {
    if (state.isActive) {
      setState(state => ({ ...state, isActive: false }));
      Alert.alert(
        "End Run",
        "Do you want to end the run?",
        [{
          text: "Cancel",
          onPress: () => { setState(state => ({ ...state, isActive: true })) },
          style: "cancel"
        },
        {
          text: "OK",
          onPress: () => saveData()
        }], { cancelable: false });
    } else {
      Alert.alert(
        "Error",
        "You cannot end run that hasn't started",
        [{
          text: "OK",
          style: "cancel"
        }], { cancelable: false });
    }
  }

  const toggleModal = () => {
    setState(state => ({ ...state, isModalVisible: !state.isModalVisible }));
  };

  useEffect(() => {
    GetLocation.getCurrentPosition({
      enableHighAccuracy: true,
      timeout: 15000,
    })
      .then(pos => {
        setState(state => ({ ...state, latitude: pos.latitude, longitude: pos.longitude }));
      })
      .catch(error => {
        const { code, message } = error;
        console.log(code, message);
      });
  });

  const getMapRegion = () => ({
    latitude: state.latitude,
    longitude: state.longitude,
    latitudeDelta: LATITUDE_DELTA,
    longitudeDelta: LONGITUDE_DELTA
  });

  const { colors } = useTheme();

  const theme = useTheme();

  return (
    <View style={styles.container}>
      <Map
        routeCoordinates={state.routeCoordinates}
        getRegion={getMapRegion()} />

      <Button title="Show modal" onPress={toggleModal} />

      <Modal testID={'modal'}
        isVisible={state.isModalVisible}
        onSwipeComplete={() => { setModalVisible(false) }}
        swipeDirection={['up', 'left', 'right', 'down']}
        style={styles.modalView}>
        <ModalView
          timeElapsed={state.timeElapsed}
          distanceTravelled={state.distanceTravelled}
          speed={state.speed}
          averageSpeed={state.averageSpeed}
          toggleModal={toggleModal} />
      </Modal>
      <FloatingButton
        openIsActive={openIsActive}
        endRun={endRun}
        toggleModal={toggleModal}
        style={{ bottom: 100 }} />
    </View>
  );
};

export default RunMapScreen;

The map component

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

export const Map = memo(props => {
  const paperTheme = useTheme();

  var mapTheme = 'standard';
  if (paperTheme.dark === true) {
    mapTheme = 'hybrid';
  }
  var backGroundColor = '#404040';

  return (
    <MapView
      provider="google"
      style={styles.map}
      mapType='standard'
      showsUserLocation={true}
      followUserLocation={true}
      region={props.getRegion}
      tintColor='#404040'
      overlays={[{
        coordinates: props.routeCoordinates,
        strokeColor: '#F02A4B',
        lineWidth: 10,
      }]}
    >
      <Polyline
        coordinates={props.routeCoordinates}
        strokeColor='#F02A4B'
        strokeWidth={8}
      />
    </MapView>
  );
});

The main problem is probably that in useEffect , you start watching the position. The second argument you use is [state] . Inside of useEffect , you also update the state . This means that whenever [state] changes, your useEffect will run again! This means infinite loop you might end up with hundreds of listeners to the location in just a few minutes. To fix this, you probably want to pass an empty array as the second argument. Read more about it in the React hooks docs about the second parameter

Another minor error: you set this.watchId but use watchId in the cleanup function. You probably want to change the line with this.watchid to:

this.watchID = navigator.geolocation.watchPosition((position) => {

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM