Memory Leak em React Native

mensonones

Emerson Vieira

Posted on August 15, 2024

Memory Leak em React Native

O que é Memory Leak?

Memory leak (vazamento de memória) ocorre quando uma aplicação não libera a memória que não está mais em uso, fazendo com que o consumo de memória aumente continuamente. No React Native, isso pode causar problemas de desempenho, como travamentos, queda de FPS e, em casos graves, a aplicação pode ser fechada pelo sistema operacional.

Causas Comuns de Memory Leak em React Native

  1. Componentes não desmontados corretamente: Quando um componente continua a "viver" mesmo após ser removido da árvore de componentes.
  2. Event Listeners não removidos: Listeners continuam ativos mesmo após o componente ser desmontado.
  3. Timers (setTimeout ou setInterval): Se não forem limpos corretamente, esses timers continuarão ativos, mesmo quando não forem mais necessários.
  4. Async Operations (Promises, Fetch, Axios): Promessas ou requisições assíncronas que continuam sendo resolvidas ou rejeitadas mesmo depois de o componente ser desmontado.
  5. Referências circulares: Isso acontece quando dois objetos fazem referência um ao outro, impedindo o coletor de lixo de liberar a memória corretamente.

Como Identificar Memory Leaks?

Algumas técnicas para detectar vazamentos de memória em aplicações React Native incluem:

  • Uso do DevTools do React Native: Monitore o uso de memória no profiler.
  • Ferramentas de análise de desempenho: No Android, o Android Studio Profiler ajuda a rastrear o uso de memória.
  • Alertas de uso excessivo de memória: O monitoramento de erros em produção com ferramentas como Sentry pode ajudar a identificar padrões de aumento de memória.

Como Prevenir Memory Leaks?

  1. Desmonte corretamente os componentes: Certifique-se de limpar timers, listeners e operações assíncronas no componentWillUnmount (ou no retorno do useEffect).
  2. Uso do AbortController com Axios ou Fetch: Para garantir que uma requisição seja cancelada quando o componente desmonta.
  3. Gerencie corretamente dependências no useEffect: Assegure-se de que a lista de dependências do useEffect está correta para evitar chamadas desnecessárias e possíveis vazamentos.
  4. Limpe os estados: Ao desmontar o componente, garanta que as operações que dependem de um estado sejam adequadamente tratadas, evitando a atualização de estados em componentes desmontados.

Exemplo

// HomeScreen.tsx
import React, { useEffect, useState } from "react";
import { View, Button } from "react-native";
import checkLocationPermission from "./location";

const HomeScreen = ({ navigation }) => {
  const [hasPermission, setHasPermission] = useState(false);

  useEffect(() => {
    const checkPermission = async () => {
      const permission = await checkLocationPermission();
      setHasPermission(permission);
    };

    checkPermission();
  }, []);

  if (!hasPermission) {
    return null;
  }

  return (
    <View style={{ padding: 20 }}>
      <Button
        title="Ir para Mapa"
        onPress={() => navigation.navigate("Map")}
        disabled={!hasPermission}
      />
    </View>
  );
};

export default HomeScreen;
Enter fullscreen mode Exit fullscreen mode

O código de exemplo da HomeScreen realiza a verificação das permissões de acesso à localização do dispositivo e, em seguida, redireciona o usuário para a tela de Mapa (MapScreen).

// MapScreen.tsx
import React, { useEffect, useState, useCallback, useRef } from "react";
import { View, Button, Text } from "react-native";
import MapView, { Marker } from "react-native-maps";
import Geolocation from "@react-native-community/geolocation";

