一个仅在Android 10上发生的so相关Crash

113 阅读1分钟

问题信息

在项目引入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")
                }
            }
        }
    }
}