Skip to content

Commit

Permalink
Merge branch 'example/performance-measurement'
Browse files Browse the repository at this point in the history
  • Loading branch information
hannojg committed Jun 7, 2022
2 parents 36dfbbb + 6cce23c commit 4eaeb29
Show file tree
Hide file tree
Showing 17 changed files with 365 additions and 26 deletions.
49 changes: 46 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
- 🤖 Especially on android, using rlottie can be more performant than [lottie-android](https://github.com/airbnb/lottie-android) (which is used by [lottie-react-native](https://github.com/lottie-react-native/lottie-react-native)):
- 📉 Using less CPU and RAM
- 🏃‍♂️ Puts less pressure on the UI/main Thread, ensuring 60 FPS even on low end devices
- Read more in [Performance Comparison]()
- Read more in [Performance Comparison](#performance-comparison)

## Usage

Expand Down Expand Up @@ -46,10 +46,53 @@ export default App;

## Performance Comparison

All data for the comparison can be found here: https://docs.google.com/spreadsheets/d/1Akz2As7HSJ7n9kmpIMYG966GY4QybOkJth5nNewYZhs/edit?usp=sharing

We compared react-native-rlottie with lottie-react-native:


### 🤖 Android

![react-native-rlottie vs lottie-react-native](./_img/android_metrics.png)

#### Key observations

- Running the animation consumes less CPU (-76%) and memory (-41%).
- The animation runs fluently in 60 FPS, whereas lottie-android causes the FPS to drop.
- This is due to the fact that the animation used for testing is a "complex" one, and running the animation with the platform's animated/art API is more expensive then to render the animation as bitmaps.

### 🍎 iOS

```
// TODO
// TODO: Comparison for ios
```

#### Key observations

- Running the animation with lottie-react-native adds CPU pressure. Using the example with an iPhone 7+ there is a ~10% CPU pressure during running the animation with lottie-rn, whereas with rlottie its 0%

### Overall key points

- Constantly running an animation with rlottie is in general more resource-saving, thus it can ensure more stable FPS, specially on low end devices.
- **HOWEVER**, the first time you render the animation rlottie will use _more_ resources than lottie-rn, as it needs to decode all frames for the first time. For large and complex animations this can be a severe factor. That's why, especially on iOS, its recommanded to pre-load an animation.
- In general, you should check the performance implications for each animation you are using, and test yourself if you are getting better results with lottie-rn or rlottie.

_Note:_ The performance results have been recorded with [react-native-performance-stats](https://github.com/skillnation/react-native-performance-stats).

### How to test yourself

<details>
<summary>Click to expand:</summary>

- Setup the example app on your machine
- You might want to replace the animation to test with your own. Simply replace the file `example/assets/icon_trophy.json`
- First, open the "Performance test: RLottie" screen in the example app and press start.
- Wait ~20 seconds until an array is printed to your console output
- Copy that data to a online service that convert JSON data in CSV: https://www.convertcsv.com/json-to-csv.htm
- Copy the resulting table and paste the data in a copy of the benchmark google sheet named earlier
- Repeat the same with the lottie-react-native screen
</details>

## Installation

```bash
Expand All @@ -71,7 +114,7 @@ npx pod-install
No additional steps for android are required, except when using the new react native architecture:

<details>
<summary>Click to expand for the instructions!</summary>
<summary>Click to expand for the instructions:</summary>

(_Note:_ This setup is required to to the fact that the on android Autolinking doesn't work with the new architecture out of the box. This procedure will change in the future.)

Expand Down
Binary file added _img/android_metrics.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletions android/src/main/java/nl/skillnation/rlottie/RLottieView.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.aghajari.rlottie.AXrLottieDrawable;
import com.aghajari.rlottie.AXrLottieImageView;

public class RLottieView extends AXrLottieImageView {
public boolean isAutoPlay = false;
private int decodeWidth, decodeHeight;
private String jsonString;
private boolean isInit;

public RLottieView(@NonNull Context context) {
super(context);
Expand All @@ -22,4 +26,71 @@ public RLottieView(@NonNull Context context, @Nullable AttributeSet attrs) {
public RLottieView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

//#region Layout size
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (decodeWidth == 0) {
decodeWidth = getWidth();
}
if (decodeHeight == 0) {
decodeHeight = getHeight();
}

maybeInitAnimation();
}

public void setDecodeWidth(int decodeWidth) {
this.decodeWidth = decodeWidth;
maybeInitAnimation();
}

public void setDecodeHeight(int decodeHeight) {
this.decodeHeight = decodeHeight;
maybeInitAnimation();
}
//#endregion

//#region Animation
public void setJSONStringSource(String jsonString) {
this.jsonString = jsonString;
maybeInitAnimation();
}

private String getCacheKey() {
if (jsonString == null || decodeWidth == 0 || decodeHeight == 0) {
return null;
}
return jsonString.hashCode() + "-" + decodeWidth + "-" + decodeHeight;
}

private void maybeInitAnimation() {
if (isInit) return;
String cacheKey = getCacheKey();
if (cacheKey == null) return;
isInit = true;

setLottieDrawable(
AXrLottieDrawable.fromJson(jsonString, cacheKey)
.setSize(decodeWidth, decodeHeight)
.build()
);

maybeAutoStartAnimation();
}
//#endregion

//#region AutoStart
public void setAutoPlay(boolean autoPlay) {
isAutoPlay = autoPlay;
maybeAutoStartAnimation();
}

private void maybeAutoStartAnimation() {
if (isAutoPlay && getLottieDrawable() != null && !getLottieDrawable().isRunning()) {
playAnimation();
}
}
//#endregion
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package nl.skillnation.rlottie;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.aghajari.rlottie.AXrLottie;
import com.aghajari.rlottie.AXrLottieDrawable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
Expand Down Expand Up @@ -35,19 +33,12 @@ protected RLottieView createViewInstance(@NonNull ThemedReactContext context) {

@ReactProp(name="isAutoPlay")
public void setIsAutoPlay(RLottieView view, boolean isAutoPlay) {
view.isAutoPlay = isAutoPlay;
view.setAutoPlay(isAutoPlay);
}

@ReactProp(name="src")
public void setSrc(RLottieView view, String jsonString) {
view.setLottieDrawable(
AXrLottieDrawable
.fromJson(jsonString, "cacheKeyTodo")
.setSize(700, 700)
.build()
);

if (view.isAutoPlay) view.playAnimation();
view.setJSONStringSource(jsonString);
}

@ReactProp(name="progress")
Expand All @@ -56,6 +47,16 @@ public void setProgress(RLottieView view, float progress) {
view.getLottieDrawable().setProgress(progress);
}

@ReactProp(name="decodeHeight")
public void setDecodeHeight(RLottieView view, int height) {
view.setDecodeHeight(height);
}

@ReactProp(name="decodeWidth")
public void setDecodeWidth(RLottieView view, int width) {
view.setDecodeWidth(width);
}

@Override
public void onDropViewInstance(@NonNull RLottieView view) {
super.onDropViewInstance(view);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,14 @@ public RLottieViewManager(ReactApplicationContext reactContext) {
protected ViewManagerDelegate<RLottieView> getDelegate() {
return mDelegate;
}

@Override
public void setDecodeWidth(RLottieView view, float value) {
this.setDecodeWidth(view, Math.round(value));
}

@Override
public void setDecodeHeight(RLottieView view, float value) {
this.setDecodeHeight(view, Math.round(value));
}
}
10 changes: 10 additions & 0 deletions example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {HomeScreen} from './src/screens/HomeScreen';
import {SimpleViewScreen} from './src/screens/SimpleViewScreen';
import {ManualControlScreen} from './src/screens/ManualControlScreen';
import {PerformanceRLottieScreen} from './src/screens/PerformanceRLottieScreen';
import {PerformanceLottieScreen} from './src/screens/PerformanceLottieScreen';

const Stack = createNativeStackNavigator();

Expand All @@ -17,6 +19,14 @@ const App = () => {
name="ManualControlScreen"
component={ManualControlScreen}
/>
<Stack.Screen
name="PerformanceRLottieScreen"
component={PerformanceRLottieScreen}
/>
<Stack.Screen
name="PerformanceLottieScreen"
component={PerformanceLottieScreen}
/>
</Stack.Navigator>
</NavigationContainer>
);
Expand Down
2 changes: 2 additions & 0 deletions example/android/app/src/main/jni/Android.mk
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ include $(REACT_ANDROID_DIR)/Android-prebuilt.mk

# Includes the MK file for `react-native-rlottie`
include $(NODE_MODULES_DIR)/react-native-rlottie/android/build/generated/source/codegen/jni/Android.mk
include $(NODE_MODULES_DIR)/react-native-performance-stats/android/build/generated/source/codegen/jni/Android.mk

include $(CLEAR_VARS)

Expand Down Expand Up @@ -38,6 +39,7 @@ LOCAL_SHARED_LIBRARIES := \
libjsi \
libreact_codegen_rncore \
libreact_codegen_rlottieview \
libreact_codegen_performancestats \
libreact_debug \
libreact_nativemodule_core \
libreact_render_componentregistry \
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "MainApplicationModuleProvider.h"

#include <rncore.h>
#include <performancestats.h>

namespace facebook {
namespace react {
Expand All @@ -12,11 +13,11 @@ std::shared_ptr<TurboModule> MainApplicationModuleProvider(
// either your application or from external libraries. The approach to follow
// is similar to the following (for a library called `samplelibrary`:
//
// auto module = samplelibrary_ModuleProvider(moduleName, params);
// if (module != nullptr) {
// return module;
// }
// return rncore_ModuleProvider(moduleName, params);
auto module = performancestats_ModuleProvider(moduleName, params);
if (module != nullptr) {
return module;
}

return rncore_ModuleProvider(moduleName, params);
}

Expand Down
21 changes: 21 additions & 0 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ PODS:
- hermes-engine (0.11.0)
- libevent (2.1.12)
- librlottie (0.2.1)
- lottie-ios (3.2.3)
- lottie-react-native (5.1.3):
- lottie-ios (~> 3.2.3)
- React-Core
- RCT-Folly (2021.06.28.00-v2):
- boost
- DoubleConversion
Expand Down Expand Up @@ -561,6 +565,13 @@ PODS:
- React-jsinspector (0.68.2)
- React-logger (0.68.2):
- glog
- react-native-performance-stats (0.2.3):
- RCT-Folly (= 2021.06.28.00-v2)
- RCTRequired
- RCTTypeSafety
- React-Codegen
- React-Core
- ReactCommon/turbomodule/core
- react-native-rlottie (0.0.2-alpha.2):
- librlottie
- RCT-Folly (= 2021.06.28.00-v2)
Expand Down Expand Up @@ -693,6 +704,7 @@ DEPENDENCIES:
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (~> 0.11.0)
- libevent (~> 2.1.12)
- lottie-react-native (from `../node_modules/lottie-react-native`)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
Expand All @@ -713,6 +725,7 @@ DEPENDENCIES:
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- react-native-performance-stats (from `../node_modules/react-native-performance-stats`)
- react-native-rlottie (from `../..`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
Expand All @@ -739,6 +752,7 @@ SPEC REPOS:
- hermes-engine
- libevent
- librlottie
- lottie-ios

EXTERNAL SOURCES:
boost:
Expand All @@ -751,6 +765,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/React/FBReactNativeSpec"
glog:
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
lottie-react-native:
:path: "../node_modules/lottie-react-native"
RCT-Folly:
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
RCTRequired:
Expand Down Expand Up @@ -783,6 +799,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
React-logger:
:path: "../node_modules/react-native/ReactCommon/logger"
react-native-performance-stats:
:path: "../node_modules/react-native-performance-stats"
react-native-rlottie:
:path: "../.."
react-native-safe-area-context:
Expand Down Expand Up @@ -832,6 +850,8 @@ SPEC CHECKSUMS:
hermes-engine: 84e3af1ea01dd7351ac5d8689cbbea1f9903ffc3
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
librlottie: 31aadd1f842008260fa7387f7fcb837b2fabeeea
lottie-ios: c058aeafa76daa4cf64d773554bccc8385d0150e
lottie-react-native: a501112fa980529ccb80b9f3ee117a7f98c6af3a
RCT-Folly: 4d8508a426467c48885f1151029bc15fa5d7b3b8
RCTRequired: 3e917ea5377751094f38145fdece525aa90545a0
RCTTypeSafety: c43c072a4bd60feb49a9570b0517892b4305c45e
Expand All @@ -848,6 +868,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: b7b553412f2ec768fe6c8f27cd6bafdb9d8719e6
React-jsinspector: c5989c77cb89ae6a69561095a61cce56a44ae8e8
React-logger: a0833912d93b36b791b7a521672d8ee89107aff1
react-native-performance-stats: 4f4206e531ed7c9c0713f3cba4a13110c9a46744
react-native-rlottie: 3115ddd1c58993f48e173a1482122f0a1d648b11
react-native-safe-area-context: a9616f1fd257ff31946b518266a62f50dbcb3d5b
React-perflogger: a18b4f0bd933b8b24ecf9f3c54f9bf65180f3fe6
Expand Down
2 changes: 2 additions & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
"dependencies": {
"@react-navigation/native": "^6.0.10",
"@react-navigation/native-stack": "^6.6.2",
"lottie-react-native": "^5.1.3",
"react": "17.0.2",
"react-native": "0.68.2",
"react-native-performance-stats": "^0.2.3",
"react-native-rlottie": "link:../",
"react-native-safe-area-context": "^4.2.5",
"react-native-screens": "^3.13.1",
Expand Down
Loading

0 comments on commit 4eaeb29

Please sign in to comment.