本系列文章列表:
Android JNI介绍(一)- 第一个Android JNI工程
Android JNI介绍(二)- 第一个JNI工程的详细分析
Android JNI介绍(三)- Java和Native的互相调用
Android JNI介绍(八)- CMakeLists的使用
在前面的文章中,我们已经了解了Java函数和native函数的绑定过程,了解了Java和native的绑定方法,本文将介绍如何让一个库依赖其他库。
一、动态库和静态库
首先介绍下动态库和静态库的概念。
-
静态库
在开发过程中,我们常常会用到一些常用的公共函数,我们可以将这些函数编到库中,在编写其他程序的时候一起整合到最终程序中,这种库就是静态库,在编译时链接,在
Linux
下一般是.a
文件。 -
动态库
动态库在内部提供函数,让其他程序在运行时调用。在
Linux
下一般通过dlopen
、dlsym
等函数动态寻找,在运行时链接,在Linux
下一般是.so
文件。
对于外部引入的静态库,我们在编译时会将其一起打包到新生成的动态库中;
对于外部引入的动态库,我们需要将它们一起打包到apk
中。
二、在Android Studio下引入外部库
这里以动态库为例,首先我们编一个动态库:
1. 编写代码
新写一个CMakeLists.txt
add_library(abi
SHARED
abi.cpp)
在src/main/cpp/abi/abi.h
中定义如下函数
extern "C" const char *getAbi();
src/main/cpp/abi/abi.cpp
内容如下,不同ABI
的动态库会回传不同的结果
#include "abi.h"
const char *getAbi() {
#ifdef __arm__
return "arm32";
#elif __aarch64__
return "arm64";
#elif __i386__
return "x86";
#elif __x86_64__
return "x86_64";
#else
return "unknown";
#endif
}
在build.gradle
中重新指定CMakeLists.txt
的路径
...
externalNativeBuild {
cmake {
// path "CMakeLists.txt"
path "src/main/cpp/abi/CMakeLists.txt"
}
}
...
2. 编译动态库
然后点击Android Studio
右侧的Gradle
窗口中,指定module
的externalNativeBuildRelease
任务(也可以gradlew
执行命令)。

build
目录下看到生成的动态库文件

3. 将动态库放到src/main/jniLibs
目录下

为什么是这个目录,而不是其他目录?这是因为,src/main/jniLibs
是Android Studio
工程的默认动态库文件存放位置,如果将生成的库放在其他位置,打包apk的时候就不会把库拿过来一起打包,于是运行时就会缺少libabi.so
,从而导致crash,如下:

