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
- Componentes não desmontados corretamente: Quando um componente continua a "viver" mesmo após ser removido da árvore de componentes.
- Event Listeners não removidos: Listeners continuam ativos mesmo após o componente ser desmontado.
- Timers (setTimeout ou setInterval): Se não forem limpos corretamente, esses timers continuarão ativos, mesmo quando não forem mais necessários.
- Async Operations (Promises, Fetch, Axios): Promessas ou requisições assíncronas que continuam sendo resolvidas ou rejeitadas mesmo depois de o componente ser desmontado.
- 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?
- Desmonte corretamente os componentes: Certifique-se de limpar timers, listeners e operações assíncronas no
componentWillUnmount
(ou no retorno douseEffect
). - Uso do
AbortController
com Axios ou Fetch: Para garantir que uma requisição seja cancelada quando o componente desmonta. - Gerencie corretamente dependências no
useEffect
: Assegure-se de que a lista de dependências douseEffect
está correta para evitar chamadas desnecessárias e possíveis vazamentos. - 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;
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;
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]);
Projeto de Exemplo
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.
Top comments (0)