-
概述
当你需要把自定义的c、c++代码打包成静态或者动态库以供java端进行JNI调用时(比如你要定义自己的音视频库时),你就需要学会使用相关编译工具来生成库文件。
对于在PC下使用的库文件,你只需要使用该PC平台版本的编译工具,比如gcc,就可以编译出可以在该PC下使用的库文件,但是对于android来说,这种库文件无法在app中使用,因为系统架构都不一样,你需要使用特定于android平台的编译工具来生成,这就是交叉编译。
-
使用Android Studio
最简单的方式就是通过AndroidStudio来配置创建native库文件,下面我们看具体配置。
-
File -> New -> New Module.. :
图片.png这里你可以选择C++标准库的编译器版本(C++ Standard),这里选择了工具链中默认应用的版本。
-
点击 Finish 后会创建一个新的native module:
图片.png我们这里的module名字设为了demo,我们看一下他的目录结构,studio会自动为你创建一个cpp目录,c&c++的源文件就存放在这,同时会生成一个CMakeLists.txt文件(
注意它的名字必须是这个
),它内部就是cmake的相关命令,gradle就是通过它来自动构建native库的。先来看看CMakeLists.txt:# Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.18.1) # Declares and names the project. project("demo") # 这里定义添加需要生成的库,可以多次定义add_library标签来生成多个库 add_library( # 设置库的名字。即System.loadLibrary()方法中传入的名字 demo # 设置成动态库 SHARED # 设置需要编译到该库中的native文件 demo.cpp) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that you want CMake to locate. log) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. demo # Links the target library to the log library included in the NDK. ${log-lib})
这就是studio默认为我们生成的cmake构建文件的默认配置,其中,add_library 是必须配置的,因为它定义了你要生成的库的信息,包括名字、静态库还是动态库、需要包含的源文件等信息,定义的库名字就是你使用JNI时System.loadLibrary方法中传入的名字,比如定义成demo,最终生成动态库的话那就是libdemo.so 。其他的配置不是必须的,只要简单了解一下就好。
如果你要定义头文件的编译路径的话:
# 添加所有库编译时的头文件搜索路径,注意路径必须是相对于引用它的源文件位置的相对路径或者绝对路径 # 若不指定该属性,则你需要指定相对于引用它的源文件(c、cpp)位置的相对路径 # 比如,cpp/demo.cpp中:#include "a.h",如果a.h存在于cpp/include下的话就会找不到,你只能这么写:#include "include/a.h" include_directories(include/) # 添加针对特定库编译时的头文件搜索路径,注意路径必须是相对于引用它的源文件位置的相对路径或者绝对路径 #target_include_directories( # car_jni # PUBLIC # ${PROJECT_SOURCE_DIR}/MyNativeLib/src/main/cpp/include/) #绝对路径
-
CMakeLists.txt 定义好了之后就需要和gradle建立联系:
android { ... defaultConfig { ... externalNativeBuild { cmake { cppFlags "-std=c++11" } } //这里设置生成库的abi版本,默认会配置所有有效abi版本的库 ndk { abiFilters /*'x86', 'x86_64', 'armeabi',*/ 'armeabi-v7a', 'arm64-v8a' } } ... externalNativeBuild { cmake { //和CMakeLists.txt建立联系 path "src/main/cpp/CMakeLists.txt" version "3.18.1" } } ... }
这样当build的时候,gradle会自动使用ndk中的toolchain,根据CMakeLists.txt中的配置信息来生成库文件。
-
Build apk 后查看apk信息后会发现在libs中的库文件:
图片.png
-
-
自定义cmake配置
NDK 通过工具链文件支持 CMake。工具链文件是用于自定义交叉编译工具链行为的 CMake 文件。用于 NDK 的工具链文件位于 NDK 中以下位置:
<NDK>/build/cmake/android.toolchain.cmake
。cmake \ -S${source path} \ -B${build path} \ -DANDROID_ABI=ABI \ -DANDROID_PLATFORM=platform-version-string \ -DANDROID_NDK=android-sdk/ndk/ndk-version \ -DCMAKE_TOOLCHAIN_FILE=android-sdk/ndk/ndk-version/build/cmake/android.toolchain.cmake \ -DANDROID_STL=c++_shared \ -DCMAKE_BUILD_TYPE=Release \ -GNinja
对于源文件路径,我用的是cmake3.18.1-g262b901,它的源文件路径标志是-S ,有的版本会是-H 。
-B 设置库构建文件的输出路径。
-D 设置其他变量参数:
-
ANDROID_ABI 表示要生成的库文件的abi版本,每次只能生成一种abi的库文件,abi类型有:
-
armeabi-v7a
: 32位ARM系统。 -
armeabi-v7a with NEON
: 启用NEON指令集的32位ARM系统,也可以配置成-DANDROID_ABI=armeabi-v7a -DANDROID_ARM_NEON=ON
。 -
arm64-v8a
: 64位ARM系统。 -
x86
: 32位x86系统。 -
x86_64
: 64位x86系统。
-
-
ANDROID_PLATFORM 指定应用或库所支持的最低 API 级别。此值对应于应用的
minSdkVersion
。此参数支持多种格式:android-$API_LEVEL
$API_LEVEL
android-$API_LETTER
$API_LETTER
格式允许您指定android-N
,而无需确定与该版本关联的编号。请注意,对于某些版本,API 只是编号增加了,但字母次序没有增加。您可以通过附加-MR1
后缀来指定这些 API。例如,API 级别 25 为android-N-MR1
。 -
ANDROID_NDK 指定ndk路径。
-
ANDROID_STL 指定库文件的c++库支持:
-
CMAKE_TOOLCHAIN_FILE 指定交叉编译工具链路径。
-
-G 指定库的构建系统,这里指定的是Ninja构建,加入我们这里指定的是-B 是usr/mph/test_ninja,则在此目录下会创建如下文件:
图片.png这时我们在这个目录下执行 ninja 命令即可在这个目录下创建出库文件:libtesla_jni.so。那为什么我们没有在命令参数中设置库名字和是否动态库,这里是怎么生成这个名字的so库的呢?因为我们 -S 指定的源文件夹中包含了CMakeList.txt,它里面配置了相关信息,cmake工具链会自动根据它生成库文件。
除了Ninja,你可以使用 cmake -help 查看 Generators 部分有哪些构建工具可以指定,前面有*号的是默认的,即当不指定 -G 参数的时候默认应用的构建工具,比如我这里:
图片.png默认的是Unix Makefiles,如果不指定的话创建的文件会是这些:
图片.png这时候就要在当前目录下执行 make 来生成库文件了。
-
-
app引入库文件
我们使用非AndroidStudio Module依赖方式关联的话,比如第三方的库文件或者我们通过外部工具链自己生成的库文件要怎么打包到apk中呢?
默认的如果把库文件放在src/main/jniLibs/ ${ABI} 目录下的话,gradle会自动打包到apk的lib/${ABI}目录下,然后在项目中loadLibrary就可以了。
如果我们放在app的libs/${ABI}下的话则需要在build.gradle中指定jniLibs路径:
android{ sourceSets { main{ jniLibs.srcDirs=['libs'] } } }
网友评论