react-native-reanimated icon indicating copy to clipboard operation
react-native-reanimated copied to clipboard

Weak performance/ UI freeze on Android

Open passengerV opened this issue 2 years ago • 8 comments

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);

  1. At least attach scrollTranslationStyle to an animated View.
  2. Start the animation with the startAnimation function.
  3. 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

passengerV avatar Jun 28 '23 14:06 passengerV

It's freezes to forewer?

XantreDev avatar Jun 29 '23 09:06 XantreDev

@XantreGodlike No, it last couple seconds (3 to ~10s).

passengerV avatar Jun 29 '23 10:06 passengerV

same for me with debug and release modes in 3.2.0, its freezes on android emulator (didn't check on devices)

drgrey87 avatar Jun 30 '23 09:06 drgrey87

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).

a-vikor avatar Jun 30 '23 10:06 a-vikor

Its freezing up for me as well however on the debug release

oiver555 avatar Jul 19 '23 01:07 oiver555

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):

  1. 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.

  1. Remove the inverted property from target FlatList.
  2. 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.

  1. Once done, adjust the entering animations (if you have them defined on your components), so SlideInDown will become SlideInUp and so on.
  2. 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

passengerV avatar Jul 19 '23 13:07 passengerV

Still, the issue is relevant. I have updated the footages and the repository. If I can somehow help you out here, please ping me.

passengerV avatar Jul 25 '23 14:07 passengerV

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.

bfricka avatar Jun 25 '24 23:06 bfricka