Flutter 应用加载动态库失败

Failed to load dynamic library in Flutter app

我在 Google Play 商店上有一个正在生产中的 Flutter 应用程序,其中包括一个使用 NDK 构建并在运行时加载的本机动态库(我称之为 libraster.so)。在大多数设备上,这个库都存在并且可以正常加载。但在某些设备上,以下 ArgumentError 会在运行时发生 Invalid argument(s): Failed to load dynamic library (dlopen failed: library "libraster.so" not found).

我认为有问题的设备是 ARM 设备。该应用未在应用模块的 build.gradle 文件中指定任何 abiFilter

使用 Google Play Console 的 App Bundle Explorer,我可以下载将分发到受影响设备的 APK,它们包含正常的 libraster.so

根据我的错误日志,目前受影响的设备是:

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

主要是 Android 4.1.2、4.2.2 和 6.0.1 设备。

这是我的应用模块 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'

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.** { *; }

使用的Flutter版本稳定1.22.5.

这是 Flutter 的 bug 吗?这些设备加载动态库的方式是否与其他设备不同? libraster.so 只是在某些情况下实际上没有与 APK 打包在一起吗?

此处报告了类似的问题:https://github.com/simolus3/moor/issues/895

对于这种情况,建议的修复方法是创建一个调用 System.loadLibrary()

的 Flutter 插件

我知道这是一个老问题,但我最近 运行 解决了这个问题,我通过在 pub spec.yaml

中添加 sqlite3_flutter_libs 作为依赖项解决了这个问题
dependencies:
   moor: ^4.3.0
   sqlite3_flutter_libs: ^0.4.2 

感谢 knaeckeKami,这里有一个解决方案。

首先,您需要创建一个插件:

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

然后您可以使用 _getAndroidDynamicLibrary(String libraryName) 而不是 DynamicLibrary.open(libraryName) 来加载您的库:

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

用法:

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