facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
119.5k stars 24.37k forks source link

App Crashes When Hermes is Disabled on Some Android 10 Devices `couldn't find DSO to load: libhermes.so SoSource` #44591

Open Abhishek-Khanduri opened 6 months ago

Abhishek-Khanduri commented 6 months ago

Description

In my React Native application, I have disabled Hermes because I need to use Jitsi, and Jitsi does not support Hermes. After disabling Hermes, my app started to crash for some Android 10 users, and I am getting almost 100-200 crash reports on Crashlytics each day.

This is my code in app/build.gradle

apply plugin: "com.android.application"
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'

import com.android.build.OutputFile
import org.apache.tools.ant.taskdefs.condition.Os

project.ext.react = [
    enableHermes: false,  // clean and rebuild if changing
    entryFile: "index.js",
]

apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"

def enableSeparateBuildPerCPUArchitecture = false

def enableProguardInReleaseBuilds = true

def jscFlavor = 'org.webkit:android-jsc:+'

def enableHermes = project.ext.react.get("enableHermes", false);

def reactNativeArchitectures() {
    def value = project.getProperties().get("reactNativeArchitectures")
    return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}

android {
    ndkVersion rootProject.ext.ndkVersion

    compileSdkVersion rootProject.ext.compileSdkVersion

    defaultConfig {
        applicationId "org.mentortogether.mentorship"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 902007
        versionName "9.2.7"
        buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()

        if (isNewArchitectureEnabled()) {
            externalNativeBuild {
                cmake {
                    arguments "-DPROJECT_BUILD_DIR=$buildDir",
                        "-DREACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid",
                        "-DREACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build",
                        "-DNODE_MODULES_DIR=$rootDir/../node_modules",
                        "-DANDROID_STL=c++_shared"
                }
            }
            if (!enableSeparateBuildPerCPUArchitecture) {
                ndk {
                    abiFilters (*reactNativeArchitectures())
                }
            }
        }
    }

    if (isNewArchitectureEnabled()) {
        externalNativeBuild {
            cmake {
                path "$projectDir/src/main/jni/CMakeLists.txt"
            }
        }
        def reactAndroidProjectDir = project(':ReactAndroid').projectDir
        def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) {
            dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck")
            from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
            into("$buildDir/react-ndk/exported")
        }
        def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) {
            dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck")
            from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
            into("$buildDir/react-ndk/exported")
        }
        afterEvaluate {
            preDebugBuild.dependsOn(packageReactNdkDebugLibs)
            preReleaseBuild.dependsOn(packageReactNdkReleaseLibs)

            configureCMakeRelWithDebInfo.dependsOn(preReleaseBuild)
            configureCMakeDebug.dependsOn(preDebugBuild)
            reactNativeArchitectures().each { architecture ->
                tasks.findByName("configureCMakeDebug[${architecture}]")?.configure {
                    dependsOn("preDebugBuild")
                }
                tasks.findByName("configureCMakeRelWithDebInfo[${architecture}]")?.configure {
                    dependsOn("preReleaseBuild")
                }
            }
        }
    }

    splits {
        abi {
            reset()
            enable enableSeparateBuildPerCPUArchitecture
            universalApk false  // If true, also generate a universal APK
            include (*reactNativeArchitectures())
        }
    }
    signingConfigs {
        debug {
            storeFile file('debug.keystore')
            storePassword 'android'
            keyAlias 'androiddebugkey'
            keyPassword 'android'
        }
        release {
            storeFile file('mentortogo-release-key.jks')
            storePassword 'password'
            keyAlias 'mentortogo'
            keyPassword 'password'
        }
    }
    buildTypes {
        debug {
            signingConfig signingConfigs.debug
        }
        release {
            signingConfig signingConfigs.release
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        }
    }
    flavorDimensions "mentortogo"
    productFlavors {
        staging {
            dimension "mentortogo"
            manifestPlaceholders = [bugsnagReleaseStage: "staging"]
        }
        prod {
            dimension "mentortogo"
            manifestPlaceholders = [bugsnagReleaseStage: "production"]
        }
    }

    def versionArray = getNpmVersionArray()
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
            def abi = output.getFilter(OutputFile.ABI)
            if (abi != null) {  // null for the universal-debug, universal-release variants
            output.versionCodeOverride =
                        versionCodes.get(abi) * 1048576 + versionArray[0]*10000 + versionArray[1]*100 + versionArray[2]
            }

        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation'com.facebook.soloader:soloader:0.11.0+'

    implementation("com.facebook.react:react-native:+") {
        exclude group: 'com.facebook', module: 'hermes'
    }

    implementation("com.facebook.react:react-native") {
        version {
            strictly "0.70.6"
        }
    }

    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"

    debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.fbjni'
    }

    debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.flipper'
        exclude group:'com.squareup.okhttp3', module:'okhttp'
    }

    debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.flipper'
    }

    if (enableHermes) {
        implementation("com.facebook.react:hermes-engine:+") { // From node_modules
            exclude group:'com.facebook.fbjni'
        }
    } else {
        implementation jscFlavor
    }
}

