Memory Leak em React Native
Emerson Vieira
Posted on August 15, 2024
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.
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
November 29, 2024
November 21, 2024
November 20, 2024