Weak performance/ UI freeze on Android
Description
Dear Reader,
While attempting to make an animated audio slider by using withTiming, withDelay and useAnimatedStyle I have noticed bad performances on Android emulator and devices. I will provide a reproducible example below and a footage:
Issues were reported on a Release version. On iOS everything works fine.
renderToHardwareTextureAndroid doesn't help either.
Steps to reproduce
In some component do: const animationDurationInMS = 20 * 1000; const {startAnimation, scrollTranslationStyle} = useAnimations(animationDurationInMS);
- At least attach
scrollTranslationStyleto an animated View. - Start the animation with the
startAnimationfunction. - Inspect the performances on Android emulator/device.
The custom animation hook:
import {useCallback} from 'react';
import {PLAYBACK_CONTAINER_PADDING} from './styles';
import {
AnimationCallback,
Easing,
useAnimatedStyle,
useSharedValue,
withDelay,
withTiming,
} from 'react-native-reanimated';
import {Dimensions} from 'react-native';
const RESET_DELAY = 500;
const RESET_DURATION = 500;
const TO_START = 0;
const KNOB_SIZE = 16;
const SCREEN_WIDTH = Dimensions.get('window').width;
const SLIDER_BORDER_WIDTH = 0.6;
const maxRange =
SCREEN_WIDTH -
2 * PLAYBACK_CONTAINER_PADDING -
KNOB_SIZE +
SLIDER_BORDER_WIDTH;
export const useAnimations = (durationMs: number) => {
const offsetX = useSharedValue(0);
const scrollTranslationStyle = useAnimatedStyle(() => {
return {transform: [{translateX: offsetX.value}]};
}, []);
const progressStyle = useAnimatedStyle(() => {
return {
width: offsetX.value + KNOB_SIZE / 2,
};
}, []);
const handleAnimationEnd: AnimationCallback = useCallback(
(isFinished?: boolean) => {
'worklet';
if (!isFinished) {
return;
}
offsetX.value = withDelay(
RESET_DELAY,
withTiming(TO_START, {duration: RESET_DURATION}),
);
},
[offsetX],
);
const startAnimation = useCallback(() => {
offsetX.value = withTiming(
maxRange,
{
duration: durationMs,
easing: Easing.linear,
},
handleAnimationEnd,
);
}, [offsetX, durationMs, handleAnimationEnd]);
return {startAnimation, progressStyle, scrollTranslationStyle};
};
Snack or a link to a repository
https://github.com/passengerV/reanimated-android-performance
Reanimated version
3.4.0
React Native version
0.72.3
Platforms
Android
JavaScript runtime
Hermes
Workflow
React Native (without Expo)
Architecture
Paper (Old Architecture)
Build type
Debug mode
Device
Android emulator
Device model
Samsung Galaxy A53, Android 13
Acknowledgements
Yes
It's freezes to forewer?
@XantreGodlike No, it last couple seconds (3 to ~10s).
same for me with debug and release modes in 3.2.0, its freezes on android emulator (didn't check on devices)
If anyone wonders for a workaround: Implementing an approach with setInterval that updates shared value each second could be acceptable where the performance issues occurs. Obviously, the "animation" won't be fluid at all, but still won't decrease app performance and functionality will remain. This approach could be applied on Android devices where the animation would take longer than 10 seconds (as I noticed issues to raise after that).
Its freezing up for me as well however on the debug release
I will revalidate the issue again in next two days as it turned out that there is an issue with the android implementation of inverted FlatList causing performance issues (ANRs).
In case someone else have an inverted FlatList which causes performance issues on Android (React Native 0.72.3):
- Add following lines to your index.js file:
import ViewReactNativeStyleAttributes from 'react-native/Libraries/Components/View/ReactNativeStyleAttributes';
ViewReactNativeStyleAttributes.scaleY = true;
Note: scaleY is a deprecated property which still can be applied to native Android Views.
- Remove the
invertedproperty from target FlatList. - Assign the following custom style to your FlatList and to the rendered items (if they have to be inverted):
export const styles = StyleSheet.create({
inverted: IS_IOS ? {transform: [{scaleY: -1}]} : {scaleY: -1},
});
Note: Where IS_IOS is Platform.OS === 'ios' as a separate constant.
- Once done, adjust the entering animations (if you have them defined on your components), so
SlideInDownwill becomeSlideInUpand so on. - Adjust component vertical margins if needed (since scaleY: -1 is applied).
Footage after changes are applied: https://drive.google.com/file/d/1ZSx7j_X9Nqt71aixJ-xLes9Z1qpYoV3H/view?usp=sharing.
Relevant issues: https://github.com/facebook/react-native/issues/30034#issuecomment-1411922104 https://github.com/facebook/react-native/issues/35350 https://github.com/facebook/react-native/issues/30034
Still, the issue is relevant. I have updated the footages and the repository. If I can somehow help you out here, please ping me.
Still, the issue is relevant. I have updated the footages and the repository. If I can somehow help you out here, please ping me.
Same issue here. Performance on Android is very bad. More complex views are obviously worse, but even simple views are pretty bad. The issue seems to be rendering the contents of the View while animating. For me at least, animating out is smooth. To further prove this, I added an arbitrary delay to the entry animation using withDelay, and if the delay is long enough, the slide in animation is smooth.
Edit: The perf issue for entry animations doesn't seem to exist as long as the first layout has been calculated:
const initNow = useRef(performance.now())
<Animated.View entering={SlideInDown} onLayout={() => console.log(performance.now() - initNow.current)}>
If I tune the delay in SlideInDown to always be longer than this first onLayout call, I get smooth performance. It would be nice if, on Android at least, entry animations wait until first layout to run (meaning they sit at initialValues). I'm sure this is naive, and would have consequences on many types of animations and layouts, but it would be nice to at least be able to configure it for a particular animated component.