问题信息
在项目引入RN后,当且仅当运行在Android 10会发生如下报错:
java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "_Unwind_Resume" referenced by "/data/app/com.soproblem-EYvRBOxIlnB6I33thkYc_Q==/base.apk!/lib/arm64-v8a/libmmkv.so"...
at java.lang.Runtime.loadLibrary0(Runtime.java:1071)
at java.lang.Runtime.loadLibrary0(Runtime.java:1007)
at java.lang.System.loadLibrary(System.java:1667)
at com.tencent.mmkv.MMKV.doInitialize(MMKV.java:121)
at com.tencent.mmkv.MMKV.initialize(MMKV.java:108)
at com.tencent.mmkv.MMKV.initialize(MMKV.java:71)
at com.soproblem.MainApplication.onCreate(MainApplication.kt:38)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1182)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6460)
at android.app.ActivityThread.access$1300(ActivityThread.java:219)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1859)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
排查过程
Crash的直接原因
通过Crash堆栈可以看到这个报错发生在使用System.loadLibrary方法加载mmkv.so时。 对比引入RN前后,发现mmkv.so并未发生任何变化,但是libc++_shared.so发生了变化。 使用nm -D libc++_shared.so打印so的符号表后,会发现引入RN前的libc++_shared.so的确包含了_Unwind_Resume:
00000000000ce350 T _Unwind_Backtrace
00000000000ce32c T _Unwind_DeleteException
00000000000cc974 T _Unwind_FindEnclosingFunction
00000000000cfabc T _Unwind_Find_FDE
00000000000ce044 T _Unwind_ForcedUnwind
00000000000cc8e0 T _Unwind_GetCFA
00000000000cc99c T _Unwind_GetDataRelBase
00000000000cc77c T _Unwind_GetGR
00000000000cc940 T _Unwind_GetIP
00000000000cc948 T _Unwind_GetIPInfo
00000000000cc964 T _Unwind_GetLanguageSpecificData
00000000000cc96c T _Unwind_GetRegionStart
00000000000cc9a4 T _Unwind_GetTextRelBase
00000000000cdecc T _Unwind_RaiseException
00000000000ce138 T _Unwind_Resume
00000000000ce234 T _Unwind_Resume_or_Rethrow
00000000000cc8e8 T _Unwind_SetGR
00000000000cc95c T _Unwind_SetIP
00000000000cc37c T _ZNKSt10bad_typeid4whatEv
00000000000cbfac T _ZNKSt11logic_error4whatEv
000000000004a9c8 T _ZNKSt12bad_any_cast4whatEv
000000000004a9d4 T _ZNKSt12experimental15fundamentals_v112bad_any_cast4whatEv
00000000000cbc74 T _ZNKSt13bad_exception4whatEv
00000000000cc064 T _ZNKSt13runtime_error4whatEv
00000000000cbce0 T _ZNKSt16bad_array_length4whatEv
000000000004d568 T _ZNKSt16nested_exception14rethrow_nestedEv
00000000000a1b58 T _ZNKSt18bad_variant_access4whatEv
000000000009177c T _ZNKSt19bad_optional_access4whatEv
00000000000cbcbc T _ZNKSt20bad_array_new_length4whatEv
...
引入RN后libc++_shared.so发生变化的原因
经过查询发现react-android和mmvk中都带有libc++_shared.so,引入RN前使用的是mmkv中的libc++_shared.so,而引入RN后使用的是react-android中的libc++_shared.so。
react-android和mmkv中携带的libc++_shared.so不一样的原因
因为react-android和mmkv中使用的NDK版本不一样,从NDK 17开始,libc++_shared.so就不再包含Unwind的符号了,而我依赖的mmkv版本所使用的NDK版本是16,所以mmkv中的libc++_shared.so是包含Unwind符号的。
引入RN后使用的是react-android中的libc++_shared.so的原因
在默认情况下,如果项目中引用了两个同名的so,Build时是会出现如下报错的:
2 files found with path 'lib/armeabi-v7a/libc++_shared.so' from inputs
但实际上该项目在运行时并未出现报错,原因是react-native-gradle-plugin中有如下代码:
if (!project.isNewArchEnabled(extension)) {
// For Old Arch, we set a pickFirst only on libraries that we know are
// clashing with our direct dependencies (mainly FBJNI and Hermes).
variant.packaging.jniLibs.pickFirsts.addAll(
listOf(
"**/libfbjni.so",
"**/libc++_shared.so",
))
} else {
// We set some packagingOptions { pickFirst ... } for our users for libraries we own.
variant.packaging.jniLibs.pickFirsts.addAll(
listOf(
// This is the .so provided by FBJNI via prefab
"**/libfbjni.so",
// Those are prefab libraries we distribute via ReactAndroid
// Due to a bug in AGP, they fire a warning on console as both the JNI
// and the prefab .so files gets considered.
"**/libreactnative.so",
"**/libjsi.so",
// AGP will give priority of libc++_shared coming from App modules.
"**/libc++_shared.so",
))
}
设置packaging.jniLibs.pickFirsts后,遇到同名so时就会取第一个,所以不再会有报错。引入RN后,第一个libc++_shared.so就是react-android中的libc++_shared.so,所以最后使用的是该so。
解决方案
现在可以确认,Crash的原因是mmkv.so引用了_Unwind_Resume,但当前项目中没有so能够提供该符号,所以自然想到解决思路有两个,一个是mmkv.so不再引用_Unwind_Resume,一个是在项目中提供该符号。
mmkv.so不再引用_Unwind_Resume
该思路的实现方式就是使用高版本的NDK重新生成mmkv.so,而新版本的mmkv已经是基于高版本NDK的了,所以升级mmkv版本即可。
在项目中提供_Unwind_Resume
mmkv中的libc++_shared.so是有_Unwind_Resume的,那是不是可以通过使用mmkv中的libc++_shared.so来解决该问题?显然是不行的,因为libreactnative.so中用到了高版本NDK才提供的符号,这些符号在mmkv携带的libc++_shared.so中是没有的。这里就出现了矛盾点,react-android需要高版本libc++_shared.so提供的符号,而mmkv需要低版本libc++_shared.so提供的符号。那是不是可以自己生成一个高版本的libc++_shared.so,让它带上_Unwind_Resume?理论上是可以的,但是成本比较高。这里其实还有一个解决方案,那就是不通过libc++_shared.so提供_Unwind_Resume,而是把_Unwind_Resume打到mmkv.so里。恰好mmkv提供了这种方式,也就是mmkv-static,不过这种方式有个缺点,就是会增加包体。
结论
如果条件允许,优先采用升级mmkv版本的方案,否则的话可以考虑使用mmkv-static替代mmkv的方案。
引申问题
为什么这个Crash只发生在Android 10?
在Android 9及以前,系统内置了gcc的libunwind,而在Android 11及以后,系统内置了LLVM的libunwind,只有Android 10上没有内置的libunwind,所以在libc++_shared.so也不包含_Unwind_Resume的情况下,就会发生Crash。
使用mmkv-static方案时如何替换三方库依赖的mmkv?
当因为某些原因需要使用mmkv-static方案时,可能会遇到mmkv是三方库引入的情况,这个时候无法直接进行替换,解决方案就是通过Gradle提供的依赖替换API来进行替换,代码如下所示:
allprojects {
configurations.configureEach {
resolutionStrategy {
// 如果在项目中有声明mmkv版本的话可以使用如下方式,假设mmkv版本为mmkv_version
dependencySubstitution {
substitute module("com.tencent:mmkv") using module("com.tencent:mmkv-static:$mmkv_version") because("To solve so conflict problem")
}
// 如果在项目中没有声明mmkv版本的话可以使用如下方式
eachDependency {
if (requested.group == "com.tencent" && requested.name == "mmkv") {
useTarget("com.tencent:mmkv-static:$requested.version")
because("To solve so conflict problem")
}
}
}
}
}