Rive Android

repository·master·Indexed 19 days ago

https://github.com/rive-app/rive-android

A lightweight runtime library for loading, displaying, and interacting with Rive animation files in Android applications. It supports real-time interactive graphics, state machines, and data binding. The library includes a C++ layer with RenderContext for managing graphics APIs like Vulkan and OpenGL ES, an AudioEngine for asset playback, and instrumentation tools for performance profiling via Perfetto and ATrace.

Tokens
8.1K
Snippets
26
Records
40
Agent score
68%

What's inside Rive Android

  1. Understand NativeObject ownership and dependencies

    master

    Rive uses a dependency-based ownership model for its C++ objects.

    • Ownership: When an object has a has dependency on another, the creator "owns" that dependency.
    • Release Mechanism: When a NativeObject calls release(), it first releases its reference to all its dependents. It then decreases its own reference counter. If the counter reaches 0, the object is disposed of and its JNI destructor is called.
    • Cascading Release: Releasing a high-level object can trigger a cascade of releases. For example, a RiveArtboardRenderer owns a File. Releasing the renderer releases the File, which in turn releases its own dependents (like Artboard or StateMachineInstance).

    Example Hierarchy:

    • RiveArtboardRenderer $\rightarrow$ owns $\rightarrow$ File $\rightarrow$ owns $\rightarrow$ Artboard $\rightarrow$ owns $\rightarrow$ StateMachineInstance $\rightarrow$ owns $\rightarrow$ SMIInput / LayerState.
  2. Avoid memory leaks with Rive Native Objects

    master

    Rive's core logic resides in C++. While many objects are automatically garbage collected, certain key classes require manual memory management to prevent leaks.

    Objects that require manual deletion:

    • File
    • Artboard
    • LinearAnimationInstance
    • StateMachineInstance

    If you are managing these objects manually (e.g., outside of RiveAnimationView), you must ensure they are properly released to avoid memory leaks.

  3. Configure build options for Rive Android

    master

    When building from source, you can use specific flags to customize the output AAR:

    Filter ABIs

    By default, the build produces four ABIs: armeabi-v7a, arm64-v8a, x86, and x86_64. To reduce size, you can filter for specific architectures using -PabiFilters.

    Exclude Audio Engine

    To minimize binary size by approximately 600kb, you can exclude the miniaudio engine (used for Rive file audio assets) using the -PnoAudio flag.

    # Build for a single architecture
    ./gradlew :kotlin:assembleRelease -PabiFilters=arm64-v8a
    
    # Build for multiple architectures (comma-separated)
    ./gradlew :kotlin:assembleRelease -PabiFilters="arm64-v8a,armeabi-v7a"
    
    # Build without the audio engine
    ./gradlew :kotlin:assembleRelease -PnoAudio
  4. Run the Rive Android example application

    master

    The app/ folder contains an example application demonstrating various Rive features (State Machines, Layout, Events, etc.).

    To run the example:

    1. In Android Studio, go to Build > Select Build Variant.
    2. Select the preview build variant. This variant uses the hosted Rive dependency from Maven.

    Note: If you are contributing to the library, use the debug build variant instead, which requires additional local setup.

  5. Build the Rive Android library from source

    master

    The build system uses Gradle, CMake, and Premake. To generate an Android Archive (AAR) file equivalent to the one published on Maven, use the Gradle CLI.

    Output location: kotlin/build/outputs/aar/kotlin-release.aar

    ./gradlew :kotlin:assembleRelease
  6. Implement a custom render loop with manual cleanup

    master

    If you are not using RiveAnimationView and need to implement a custom render loop, you must manage the lifecycle of File objects manually.

    To ensure proper cleanup, add the File to the Renderer's dependencies. This ensures that the File (and its dependents) will be cleaned up at the appropriate time, such as within your own onDetachedFromWindow() implementation.

  7. Profile Rive Android rendering with Perfetto Tracing

    master

    You can profile the Rive Android runtime rendering using a pre-configured Perfetto trace configuration located in rive-trace.textproto. This allows you to capture detailed performance data for your specific application package.

    Steps to profile your app:

    1. Configure the package name: Open tools/perfetto/rive-trace.textproto and update the atrace_apps field with your Android application's package name.
    2. Push the configuration: Transfer the modified config file to your device using ADB.
    3. Record the trace: Execute the perfetto command on the device to start recording.
    4. Retrieve the trace: Pull the resulting .pftrace file from the device to your local machine.
    5. Analyze: Upload the trace file to the Perfetto UI for visualization.
    # 1. Update atrace_apps in tools/perfetto/rive-trace.textproto with your package name
    
    # 2. Push the config to the device
    adb push tools/perfetto/rive-trace.textproto /data/local/tmp/rive-trace.textproto
    
    # 3. Record a trace
    adb shell perfetto -c /data/local/tmp/rive-trace.textproto -o /data/misc/perfetto-traces/rive-trace.pftrace
    
    # 4. Pull the trace
    adb pull /data/misc/perfetto-traces/rive-trace.pftrace .
    
    # 5. Open the trace in https://ui.perfetto.dev
  8. Manage Rive object lifecycles using RiveAnimationView

    master

    The RiveAnimationView is the recommended way to use Rive in Android because it abstracts memory management. It automatically allocates and de-allocates Native objects using Android's lifecycle hooks (onAttachedToWindow() and onDetachedFromWindow()).

    Key behaviors:

    • When the View is detached from the window, it automatically deletes the corresponding Rive File object and all derived objects (like Artboard or StateMachineInstance).
    • Warning: Do not use Native Rive objects outside the lifecycle of the View that created them. Accessing objects after RiveAnimationView.onDetachedFromWindow() will result in a RiveException because the underlying C++ objects have been deleted.

    If you encounter a RiveException when accessing C++ properties, it indicates the object is stale and must be replaced.

  9. Manage Rive file state with RiveFileController

    master

    The RiveFileController is the primary interface for managing a Rive file's lifecycle, including its artboards, animations, and state machines. It handles playback control, input queuing, and layout configuration (fit and alignment).

    Key responsibilities:

    • File & Artboard Management: Loading a File and selecting an Artboard.
    • Playback Control: Playing, pausing, and stopping specific animations or state machines.
    • State Interaction: Triggering state machine inputs (Booleans, Numbers, Triggers) and updating text runs.
    • Layout: Configuring how the artboard fits within its container using Fit and Alignment.
    // Basic initialization (constructor usage)
    val controller = RiveFileController(
        loop = Loop.LOOP,
        autoplay = true,
        file = myRiveFile,
        activeArtboard = myArtboard
    )
  10. Understand RenderSurfaceVulkan backend types

    master

    RenderSurfaceVulkan is a polymorphic surface that can operate in one of two exclusive modes, managed via a std::variant:

    1. Window Backend (VulkanWindowSurface): Used for rendering to an Android ANativeWindow. It manages a VkSurfaceKHR and a VulkanSwapchain. It can transition between a VulkanPendingWindowSurface state (lazy swapchain creation) and a VulkanPreparedWindowSurface state.
    2. Image Backend (VulkanImageSurface): Used for off-screen rendering. It manages either a VulkanPendingImageSurface (fixed-size creation data) or a VulkanPreparedImageSurface (containing a VulkanHeadlessFrameSynchronizer).
  11. Use RenderSurface for backend-specific surface management

    master

    The RenderSurface class acts as a backend-specific wrapper that manages a lazy Rive render target. In the Android integration, Kotlin stores concrete subclasses of RenderSurface as opaque surface pointers.

    Key behaviors:

    • Lazy Initialization: The concrete render target is not created immediately upon instantiation; instead, it is created on the first draw through an active RenderContext via getOrCreateRenderTarget.
    • Lifecycle: The render target is dropped and recreated whenever a resize occurs.
    • Threading: The resize method must be called on the command server thread.