Jaween
Jaween

Reputation: 374

Failed to load dynamic library in Flutter app

I have a Flutter app in production on the Google Play store, which includes a native dynamic library built using the NDK and loaded at runtime (I have called it libraster.so). On most devices this library is present and loads fine. But on certain devices, the following ArgumentError occurs at runtime Invalid argument(s): Failed to load dynamic library (dlopen failed: library "libraster.so" not found).

The devices in question are ARM devices I believe. The app doesn't specify any abiFilter in the app module's build.gradle file.

Using Google Play Console's App Bundle Explorer, I can download the APKs that would be distributed to the affected devices, and they contain libraster.so as normal.

According to my error logs, the device which are affected so far are:

Model Name Android version
SM-G928F Samsung Galaxy S6 Edge+ 6.0.1
SM-J500M Samsung Galaxy J5 6.0.1
SM-J710GN Samsung Galaxy J7 2016 6.0.1
SM-T110 Samsung Galaxy Tab 3 Lite 7.0 4.2.2
SM-T111M Samsung Galaxy Tab 3 Lite 7.0 4.2.2
GT-I8262 Samsung Galaxy Core Duos 4.1.2
GT-I8552 Samsung Galaxy Win Duos 4.1.2
GT-I8552B Samsung Galaxy Win Duos 4.1.2
GT-I9082L Samsung Galaxy Grand Duos 4.2.2
GT-I9300 Samsung Galaxy S III 4.1.2
GT-N8000 Samsung Galaxy Note 10.1 4.1.2
GT-N8010 Samsung Galaxy Note 10.1 4.1.2
GT-P3110 Samsung Galaxy Tab 2 7.0 4.1.2
GT-P5110 Samsung Galaxy Tab 2 10.1 4.2.2
SO-03E Sony Xperia Tablet Z 4.1.2
B1-A71 Acer Iconia Tab B1-A71 4.1.2
F-01F Fujitsu Arrows NX F-01F 4.2.2
ME173X Asus Memo Pad HD7 4.2.2

Mostly Android 4.1.2, 4.2.2 and 6.0.1 devices.

Here's a simplified version of my app module's build.gradle:

apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
    compileSdkVersion 29

    lintOptions {
        disable 'InvalidPackage'
    }

    defaultConfig {
        applicationId "com.example.package"
        minSdkVersion 16
        targetSdkVersion 29
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        multiDexEnabled true

        externalNativeBuild {
            cmake {
                arguments "-DCOMPILE_TESTS:BOOL=OFF"
            }
        }

        // Maintains debug symbols
        packagingOptions {
          doNotStrip '**.so'
        }
    }

    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release

            minifyEnabled true
            useProguard true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

            ndk {
                debugSymbolLevel = 'FULL'
            }
        }
    }

    externalNativeBuild {
        cmake {
            version "3.19.2"
            path "path/to/CMakeLists.txt"
        }
    }
}

flutter {
    source '../..'
}

dependencies {
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'androidx.multidex:multidex:2.0.1'
}

apply plugin: 'com.google.gms.google-services'

And proguard-rules.pro:

#Flutter Wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.**  { *; }
-keep class io.flutter.util.**  { *; }
-keep class io.flutter.view.**  { *; }
-keep class io.flutter.**  { *; }
-keep class io.flutter.plugins.**  { *; }
-dontwarn io.flutter.embedding.**

-keep class com.example.package.** { *; }

The Flutter version used is stable 1.22.5.

Is this a bug in Flutter? Do these devices load dynamic libraries differently to other devices? Is libraster.so just actually not being packaged with the APK in some scenarios?

Upvotes: 6

Views: 18717

Answers (3)

Hamed Rezaee
Hamed Rezaee

Reputation: 7252

here are a solution thanks to knaeckeKami.

first, you need to create a plugin:

package <your_package>

import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager

import androidx.annotation.NonNull

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar

class Plugin: FlutterPlugin, MethodCallHandler {
  companion object {
    const val TAG = <YOUR_TAG>
  }

  private lateinit var channel: MethodChannel
  private lateinit var context: Context

  override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    context = flutterPluginBinding.applicationContext
    channel = MethodChannel(flutterPluginBinding.binaryMessenger, <CHANNEL_NAME>)

    channel.setMethodCallHandler(this)
  }

  override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    if (call.method == "getNativeLibraryDirectory") {
      val applicationInfo: ApplicationInfo = context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)

      if (applicationInfo != null) {
        result.success(applicationInfo.nativeLibraryDir)
      } else {
        result.success(null)
      }
    } else {
      result.notImplemented()
    }
  }

  override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
    channel.setMethodCallHandler(null)
  }
}

then you can use _getAndroidDynamicLibrary(String libraryName) instead of DynamicLibrary.open(libraryName) to load your library:

Future<DynamicLibrary> _getAndroidDynamicLibrary(String libraryName) async {
  try {
    return DynamicLibrary.open(libraryName);
  } catch (_) {
    try {
      final String? nativeLibraryDirectory = await _getNativeLibraryDirectory();

      return DynamicLibrary.open('$nativeLibraryDirectory/$libraryName');
    } catch (_) {
      try {
        final PackageInfo packageInfo = await PackageInfo.fromPlatform();
        final String packageName = packageInfo.packageName;

        return DynamicLibrary.open('/data/data/$packageName/lib/$libraryName');
      } catch (_) {
        rethrow;
      }
    }
  }
}

usage:

Future<DynamicLibrary> _getNativeAppTokenLibrary() async => Platform.isAndroid
    ? await _getAndroidDynamicLibrary(_libraryName)
    : DynamicLibrary.process();

Upvotes: 1

rya
rya

Reputation: 1506

I know this is an old issue, but I ran into this issue recently and I solved it by adding sqlite3_flutter_libs as a dependency in pub spec.yaml

dependencies:
   moor: ^4.3.0
   sqlite3_flutter_libs: ^0.4.2 

Upvotes: 3

Cristi
Cristi

Reputation: 1578

A similar issue was reported here: https://github.com/simolus3/moor/issues/895

The suggested fix was for this case to create a Flutter plugin which calls System.loadLibrary()

Upvotes: 4

Related Questions