const MapScreen = ({ navigation }) => {
  const [location, setLocation] = useState({
    latitude: 37.78825,
    longitude: -122.4324,
    latitudeDelta: 0.005,
    longitudeDelta: 0.005,
  });
  const [errorMsg, setErrorMsg] = useState(null);
  const [loading, setLoading] = useState(true);
  const [isLocationValid, setIsLocationValid] = useState(false);
  const watchId = useRef(null);

  const getGpsLocation = useCallback(() => {
    console.log("Iniciando escuta de localização...");
    watchId.current = Geolocation.watchPosition(
      (position) => {
        if (position && position.coords) {
          const { latitude, longitude } = position.coords;
          setLocation({
            latitude,
            longitude,
            latitudeDelta: 0.005,
            longitudeDelta: 0.005,
          });
          setLoading(false);
          setIsLocationValid(true);
          console.log("Localização atualizada:", position);
        } else {
          console.error("Posição ou coordenadas não definidas");
        }
      },
      (error) => {
        console.error("Error getting GPS location:", error);
        setErrorMsg(error.message);
        setLoading(false);
      },
      {
        enableHighAccuracy: true,
        timeout: 30000,
        maximumAge: 10000,
        distanceFilter: 0,
        interval: 5000,
        fastestInterval: 5000,
      },
    );
  }, []);

  useEffect(() => {
    getGpsLocation();

    /* return () => {
      if (watchId.current !== null) {
        Geolocation.clearWatch(watchId.current);
        console.log('Escuta de localização removida');
        watchId.current = null; // Resetar para evitar limpeza dupla
      }
    }; */
    return () => {
      // A escuta de localização não será removida, causando um memory leak
      console.log(
        "Componente desmontado, mas a escuta de localização continua",
      );
    };
  }, [getGpsLocation]);

  return (
    <View style={{ flex: 1 }}>
      {isLocationValid ? (
        <MapView
          style={{ flex: 1 }}
          initialRegion={{
            latitude: location.latitude,
            longitude: location.longitude,
            latitudeDelta: location.latitudeDelta,
            longitudeDelta: location.longitudeDelta,
          }}
          loadingEnabled={loading}
          region={{
            latitude: location.latitude,
            longitude: location.longitude,
            latitudeDelta: location.latitudeDelta,
            longitudeDelta: location.longitudeDelta,
          }}
          showsUserLocation={true}
        >
          <Marker
            coordinate={{
              latitude: location.latitude,
              longitude: location.longitude,
            }}
            title="Sua localização"
          />
        </MapView>
      ) : (
        <View
          style={{ flex: 1, justifyContent: "center", alignItems: "center" }}
        >
          <Text>Obtendo localização...</Text>
        </View>
      )}
      <View style={{ padding: 20 }}>
        {errorMsg && <Text>{errorMsg}</Text>}
        <Button title="Voltar para Home" onPress={() => navigation.pop()} />
      </View>
    </View>
  );
};

export default MapScreen;
Enter fullscreen mode Exit fullscreen mode

O código exemplo de MapScreen, um memory leak ocorre porque a escuta de localização (Geolocation.watchPosition) não é limpa corretamente quando o componente MapScreen é desmontado. Isso faz com que a aplicação continue a escutar a localização mesmo quando o componente já não está em uso, resultando em um consumo desnecessário de memória.

Ao abrir a tela de Mapa e obter a localização, ao voltar para a tela Home, a coleta da posição continua sendo realizada. Isso pode ser observado no console através do seguinte log da tela de Mapa: console.log('Localização atualizada:', position);.

A correção é simples: basta limpar a escuta de localização no retorno da função useEffect:

useEffect(() => {
  getGpsLocation();
  return () => {
    if (watchId.current !== null) {
      Geolocation.clearWatch(watchId.current);
      console.log("Escuta de localização removida");
      watchId.current = null; // Resetar para evitar limpeza dupla
    }
  };
}, [getGpsLocation]);
Enter fullscreen mode Exit fullscreen mode

Projeto de Exemplo

ReactNativeMemoryLeak

Conclusão

Memory leaks podem impactar severamente a experiência do usuário em uma aplicação React Native. Identificar e mitigar esses vazamentos é essencial para manter uma performance estável e evitar problemas maiores, como travamentos ou encerramento inesperado da aplicação. Ao seguir boas práticas, como desmontar corretamente os componentes e cancelar operações assíncronas, você pode evitar a maioria dos problemas de vazamento de memória.

💖 💪 🙅 🚩
mensonones
Emerson Vieira

Posted on August 15, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related