if (isNewArchitectureEnabled()) {
    configurations.all {
        resolutionStrategy.dependencySubstitution {
            substitute(module("com.facebook.react:react-native"))
                    .using(project(":ReactAndroid"))
                    .because("On New Architecture we're building React Native from source")
            substitute(module("com.facebook.react:hermes-engine"))
                    .using(project(":ReactAndroid:hermes-engine"))
                    .because("On New Architecture we're building Hermes from source")
        }
    }
}

task copyDownloadableDepsToLibs(type: Copy) {
    from configurations.implementation
    into 'libs'
}

apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

def isNewArchitectureEnabled() {
    return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}

This is my build.gradle code

import groovy.json.JsonSlurper

buildscript {
    ext {
        buildToolsVersion = "33.0.0"
        minSdkVersion = 24
        compileSdkVersion = 33
        targetSdkVersion = 33

        if (System.properties['os.arch'] == "aarch64") {
            ndkVersion = "24.0.8215888"
        } else {
            ndkVersion = "21.4.7075529"
        }
    }
    repositories {
        google()
        maven {
            url 'https://maven.fabric.io/public'
        }
        mavenCentral()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:6.6.1")
        classpath("com.facebook.react:react-native-gradle-plugin")
        classpath("de.undercouch:gradle-download-task:5.0.1")
        classpath('com.google.gms:google-services:4.3.3')
        classpath('io.fabric.tools:gradle:1.31.2')
        classpath('com.bugsnag:bugsnag-android-gradle-plugin:5.+')
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
    }
}

allprojects {
    repositories {
        mavenCentral {
            content {
                excludeGroup "com.facebook.react"
            }
        }
        google()
        maven {
            url ("$rootDir/../node_modules/react-native/android")
        }
        maven {
            url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases"
        }
        maven {
            url 'https://www.jitpack.io'
        }
        maven {
            url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases"
        }
        configurations.all {
            resolutionStrategy {
                eachDependency { DependencyResolveDetails details ->
                    if (details.requested.group == 'com.facebook.react' && details.requested.name == 'react-native') {
                        def file = new File("$rootDir/../node_modules/react-native/package.json")
                        def version = new groovy.json.JsonSlurper().parseText(file.text).version
                        details.useVersion version
                    }
                }
            }
        }
    }
}

def getNpmVersionName() {
    def inputFile = new File("../package.json")
    def packageJson = new JsonSlurper().parseText(inputFile.text)
    return packageJson["version"]
}

def getNpmVersionArray() {
    def (major, minor, patch) = getNpmVersionName().minus(~/-.*/).tokenize('.')
    return [Integer.parseInt(major), Integer.parseInt(minor), Integer.parseInt(patch)] as int[]
}

This is my gradle.properties code

# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true

# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.125.0

# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew <task> -PreactNativeArchitectures=x86_64
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64

# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false

# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=false
android.disableAutomaticComponentCreation=true

I researched and tried all the possible solutions mentioned in the React Native Library repository for similar issues, but got no luck till now. This issue is impacting a lot of users of my application, the impact is huge so this needs to be fixed ASAP.

React Native Version

0.71.90

Affected Platforms

Runtime - Android

Output of npx react-native info

System:
    OS: macOS 14.2.1
    CPU: (8) arm64 Apple M2
    Memory: 121.06 MB / 8.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 16.20.2 - ~/.nvm/versions/node/v16.20.2/bin/node
    Yarn: 1.22.21 - /opt/homebrew/bin/yarn
    npm: 8.19.4 - ~/.nvm/versions/node/v16.20.2/bin/npm
    Watchman: 2024.04.08.00 - /opt/homebrew/bin/watchman
  Managers:
    CocoaPods: Not Found
  SDKs:
    iOS SDK: Not Found
    Android SDK: Not Found
  IDEs:
    Android Studio: 2022.3 AI-223.8836.35.2231.11090377
    Xcode: /undefined - /usr/bin/xcodebuild
  Languages:
    Java: 11.0.22 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 18.1.0 => 18.1.0
    react-native: 0.70.6 => 0.70.6
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found

Stacktrace or Logs

The error is occurring on the following devices:

In my application, I disabled hermes as I am using Jitsi in my application and Jitsi was not working as expected with hermes, after disabling hermes Jitsi worked fine but after publishing the app I started to get this error on Crashlytics, and the issue is only occurring on some Android 10 devices.

Fatal Exception: java.lang.UnsatisfiedLinkError
dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/base.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.arm64_v8a.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.bn.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.en.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.gu.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.hi.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.mr.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.xxhdpi.apk"],nativeLibraryDirectories=[/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/lib/arm64, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/base.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.arm64_v8a.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.bn.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.en.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.gu.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.hi.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.mr.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.xxhdpi.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64]]] couldn't find "libhermes.so"