4. 在CMakeLists.txt中配置依赖的动态库
在build.gradle
中切换回原来的CMakeLists.txt
...
externalNativeBuild {
cmake {
path "CMakeLists.txt"
// path "src/main/cpp/abi/CMakeLists.txt"
}
}
...
CMakeLists.txt
内容如下,添加依赖
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp)
find_library(
log-lib
log)
set(abi-lib ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libabi.so)
message("abi-lib is ${abi-lib}")
target_link_libraries(
native-lib
${abi-lib}
${log-lib})
其中,
message
函数用于输出信息到控制台;
set
函数用于创建变量,用法为set(variable value)
;
CMAKE_SOURCE_DIR
表示当前CMakeLists.txt
文件所在的目录;
CMAKE_ANDROID_ARCH_ABI
表示当前编译的ABI
,如 armeabi-v7a
、arm64-v8a
等;
此时点击externalNativeBuildRelease
,Run
窗口输出如下,说明依赖libabi.so
成功
release|armeabi-v7a :abi-lib is D:/android/android-projects/git/JNIDemo/app/src/main/jniLibs/armeabi-v7a/libabi.so
release|armeabi-v7a :-- Configuring done
release|armeabi-v7a :-- Generating done
release|armeabi-v7a :-- Build files have been written to: D:/android/android-projects/git/JNIDemo/app/.externalNativeBuild/cmake/release/armeabi-v7a
release|arm64-v8a :abi-lib is D:/android/android-projects/git/JNIDemo/app/src/main/jniLibs/arm64-v8a/libabi.so
release|arm64-v8a :-- Configuring done
release|arm64-v8a :-- Generating done
release|arm64-v8a :-- Build files have been written to: D:/android/android-projects/git/JNIDemo/app/.externalNativeBuild/cmake/release/arm64-v8a
release|x86 :abi-lib is D:/android/android-projects/git/JNIDemo/app/src/main/jniLibs/x86/libabi.so
release|x86 :-- Configuring done
release|x86 :-- Generating done
release|x86 :-- Build files have been written to: D:/android/android-projects/git/JNIDemo/app/.externalNativeBuild/cmake/release/x86
release|x86_64 :abi-lib is D:/android/android-projects/git/JNIDemo/app/src/main/jniLibs/x86_64/libabi.so
release|x86_64 :-- Configuring done
release|x86_64 :-- Generating done
release|x86_64 :-- Build files have been written to: D:/android/android-projects/git/JNIDemo/app/.externalNativeBuild/cmake/release/x86_64
5. 在工程中调用libabi.so
中的函数
在Java代码中定义native函数并调用
public native String getABI();
......
Log.i(TAG, "onCreate: getABI = " + getABI());
......
native实现
...
#include "abi/abi.h"
...
extern "C" JNIEXPORT jstring
JNICALL
Java_com_wsy_jnidemo_MainActivity_getABI(
JNIEnv *env,
jobject /* this */) {
return env->NewStringUTF(getAbi());
}
在module
的build.gradle
中进行配置,以指定ABI
运行
android{
...
defaultConfig{
...
ndk{
abiFilters "armeabi-v7a" // 指定以armeabi-v7a运行
// abiFilters "arm64-v8a","armeabi-v7a" // 以arm64-v8a、armeabi-v7a中,目标设备支持的最优ABI运行
// abiFilters "arm64-v8a" // 指定以arm64-v8a运行
}
...
}
...
}
前后以armeabi-v7a
、arm64-v8a
ABI运行,结果如下

平时我们一般会使用这种方式进行开发。
下面介绍下直接使用dlopen
、dlsym
进行调用的方法。
6. 使用dlopen
和dlsym
进行调用
-
dlfcn.h
中的函数介绍// 打开动态库。 // 参数为:动态库路径,加载选项 // 返回值:动态库的句柄 void* dlopen(const char* __filename, int __flag); // 关闭动态库 // 参数为:动态库的句柄 // 返回值:0代表成功,其他代表失败 int dlclose(void* __handle); // 寻找动态库中的符号 // 参数为:动态库的句柄,符号名称 // 回传值,寻找到的对象 void* dlsym(void* __handle, const char* __symbol);
-
通过
dlopen
和dlsym
进行函数调用,流程说明:- 打开动态库文件
- 在动态库中寻找函数
- 找到函数,则直接运行
代码如下:
extern "C" JNIEXPORT jstring JNICALL Java_com_wsy_jnidemo_MainActivity_getABIByDlopen( JNIEnv *env, jobject /* this */, jstring libPath) { const char *cLibPath = env->GetStringUTFChars(libPath, JNI_FALSE); void *handle = dlopen(cLibPath, RTLD_LAZY); env->ReleaseStringUTFChars(libPath, cLibPath); const char *abi = "unknown"; if (handle == NULL) { LOGI("lib not found!"); } else { LOGI("lib found!"); getAbiFunc getAbiFunction = (getAbiFunc) (dlsym(handle, "getAbi")); if (getAbiFunction == NULL) { LOGI("function not found!"); } else { abi = getAbiFunction(); LOGI("getAbi by dlopen success, abi is : %s", abi); } dlclose(handle); } return env->NewStringUTF(abi); }
分别以
armeabi-v7a
和arm64-v8a
运行,结果如下: