最近在编译一个JNI项目遇到了这样的错误:
Execution failed for task ':app:transformNativeLibsWithMergeJniLibsForRelease'.
> More than one file was found with OS independent path 'lib/armeabi/libJniTest.so'
首先想到的依赖的多个aar库中包含了冲突的so文件,这种情况可以通过配置packagingOptions解决。但工程中并未依赖其他库,问题是如何出现的呢?
一、APK会打包哪些so?
首先先了解下APK中的so通常来自哪里:
1. 自身构建的native库
如果工程自身包含native代码,并使用CMake等工具构建,会生成对应架构的so库:
android {
...
externalNativeBuild {
cmake {
...
abiFilters 'armeabi', 'armeabi-v7a'
}
}
}
...
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
CMake工作目录在app/.externalNativeBuild/cmake/debug/armeabi
下,我们在CMakeCahe.txt
中可以看到,CMAKE_LIBRARY_OUTPUT_DIRECTORY
默认值为:
CMAKE_LIBRARY_OUTPUT_DIRECTORY:UNINITIALIZED=/Users/tsia/xxx/xxx/app/build/intermediates/cmake/debug/obj/armeabi
则工程构建完之后,该目录下就会输出对应架构的so:这些so会打包到APK中。
2.指定的外部库
如果我们依赖了一个外部的so,编译时我们可以通过add_library
添加外部库依赖,如果我们希望打包的时候能一起带进来,需要在gradle中配置jniLibs的路径:
android {
...
sourceSets {
main {
jniLibs.srcDirs = ["mylibs"]
}
}
}
mylibs和gradle文件在同一目录下,是相对的路径。jniLibs.srcDirs
就是告诉gradle那个目录下的so库要打到包中。
3. compile的外部库
如果通过compile方式依赖一个外部aar库,其中的so会被打包到APK中。
二、问题分析
我的项目中只包含自己构建的native库,唯一不同的是我在CMakeLists.txt中指定了构建产物的输出目录:
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
于是在src/main/jniLibs/${ANDROID_ABI}
下也输出了一份so库:
虽然我们没有设置,但jniLibs.srcDir有默认值,通过
println "jniLibs.srcDirs= ${android.sourceSets.main.jniLibs.srcDirs}"
打印发现:
jniLibs.srcDirs= [/Users/tsia/Documents/xxx/xxx/app/src/main/jniLibs]
CMake输出目录和jniLibs指向同一个目录,下面的so会被打包两次产生冲突,原来如此!
三、如何解决
方法1
jniLibs目录设为空,如指向其他目录也行,只要不要和CMake输出目录重合。
android {
...
sourceSets {
main {
jniLibs.srcDirs = []
}
}
}
方法2
中配置打包规则,有冲突选择就第一个最为打包内容:
packagingOptions {
pickFirst "**/libJniTest.so"
}
相关链接:
《使用CMake构建Android JNI工程》