被React Native插件狂虐2天之后,写下c++_share.so冲突处理心路历程

前情提要

为了应对活体检测客户react-native端的支持,需要开发react-native插件供客户使用。关于react-native插件开发具体可以参考react官网:

https://reactnative.cn/docs/native-modules-android
https://reactnative.cn/docs/native-modules-ios
https://reactnative.cn/docs/native-components-android
https://reactnative.cn/docs/native-components-ios

具体包含两部分

  1. ViewManager:包装原生的view供react-native的js部分使用
  2. NativeModule:提供原生的api能力供react-native的js部分调用

心路历程

参考着官方事例,插件代码很快就完成。开开心心把插件发布到github之后试用了一下就遇到了第一个问题

看错误很容易发现是so冲突了,也就是说react-native脚手架创建的项目原本就存在libc++_share.so,正好我们的活体检测sdk也存在libc++_shared.so。冲突的解决方法也很简单,在android域中添加如下配置:

1
2
3
4
5
6
packagingOptions {
pickFirst 'lib/arm64-v8a/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/x86/libc++_shared.so'
pickFirst 'lib/x86_64/libc++_shared.so'
}

这边顺便解释下packagingOptions中几个关键字的意思和作用

关键字 含义 实例
doNotStrip 可以设置某些动态库不被优化压缩 doNotStrip ‘*/arm64-v8a/libc++_shared.so’
pickFirst 匹配到多个相同文件,只提取第一个 pickFirst ‘lib/arm64-v8a/libc++_shared.so’
exclude 过滤掉某些文件或者目录不添加到APK中 exclude ‘lib/arm64-v8a/libc++_shared.so’
exclude 将匹配的文件合并添加到APK中 merge ‘lib/arm64-v8a/libc++_shared.so’

上述例子中处理的方式是遇到冲突取第一个libc++_shared.so。冲突解决之后继续运行,打开摄像头过一会儿就崩溃了,报错如下:

1
std::cout << "src: (" << h << ", " << w << ")" << std::endl;

仅仅是简单的c++输出流,对功能本来没有影响。很好奇为什么会崩溃,查了好久一无所获。既然不影响功能就先删掉了这行代码,果然就不报错了,功能都能正常使用了,开开心心的交给测试回归。一切都是好好的,直到跑在arm64-v8a的设备上,出现了如下报错:

这次有明显的报错信息,意思是当运行opencv_java3.so的时候缺少_sfp_handler_exception函数,这个函数实际上是在c++_shared.so库中的。奇怪的是原生代码运行在arm64-v8a的设备上是好的,那怎么跑在react-native环境就会缺少_sfp_handler_exception函数了呢?

直到我在原生用ndk20a编译代码报了同样的错误,才意识到一切问题的源头是pickFirst引起的。

可以明显的看到react-native和原生环境跑出来的apk包中c++_shared.so的大小是不同的。

也就是说pickFirst是存在安全隐患的,就拿这个例子来说,假如两个c++_shared.so是用不同版本的ndk打出来的,其实内部的库函数是不一样的,pickFirst贸然选择第一个必然导致另外的库不兼容。那么是不是可以用merge合并两个c++_shared.so,试了一下针对so merge失效了,只能是另辟蹊径。

如果我们的sdk只有一个库动态依赖于c++_shared.so,大可把c++_shared.so以静态库的方式打入,这样就不会有so冲突问题,同时也解决了上述问题。配置如下:

1
2
3
4
5
6
7
8
9
externalNativeBuild {
ndk {
abiFilters "armeabi-v7a", "arm64-v8a"
}
cmake {
cppFlags "-std=c++11 -frtti -fexceptions"
arguments "-DANDROID_STL=c++_shared" //shared改为static
}
}

可惜的是例子中的sdk不止一个库动态依赖于c++_shared.so,所以这条路也行不通。那么只能从react-native侧出发寻找方案。

方案一(推荐)

找出react-native这边的c++_shared.so是基于什么ndk版本打出来的,想办法把两端的ndk版本保持统一,问题也就迎刃而解了。

从react-native对应的android工程的蛛丝马迹中发现大概是基于ndk r20b打出来的。接下来就是改造sdk中c++_shared.so基于的ndk版本了。

  1. 基于ndk r20b版本重新编译opencv库
  2. 把opencv库连接到项目,基于ndk r20b版本重新编译alive_detected.so库

把编译好的sdk重新导入插件升级,运行之后果然所有的问题得以解决。

方案二

去除react-native中的c++_shared.so库,react-native并不是一开始就引入了c++_shared.so。从React Native版本升级中去查看c++_shared.so是哪个版本被引入的,可以发现0.59之前的版本是没有c++_shared.so库的,详见对比:

那么我们把react-native版本降级为0.59以下也能解决问题,降级步骤如下:

  1. 进入工程
1
cd Temple
  1. 指定版本
1
npm install --save react-native@0.58.6
  1. 更新
1
react-native upgrade
  1. 一路替换文件

总结

Android开发会面临各种环境问题,遇到问题还是要从原理出发,理清问题发生的根源,这样问题就很好解决。