本文用一个demo验证Android Studio如何编译生成一个C++动态库文件(so文件) 给 Java应用层使用。然后这个so库内部又如何调用 一个预有so库中的 C语言函数。
1. 新建一个NDK工程
1.1 新建一个android studio Native C++工程
在main/cpp目录下默认有一个native-lib.cpp,还有一个CMakeList.txt文件(编译脚本),其中
native-lib.cpp实现了从JNI里返回一个C++字符串,具体代码如下:
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring
Java_com_example_ndktest_MainActivity_stringFromJNI(JNIEnv* env,jobject) {
std::string hello = "Hello from cppmain";
return env->NewStringUTF(hello.c_str());
}
其中env->NewStringUTF(hello.c_str());将C++字符串hello转换成了jstring类型(JNI可以传递给JAVA的字符串类型)
1.2 配置build.gradle
我们把CMakeList.txt移动到app moudle的根目录下,即与app模块下的build.gradle放在同一目录下,在以下脚本3中设置cmake中的path(CMakeList.txt编译脚本的存放路径),path值直接就是CMakeList.txt。
android--defaultcofig--下添加如下脚本
脚本1:
externalNativeBuild {
cmake {
abiFilters 'armeabi-v7a'
}
}
注解:表示只编译armeabi-v7a CPU架构下已经存放的的so库。
脚本2:
ndk{
abiFilters 'armeabi-v7a'
}
注解:表示只生成armeabi-v7a CPU架构下的so库,并打包到APP里。
同时,添加这个脚本是为了解决这个问题:用android studio直接运行项目会报错,找不到最终生成的libnative-lib.so,只有build生成apk,然后传到手机上运行才不报错。
添加了这个ndk脚本后就一切正常了。具体原因还需进一步研究下。
脚本3(在android配置下添加):
externalNativeBuild {
cmake {
path "CMakeLists.txt"
version "3.10.2"
}
}
注解:其中path是CMakeLists编译脚本文件的路径,以 path "CMakeLists.txt"为例,表示CMakeLists.txt文件在android studio工程的主app模块根目录下,与app模块的build.gradle文件在同一目录下。version "3.10.2"使用的CMake编译工具的版本。
2. 在native-lib.cpp中的stringFromJNI数里调用另一个so库libTest.so中的test()方法
2.1 修改native.cpp的stringFromJNI函数:调用test()
#include <jni.h>
#include <string>
#include <android/log.h>
extern "C"{
extern int test();
}
extern "C" JNIEXPORT jstring
Java_com_example_ndktest_MainActivity_stringFromJNI(JNIEnv* env,jobject) {
std::string hello = "Hello from cppmain";
__android_log_print(ANDROID_LOG_ERROR,"jni","libtest.so 里面的 test 方法:%d",test());
return env->NewStringUTF(hello.c_str());
}
(1)相比之前加了这样代码: __android_log_print(ANDROID_LOG_ERROR,"jni","libTest.so 里面的 test 方法:%d",test()); 折行代码用于打印日志,输出test()方法返回的int值。
(2)要调用test()方法,首先得用extern声明外部原型,因为test()函数的实现是在其它C语言文件里。加了一个extern "C"是因为test()是一个C语言程序,要在C++里调用C代码必须得声明关键字extern "C".
2.2 拷贝libTest.so库到我们新建的NDK项目中
将libTest.so复制到src/main/jniLibs/armeabi-v7a目录下,我们在此只编译armeabi-v7a CPU架构的so库
2.3 修改CMakeList.txt编译脚本,引入第三方库libTest.so,并链接到本地要生成的libnative-lib.so库
脚本如下:
cmake_minimum_required(VERSION 3.10.2)
#声明项目名
project("ndktest")
#将.C文件与C++(就是cpp)文件的查找路径赋值给一个变量source,多个文件路径用空格隔开。
file(GLOB source src/main/cpp/*.c src/main/cpp/*.cpp src/main/cpp/a/*.cpp )
#将C/C++源码编译,生成一个本地so动态库,库名是native-lib,源码路径就是上面那个source变量的值,${source}取值,SHARED表示将cmake要编译生成的动态库(so文件)
add_library( # Sets the name of the library.
native-lib
SHARED
${source}) # 在此项目中也可直接在此填写 src/main/cpp/native-lib.cpp
# 将下面的log库的路径赋值给log-lib, 用于target_link_libraries中链接到将要生成的本地native- lib库
find_library(
log-lib
log # log库的路径,因为log为系统库,所以只需要名字,cmake就知道其路径了
)
#配置第三方Test.so库的链接路径,我们的Test.so库放在了armeabi-v7a下,2.1中已经提到。
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a")
#将Test库,log库与本地将要生成的动态库链接在一起,这样native-lib库里的stringFromJNI函数就可以调用test()函数与log库中的__android_log_print()函数了。
target_link_libraries( # Specifies the target library.
native-lib
Test
# Links the target library to the log library included in the NDK.
${log-lib}
)
3. 在java层调用本地库native-lib.so中的JNI函数stringFromJNI
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
public native String stringFromJNI();
}