Fatal Exception: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/base.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.arm64_v8a.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.bn.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.en.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.gu.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.hi.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.mr.apk", zip file "/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.xxhdpi.apk"],nativeLibraryDirectories=[/data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/lib/arm64, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/base.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.arm64_v8a.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.bn.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.en.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.gu.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.hi.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.mr.apk!/lib/arm64-v8a, /data/app/org.mentortogether.mentorship-AMBt-GyVwMiPAlRVJO5lMA==/split_config.xxhdpi.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64]]] couldn't find "libhermes.so"
       at java.lang.Runtime.loadLibrary0(Runtime.java:1067)
       at java.lang.Runtime.loadLibrary0(Runtime.java:1007)
       at java.lang.System.loadLibrary(System.java:1667)
       at com.facebook.soloader.nativeloader.SystemDelegate.loadLibrary(SystemDelegate.java)
       at com.facebook.soloader.nativeloader.NativeLoader.loadLibrary(NativeLoader.java:52)
       at com.facebook.soloader.nativeloader.NativeLoader.loadLibrary(NativeLoader.java:30)
       at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:774)
       at com.facebook.hermes.reactexecutor.HermesExecutor.loadLibrary(HermesExecutor.java:25)
       at com.facebook.hermes.reactexecutor.HermesExecutor.<clinit>(HermesExecutor.java)
       at com.facebook.hermes.reactexecutor.HermesExecutor.loadLibrary(HermesExecutor.java:23)
       at com.facebook.react.ReactInstanceManagerBuilder.getDefaultJSExecutorFactory(ReactInstanceManagerBuilder.java:393)
       at com.facebook.react.ReactInstanceManagerBuilder.build(ReactInstanceManagerBuilder.java:343)
       at com.facebook.react.ReactNativeHost.createReactInstanceManager(ReactNativeHost.java:96)
       at com.facebook.react.ReactNativeHost.getReactInstanceManager(ReactNativeHost.java:42)
       at org.mentortogether.mentorship.MainApplication.onCreate(MainApplication.java:73)
       at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1190)
       at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6852)
       at android.app.ActivityThread.access$1300(ActivityThread.java:268)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1982)
       at android.os.Handler.dispatchMessage(Handler.java:107)
       at android.os.Looper.loop(Looper.java:237)
       at android.app.ActivityThread.main(ActivityThread.java:7807)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1047)

Looking at the error trace it seems like the device is looking for hermes, but I have disabled so it is very strange to me.

I have also cleaned the Gradle before creating the bundle and also tried this multiple times on the internal testing environment and the same behavior there as well 😢

github-actions[bot] commented 6 months ago
:warning: Unsupported Version of React Native
:information_source: It looks like your issue or the example you provided uses an unsupported version of React Native.

Due to the number of issues we receive, we're currently only accepting new issues against one of the supported versions. Please upgrade to latest and verify if the issue persists (alternatively, create a new project and repro the issue in it). If you cannot upgrade, please open your issue on StackOverflow to get further community support.
github-actions[bot] commented 6 months ago
:warning: Missing Reproducible Example
:information_source: We could not detect a reproducible example in your issue report. Please provide either:
  • If your bug is UI related: a Snack
  • If your bug is build/update related: use our Reproducer Template. A reproducer needs to be in a GitHub repository under your username.
brien-crean commented 6 months ago

It looks like you have set your minSdkVersion to 24 @Abhishek-Khanduri so perhaps this is relevant to you

cortinico commented 6 months ago

This is my code in app/build.gradle

This is how the app/build.gradle file should look like in a 0.71 project: https://github.com/facebook/react-native/blob/0.71-stable/template/android/app/build.gradle

So your application is definitely misconfigured @Abhishek-Khanduri

sudhanshu-busy commented 6 months ago

my minSdkVersion is 21 but i am facing this same issue multiple of times. @cortinico @brien-crean

Abhishek-Khanduri commented 6 months ago

@sudhanshu-busy I followed a systematic approach to troubleshoot and resolve the crash issue with our React Native app on an Android 10 device. Here are the steps I took:

Set Up Sample App:

Configuration Transfer:

Incremental Configuration Addition:

Identified Problematic Code Snippet:

Isolated the Issue:

This is the code snippet that we suspect that is causing the issue

allprojects {
    repositories {
        maven {
            url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases"
        }
        configurations.all {
            resolutionStrategy {
                eachDependency { DependencyResolveDetails details ->
                    if (details.requested.group == 'com.facebook.react' && details.requested.name == 'react-native') {
                        def file = new File("$rootDir/../node_modules/react-native/package.json")
                        def version = new groovy.json.JsonSlurper().parseText(file.text).version
                        details.useVersion version
                    }
                }
            }
        }
    }
}

You can use similar approach to back trace the root cause

react-native-bot commented 2 days ago

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.