JNI(一) JNI - NDK-交叉编译
JNI
1.1 JNI介绍
Java Nativie interface
Java 本地接口,JNI是Java调用本地语言的一种特性。通过Jni 可以使java与本地语言之间相互调用
- 如java 与c/c++ 相互调用
1.2 实现步骤
在
java
中声明Native
方法public native String stringFromJNI();
javac 命令
编译1
中的java源文件
得到class
文件javah -jni命令
导出JNI的头(.h
)文件使用java 需要交互的本地代码,实现在java中声明的native方法
extern "C" JNIEXPORT jstring JNICALL Java_com_example_ndktest_NdkManager_stringFromJNI(JNIEnv *env, jobject thiz) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str()); }
将本地代码编译成动态库
- windows下是.dll,linux下是.so ,Mac系统是.jnilib
通过java 命令执行java程序,实现java调用本地代码
1.3 native层通过jni 可以干什么
- 创建,更新java对象(包括数组字符串)
- 调用java方法
- 加载类并获取类信息
1.3.1 对类的操作
GetObjectClass
获取类class文件 一般获取参数中传递的自定义类型的class文件FindClass(类的全路径)
获取class 文件 一般获取没在参数列表中 但需要使用的类的class 文件 参数是 目标类的路径GetMethodID
获取方法名GetFieldID
获取字段具体请看如下示例
extern "C" JNIEXPORT jobject JNICALL Java_com_example_ndktest_NdkManager_changName(JNIEnv *env, jobject thiz, jobject st, jstring name) {//反射java方法//1 获取java 对应的class 文件jclass stClass = env->GetObjectClass(st);// 2 找到要调用的方法 getNamejmethodID setAge = env->GetMethodID(stClass, "setAge", "(I)V");jmethodID printInfo = env->GetStaticMethodID(stClass, "printInfo", "(Ljava/lang/String;)V");jmethodID getName = env->GetMethodID(stClass, "getName", "()Ljava/lang/String;");//3 调用 getNamejstring sName = static_cast<jstring> (env->CallObjectMethod(st, getName));// 将返回值jstring 转换为c可用的charconst char *c = env->GetStringUTFChars(sName, 0);LOGE("获取到的名字是:%s", c);//释放局部引用env->ReleaseStringUTFChars(sName, c);// 调用setAgeenv->CallVoidMethod(st, setAge, 25);// 调用静态方法jstring str = env->NewStringUTF("printInfo");env->CallStaticVoidMethod(stClass, printInfo, str);//释放局部引用env->DeleteLocalRef(str);//反射类属性//反射获取属性idjfieldID nameId = env->GetFieldID(stClass, "name", "Ljava/lang/String;");jfieldID ageId = env->GetFieldID(stClass, "age", "I");//设置属性id// nameenv->SetObjectField(st, nameId, name);// ageenv->SetIntField(st, ageId, 30);//传递参数object的方法 获取methodidjmethodID toString=env->GetMethodID(stClass,"toSting", "(Lcom/example/ndktest/Student;)V");//创建对象//1 首先获取class 在上面我们获取了Student的class文件这里就不再重复获取//2 获取构造方法jmethodID constour= env->GetMethodID(stClass,"<init>", "(Ljava/lang/String;I)V");//3 创建对象jobject st1=env->NewObject(stClass,constour,env->NewStringUTF("jesse"),20);// 4 调用 参数为 Studnet的方法env->CallVoidMethod(st,toString,st1);// 5 释放局部引用env->DeleteLocalRef(st1);env->DeleteLocalRef(stClass);return st; }
1.3.2 获取从java层传递过来的参数
参数为数组
引用类型数组都为jobjectArray
java
// 引用数据类型为参数public native int Test(int[] list, String[] str);
c++
extern "C" JNIEXPORT jint JNICALL Java_com_example_ndktest_NdkManager_Test(JNIEnv *env, jobject thiz, jintArray i_Array,jobjectArray str) {// 获取数组首元素地址// 第二个参数// jboolean* isCopy// true 表示新申请内存 拷贝一个新数组// false 就是使用java数组jint *p = env->GetIntArrayElements(i_Array, NULL);// 如果是c环境 就需要如下使用 env-> 替换成(*env)->//jint *p1 =(*env).GetIntArrayElements(i_Array, NULL);// 因为 c环境下 JNIEnv 本身就是个指针,这里再次声明为指针就成了二级指针,所以要解一次引用拿到一级指针来操作//获取数组的长度jint length = env->GetArrayLength(i_Array);for (int i = 0; i < length; i++) {if (i == 3) {*(p + i) = 10;}LOGE("获取的java 数组值为: %d", *(p + i));}// 释放数组// 参数1// 参数2// 参数3: mode 模式 有三个值// 0 刷新java数组 并释放c/c++ 数组// 1 = JNI_COMMIT:只刷新JAVA数组// 2 = JNI_ABORT : 只释放c/c++ 数组env->ReleaseIntArrayElements(i_Array, p, 0);//类型数组通过如下方式遍历 首先获取数组长度 遍历 按下下标获取int count = env->GetArrayLength(str);for (int i = 0; i < count; i++) {// 获取jstringjstring s1 = static_cast<jstring>(env->GetObjectArrayElement(str, i));// 转为c/c++ 中可操作的charconst char *c = env->GetStringUTFChars(s1, 0);LOGE("获取的java str数组值为: %s", c);// 释放env->ReleaseStringUTFChars(s1, c);}return count; }
参数为基本数据类型
则可直接使用
java
// 基本数据类型为参数public native int Test1(int i);
c++
extern "C" JNIEXPORT jint JNICALL Java_com_example_ndktest_NdkManager_Test1(JNIEnv *env, jobject thiz, jint i) {return i + 1; }
参数为自定义类型
- 则需通过操作类的方法操作
- 请参考上面1.3.1中的例子
1.4 JNI 语法
java中声明 native方法
public native String stringFromJNI();
Native中实现被声明的native方法
extern "C" JNIEXPORT jstring JNICALL Java_com_example_ndktest_NdkManager_stringFromJNI(JNIEnv *env, jobject thiz) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str()); }
1.4.1 Native中代码解释
1.4.1.1 extern “C”
Extern “C”是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函数。这是因为C++编译后库中函数名会变得很长,与C生成的不一致,造成C++不能直接调用C函数,加上extren “c”后,C++就能直接调用C函数了
1.4.1.2 JNIEXPORT
宏定义:#define JNIEXPORT __attribute__ ((visibility ("default")))
在 Linux/Unix/Mac os/Android 这种类 Unix 系统中,定义为__attribute__ ((visibility ("default")))
表示函数对外界可见
GCC 有个visibility属性, 该属性是说, 启用这个属性:
- 当-fvisibility=hidden时,动态库中的函数默认是被隐藏的即 hidden。
- 当-fvisibility=default时,动态库中的函数默认是可见的。
1.4.1.3 JNICALL
宏定义,在 Linux/Unix/Mac os/Android 这种类 Unix 系统中,它是个空的宏定义: #define JNICALL
,所以在 android 上删除它也可以
1.4.1.4 thiz
- thiz 就是java中声明native方法的类 即调用native方法的类
1.4.1.5JNIEnv
- JNIEnv类型实际上代表了Java环境,通过这个 JNIEnv* 指针,就可以对 Java 端的代码进行操作:
- 调用 Java 函数
- 操作 Java 对象
- JNIEnv 的本质是一个与线程相关的结构体,每个线程存在一个JNIEnv
1.4.1.6 JavaVM
- JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个
- JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv,同时 JNIEnv 具有线程相关性,也就是 B 线程无法使用 A 线程的 JNIEnv
1.4.1.7 如何在native线程中使用JNIenv
如果想在 native 线程中使用 JNIEnv* 需要使用 JVM 的 AttachCurrentThread 方法进行绑定,在子线程退出时,要调用JavaVM的DetachCurrentThread函数来释放对应的资源,否则会出错。
JavaVM *_vm;jint JNI_OnLoad(JavaVM* vm, void* reserved){_vm = vm;return JNI_VERSION_1_6;}void* threadTask(void* args){JNIEnv *env;// 通过 javavm 绑定当前线程赋值给envjint result = _vm->AttachCurrentThread(&env,0);if (result != JNI_OK){return 0;}// ...// 线程 task 执行完后不要忘记分离_vm->DetachCurrentThread(); }extern "C" JNIEXPORT void JNICALL Java_com_example_ndktest_NdkManager_nativeThreadTest(JNIEnv *env, jobject thiz) {pthread_t pid;pthread_create(&pid,0,threadTask,0); }
这里通过在JNI_OLoad中 保存的全局变量来实现,这个方式涉及到jni的注册方式 后面会详细说
1.5 JNI 注册类型
1.5.1 静态注册
当Java层调用navtie函数时,会在JNI库中根据函数名查找对应的JNI函数。如果没找到,会报错。如果找到了,则会在native函数与JNI函数之间建立关联关系,其实就是保存JNI函数的函数指针。下次再调用native函数,就可以直接使用这个函数指针。
语法
- JNI函数名格式(包名里面的”.”需要改为”_”)
- Java_ + 包名(com_example_ndktest)+ 类名(_MainActivity) + 函数名(_stringFromJNI)
静态注册的缺点
- 要求JNI函数的名字必须遵循JNI规范的命名格式;
- 名字冗长,容易出错;
- 初次调用会根据函数名去搜索JNI中对应的函数,会影响执行效率;
- 需要编译所有声明了native函数的Java类,每个所生成的class文件都要用javah工具生成一个头文件;
例子
包名
com.example.ndktest
类名
NdkManager
java类
package com.example.ndktest;import java.util.ArrayList; import java.util.List;public class NdkManager {static {// native实现在ndktest 库内所以需加载该库 后面cmake中会说到System.loadLibrary("ndktest");}public native String stringFromJNI(); }
native实现
#include <jni.h> #include <string> #include <android/log.h>// 定义log 宏 输出 __VA_ARGS__表示可变参数 代表了前面括号里的"..." #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "JNI", __VA_ARGS__);extern "C" JNIEXPORT jstring JNICALL Java_com_example_ndktest_NdkManager_stringFromJNI(JNIEnv *env, jobject thiz) {std::string hello = "Hello from C++";// 1. 获取 thiz 的 class,也就是 java 中的 Class 信息jclass thisclazz = env->GetObjectClass(thiz);// 2. 根据 Class 获取 getClass 方法的 methodID,第三个参数是签名(params)returnjmethodID mid_getClass = env->GetMethodID(thisclazz, "getClass", "()Ljava/lang/Class;");// 3. 执行 getClass 方法,获得 Class 对象jobject clazz_instance = env->CallObjectMethod(thiz, mid_getClass);// 4. 获取 Class 实例jclass clazz = env->GetObjectClass(clazz_instance);// 5. 根据 class 的 methodIDjmethodID mid_getName = env->GetMethodID(clazz, "getName", "()Ljava/lang/String;");// 6. 调用 getName 方法jstring name = static_cast<jstring>(env->CallObjectMethod(clazz_instance, mid_getName));//打印 thiz类名LOGE("class name:%s", env->GetStringUTFChars(name, 0));return env->NewStringUTF(hello.c_str()); }
1.5.2 动态注册
通过提供一个函数映射表,注册给
JVM
虚拟机,这样JVM
就可以用函数映射表来调用相应的函数,就不必通过函数名来查找需要调用的函数。Java与
JNI
通过JNINativeMethod
的结构来建立函数映射表,它在jni.h
头文件中定义,其结构内容如下:typedef struct {const char* name; // 对应java中方法名const char* signature; // 方法签名void* fnPtr;// /对应交互cpp中方法的 函数指针 (指向对应函数) } JNINativeMethod;
创建映射表后,调用
env->RegisterNatives
函数将映射表注册给JVM
;- 当Java层通过
System.loadLibrary
加载JNI库时,会在库中查JNI_OnLoad
函数。 JNI_OnLoad为JNI
库的入口函数,需要在这里完成所有函数映射和动态注册工作,及其他一些初始化工作。
- 当Java层通过
实例
Java 类名
NativeManager
包名com.example.dynamicndk
public class NativeManager {// Used to load the 'dynamicndk' library on application startup.static {System.loadLibrary("dynamicndk");}// 动态注册public native String stringFromJNI();public native void Test();// native 线程 调用javapublic native void threadTest(); }
cpp
#include <jni.h> #include <string> #include <android/log.h>#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "JNI", __VA_ARGS__);JavaVM *_vm;//保存JavaVM 声明为全局变量 方便提取JNIEnv 或者又需要的时候用到char *mClassName = "com/example/dynamicndk/NativeManager";void Test(JNIEnv *env, jobject thiz) {LOGE("动态注册"); }jstring stringFromJNI(JNIEnv *env, jobject thiz) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str()); } // 创建要绑定的native函数 对应的映射表 JNINativeMethod jniMethod[] = {{"Test", "()V", (void *) Test},{"stringFromJNI", "()Ljava/lang/String;", (jstring *) stringFromJNI}, }; // System.loadLibrary 后第一个执行的函数 int JNI_OnLoad(JavaVM *vm, void *unused) {LOGE("JniOnLoad");//保存为全局变量javaVM_vm = vm;// 保存全局变量JNIEnvJNIEnv *env = nullptr; //nullptr 取代NUll给指针赋值 也可以直接写0// 这里要传递env指针的地址进去方便给该地址指向的指针赋值int success = vm->GetEnv((void **) &env, JNI_VERSION_1_6);//判断是否成功if (success != JNI_OK) {LOGE("SUCCESS %d:", success);return -1;}// 获取要绑定的类 即对应的java 交互类jclass jcls = env->FindClass(mClassName);// 注册要关联的native方法env->RegisterNatives(jcls, jniMethod, sizeof(jniMethod) / sizeof(JNINativeMethod));return JNI_VERSION_1_6; }
步骤总结
- 创建函数映射表
- 获取函数对应java类
- 获取JNIEnv,通过JNIEnv注册 函数映射表
优势
- 动态注册 命名更灵活
1.6 数据类型转换
1.6.1 基本数据类型
java类型 | nativie类型 | 描述 |
---|---|---|
boolean | jboolean | unsigned 8 bits 整型 |
byte | jbyte | signed 8 bits 整型 |
char | jchar | unsigned 16 bits 整型 |
short | jshort | signed 16 bits 整型 |
int | jint | signed 32 bits 整型 |
long | jlong | signed 64 bits 整型 |
float | jfloat | signed 32 bits 浮点型 |
double | jdouble | signed 64 bits 浮点型 |
void | void | 无整形 |
1.6.2 引用数据类型
java | native |
---|---|
object | jobject |
java.lang.Class instance | jclass |
java.lang.String instance | jstring |
array | jarray |
Object[] | jobjectArray |
boolean[] | jbooleanArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double[] | jdoubleArray |
java.lang.Throwable objects | jthrowable |
1.6.3 函数签名
格式
[参数1类型字符参数2类型字符...]返回值类型字符
注意引用类型需以
L
开头后跟类型全路径并以;
结尾- 例
Ljava/lang/String;
- 例
参数
若为多参数也无需间隔直接后面添加类型字符即可
例
java 方法 方法签名 String getStr(int a,int b)
(II)Ljava/lang/String;
String getS(int a,String b)
(ILjava/lang/String;)Ljava/lang/String;
若没参数则括号内不写内容若无返回值则返回值位置用V
- 例
()V
- 例
注意引用类型需以
L
开头后跟类型全路径并以;
结尾- 例
Ljava/lang/String;
- 例
对照表
Java类型 对应字符 void V boolean Z int I long J double D float F byte B char C short S int[] [I ( 数组以[开始后跟对应类型字符
)String Ljava/lang/String; ( 引用类型 以
L开头后跟类路径以
;结尾
)Object[] [Ljava/lang/object;
1.7 JNI引用
局部引用(Local Reference)
在函数内部创建,声明的变量和对象都属于局部引用
方法调用结束,局部引用自动释放
当然也可以手动释放
DeleteLocalRef() // 创建的对象用DeleteReleaseXXX
全局引用(Global Reference)
JNI 允许从局部变量创建全局变量
//声明全局引用 jclass st1Class; extern "C" JNIEXPORT jobject JNICALL Java_com_example_ndktest_NdkManager_changName(JNIEnv *env, jobject thiz, jobject st, jstring name) {//查找类if(st1Class==NULL){jclass cls = env->FindClass("com/example/ndktest/Student");//声明为全局引用st1Class= static_cast<jclass>(env->NewGlobalRef(cls));env->DeleteLocalRef(cls);//释放局部引用//env->DeleteGlobalRef(st1Class);//释放全局引用} }
可以跨方法跨线程
释放需调用
DeleteGlobalRef
弱全局引用(Weak Global Reference)
与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的VM内部的对象
所以在使用弱引用时,必须先检查缓存过的弱引用是指向活动的对象,还是指向一个已经被GC的对象
创建
//声明全局引用 jclass st1Class; extern "C" JNIEXPORT jobject JNICALL Java_com_example_ndktest_NdkManager_changName(JNIEnv *env, jobject thiz, jobject st, jstring name) {//判断是否指向活动的对象jboolean isEqual =env->IsSameObject(st1Class,NULL);if(st1Class==NULL||isEqual){jclass cls = env->FindClass("com/example/ndktest/Student");//声明为全局引用st1Class= static_cast<jclass>(env->NewWeakGlobalRef(cls));env->DeleteLocalRef(cls);//释放局部引用//env->DeleteWeakGlobalRef(st1Class);//释放弱全局引用} }
释放
- 调用
DeleteWeakGlobalRef
来释放
- 调用
NDK
简介
Android NDK 是一组允许您将 C 或 C++(“原生代码”)嵌入到 Android 应用中的工具,NDK描述的是工具集
是通过
jni
调用的c/c++的原生代码的
重要结构目录
- 我们可以在 sdk/ndk-bundle 中查看 ndk 的目录结构,下面列举出三个重要的成员:
- ndk-build: 该 Shell 脚本是 Android NDK 构建系统的起始点,一般在项目中仅仅执行这一个命令就可以编译出对应的动态链接库了。
- platforms: 该目录包含支持不同 Android 目标版本的头文件和库文件, NDK 构建系统会根据具体的配置来引用指定平台下的头文件和库文件。
- toolchains: 该目录包含目前 NDK 所支持的不同平台下的交叉编译器 - ARM 、X86、MIPS ,目前比较常用的是 ARM。 // todo ndk-depends.cmd
交叉编译
- 在一个平台上编译出另一个平台上可以执行的二级制文件的过程叫做交叉编译
- 比如在windows上编译出android可用的库文件
库文件格式
- 静态库 `.a
- 编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了,linux中后缀名为”.a”
- 动态库
.so
- 在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库。linux 中后缀名为”.so”,gcc在编译时默认使用动态库。
makefile (.mk) 编译
简介
makefile 就是“自动化编译”:一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,如何进行链接等等操作。 Android 使用 Android.mk 文件来配置 makefile
1.1 Android.mk
# 源文件在的位置。宏函数 my-dir 返回当前目录(包含 Android.mk 文件本身的目录)的路径。 LOCAL_PATH := $(call my-dir)# 引入其他makefile文件。CLEAR_VARS 变量指向特殊 GNU Makefile,可为您清除许多 LOCAL_XXX 变量 # 不会清理 LOCAL_PATH 变量 include $(CLEAR_VARS)# 指定库名称,如果模块名称的开头已是 lib,则构建系统不会附加额外的前缀 lib;而是按原样采用模块名称,并添加 .so 扩展名。 LOCAL_MODULE := hello # 包含要构建到模块中的 C 和/或 C++ 源文件列表 以空格分开 LOCAL_SRC_FILES := hello.c # 构建动态库 include $(BUILD_SHARED_LIBRARY)
1.2 对应Gradle的设置
app/gradle
apply plugin: 'com.android.application'android {compileSdkVersion 29defaultConfig {...// 应该将源文件编译成几个 CPU soexternalNativeBuild{ndkBuild{abiFilters 'x86','armeabi-v7a'}}// 需要打包进 apk 几种 sondk {abiFilters 'x86','armeabi-v7a'}}// 配置 native 构建脚本位置externalNativeBuild{ndkBuild{path "src/main/jni/Android.mk"}}// 指定 ndk 版本ndkVersion "20.0.5594570"... }dependencies {implementation fileTree(dir: "libs", include: ["*.jar"])... }
Google 推荐开发者使用 cmake 来代替 makefile 进行交叉编译了,makefile 在引入第三方预编译好的 so 的时候会在 android 6.0 版本前后有些差异,比如在 6.0 之前需要手动 System.loadLibrary 第三方 so,在之后则不需要。 关于 makefile 还有很多配置参数,这里不在讲解,更多参考官方文档。
- 在 6.0 以下,System.loadLibrary 不会自动加载 so 内部依赖的 so 在 6.0 以下,System.loadLibrary 会自动加载 so 内部依赖的 so 所以使用 mk 的话需要做版本兼容
Cmake 编译
- 是一个构建工具
1.1 CMakeLists.txt
# cmake版本 cmake_minimum_required(VERSION 3.10.2)#声明并命名项目 project("ndktest")# 声明并命名库 # 将其设置 是否共享 此处有三个值 # SHARED: 表示动态库,可以在(Java)代码中使用 System.loadLibrary(name) 动态调用; # STATIC: 表示静态库,集成到代码中会在编译时调用; # MODULE: 只有在使用 dyId 的系统有效,如果不支持 dyId,则被当作 SHARED 对待; # EXCLUDE_FROM_ALL: 表示这个库不被默认构建,除非其他组件依赖或手工构建; # 添加库中源文件的路径 add_library( # 设置库的名字 比如现在会生成 ndktest.sondktest# 设置库为共享库SHARED# 提供源文件相对路径native-lib.cpp) # 搜索并指定预构建库并将路径存储为变量(这里是log-lib)。 # NDK中已经有一部分预构建库(比如 log),并且ndk库已经是被配置为cmake搜索路径的一部分 # 可以不写 直接在 target_link_libraries 写上log # 比如上文中我们引入的 android/log.h 就是从调用的ndk 目录下的 liblog.so 前面的lib可省略直接写log 即可找到 find_library( # 设置路径变量名称log-lib# 从系统路径中查找指定名字的so库路径 赋值给上面的log-liblog)# 指定CMake应链接到目标库的库。可以链接多个库,例如在此生成脚本中定义的库、预构建的第三方库或系统库。 target_link_libraries( # 指定目标库ndktest# 链接目标库的路径 这里是log的路径 即上面 find_library 中 的log-lib # 这种是以变量形式链接目标库路径 当然我们也可以省区find_library这一步直接指定目标库# 就比如这里的ndktest 直接写log 也可以找到# 如果需要引入三方so 就得指定目录来查找了${log-lib})
1.2 Gradle 中的配置
android {//......defaultConfig {applicationId "com.example.ndktest"minSdk 21targetSdk 31versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"externalNativeBuild {cmake {// 设置c++ 标准cppFlags '-std=c++11'//需要生成几种cpu架构下的so,不写默认都生成abiFilters "armeabi-v7a","x86"}}// 打包支持几种架构的apk包 比如这里就会生成支持x86 和armeabi-v7a两种的架构的apkndk {abiFilters 'x86','armeabi-v7a'}}// 分架构打包出不同架构的apksplits {abi {enable truereset()include 'armeabi-v7a', 'x86'universalApk true}}//......// 配置native构建脚本的文件路径externalNativeBuild {cmake {path file('src/main/cpp/CMakeLists.txt')version '3.10.2'}}//..... }
externalNativeBuild
在defaultConfig
中为指导源文件编译 在defaultConfig
外为配置native的构建脚本路径
添加多个源文件
- 可以在cmake文件内的addlibrary后继续添加 如下
add_library( # 设置库的名字 比如现在会生成 ndktest.sondktest# 设置库为共享库SHARED# 提供源文件相对路径native-lib.cpptest.cpp)
- 也可以设置一个变量存储 源文件相对路径 然后添加到add_library中
set(LIBSRC native-lib.cpp test.cpp)
add_library( # 设置库的名字 比如现在会生成 ndktest.sondktest# 设置库为共享库SHARED# 提供源文件相对路径${LIBSRC})
- 也可以设置路径 模糊匹配
file(GLOB native_srcs "src/main/cpp/*.cpp")
add_library( # 设置库的名字 比如现在会生成 ndktest.sondktest# 设置库为共享库SHARED# 提供源文件相对路径${native_srcs})
引用三方动态库.so
假如引用一个c 编写的so ->
libtest.so
内只含有一个hc 文件这里将
CMakeList.txt
从cpp目录移动到app目录下 方便后面拼接路径将对应架构的so 放到
jinLibs
对应架构目录下 这里以armeabi-v7a
为例
方式一
增加 CMake 查找路径 直接给 cmake 在添加一个查找路径,在这个路径下可以找到 external
在
CMakeList.txt
中添加如下代码 代码级别与 target_link_libraries、find_library同级别#设置C++ 编译 -L 设置库路径 -L设置的路径与下面的link_directories 应该类似 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CURRENT_SOURCE_DIR}/jniLibs/${CMAKE_ANDROID_ARCH_ABI}") #指定静态库或动态库的搜索路径 该指令的作用主要是指定要链接的库文件的路径 #自己写的动态库文件放在自己新建的目录下时,可以用该指令指定该目录的路径以便工程能够找到。 link_directories(${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI} )
再
CMakeList.txt
中设置一个变量CMAKE_CXX_FLAGS
使用c++编译为c++环境下的变量名CMAKE_C_FLAGS
使用c编译 为C环境下的变量名
该变量会传递给编译器
重新定义该变量 通过
-L
指定 目标路径 可以通过如下变量 动态识别目录 从而查找指定的soCMAKE_SOURCE_DIR
当前文件路径ANDROID_ABI
是abi
目录
link_directories
- 指定静态库或动态库的搜索路径 该指令的作用主要是指定要链接的库文件的路径
- 自己写的动态库文件放在自己新建的目录下时,可以用该指令指定该目录的路径以便工程能够找到。
将
test
写到target_link_libraries
表示我们要链接libtest.so
so 以lib开头时 写到
target_link_libraries
或find_library
时 lib 可以省略只写名字即可target_link_libraries( # Specifies the target library.ndktesttest# Links the target library to the log library# included in the NDK.${log-lib})
方式二
方式二
在
CMakeList.txt
中添加如下代码 代码级别与 target_link_libraries、find_library同级别且需要在systemload中 load 该so
这种方式在android 6.0 以下还能用 6.0 以上就会出现目录不对的问题 所以cmake中 我们一般采用第一种方式
# test 代表第三方 so - libtest.so # SHARED 代表动态库,静态库是 STATIC; # IMPORTED: 表示是以导入的形式添加进来(预编译库) add_library(test SHARED IMPORTED)#设置 test 的 导入路径(IMPORTED_LOCATION) 属性,不可以使用相对路径 # CMAKE_SOURCE_DIR: 当前cmakelists.txt的路径 (cmake工具内置的) # android cmake 内置的 ANDROID_ABI : 当前需要编译的cpu架构 set_target_properties(external PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libtest.so)
将
test
写到target_link_libraries
表示我们要链接libtest.so
target_link_libraries( # Specifies the target library.ndktesttest# Links the target library to the log library# included in the NDK.${log-lib})
引入so后使用
若
libtest.so
内存在test()
函数
情况一 存在test.h
直接
#include<test.h>
若
test
函数是用c 编写的我们的环境又是c++环境 则需要提前标记
extern "C"
才能正常调用extern "C" {extern void test(); }
不是c++ 环境 则直接调用test函数即可
情况二 不存在 test.h
则要将函数声明为 extern 即 外部定义在这里引用
extern void test();
若
test
函数是用c 编写的我们的环境又是c++环境 则需要提前标记
extern "C"
才能正常调用extern "C" {extern void test(); }
不是c++ 环境 则直接调用test函数即可
引入静态库 .a
不用必须放在jnilibs 目录下 只需要像引用so一样设置好库的查找路径即可
# test1 代表第三方.a文件 全名是libtest1.a # SHARED 代表动态库,静态库是 STATIC; # IMPORTED: 表示是以导入的形式添加进来(预编译库) add_library(test1 STATIC IMPORTED)#设置 test 的 导入路径(IMPORTED_LOCATION) 属性,不可以使用相对路径 # CMAKE_SOURCE_DIR: 当前cmakelists.txt的路径 (cmake工具内置的) # android cmake 内置的 ANDROID_ABI : 当前需要编译的cpu架构 AS3.2后, ${ANDROID_ABI} 改成 ${CMAKE_ANDROID_ARCH_ABI} set_target_properties(external PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libtest1.a)
使用方式 和上面so 使用方式相同
JNI(一) JNI - NDK-交叉编译相关推荐
- jni ndk_带有NDK的Android JNI应用程序
jni ndk In this tutorial, we'll be discussing JNI and develop a basic Android Application using NDK ...
- NDK交叉编译及so库导入Android项目
前言 记录 NDK交叉编译及so库导入Android项目 的简单步骤,以备后续用到时查看. 环境 在Linux和Mac环境下,分别编译输出so库. Red Hat Enterprise Linux 8 ...
- 【Android NDK 开发】NDK 交叉编译 ( Ubuntu 中交叉编译动态库 | Android Studio 中配置使用第三方动态库 )
文章目录 I . 动态库 与 静态库 II . 编译动态库 III. Android Studio 使用第三方动态库 IV . Android Studio 关键代码 V . 博客资源 I . 动态库 ...
- android ndk交叉编译pcl 1.9.1
目录 android ndk交叉编译pcl 1.9.1 依赖库 android ndk 下载 使用cmake交叉编译lib库 编译pcl 完整的android studio cmake配置代码 and ...
- devc 如何编写java,Android For JNI(1)——JNI的概念以及C语言开发工具dev-c++,编写你的第一个C语言程序,使用C启动JAVA程序...
Android For JNI(一)--JNI的概念以及C语言开发工具dev-c++,编写你的第一个C语言程序 当你的Android之旅一步步的深入的时候,你其实会发现,很多东西都必须去和framew ...
- 【Android NDK 开发】NDK 交叉编译 ( NDK 函数库目录 | Linux 交叉编译环境搭建 | 指定头文件目录 | 指定函数库目录 | 编译 Android 命令行可执行文件 )
文章目录 I . NDK platforms 目录下的 函数库 II . Ubuntu 配置 NDK 交叉编译环境 III . 同时指定编译的头文件和库文件 IV . 指定编译的头文件 V . 指定编 ...
- NDK 交叉编译常用变量
总结一发NDK交叉编译的套路 工具说明 addr2line 把程序地址转换为文件名和行号.在命令行中给它一个地址和一个可执行文件名,它就会使用这个可执行文件的调试信息指出在给出的地址上是哪个文件以及行 ...
- CURL NDK 交叉编译
移植curl到android,且支持https和http2.0 依赖前两篇文章 libnghttp2 NDK 交叉编译 openssl NDK 交叉编译 创建工作目录,并进入 1 2 mkdir an ...
- android 能调用gcc_如何在命令行下使用Android NDK交叉编译工具
我们知道,在Linux下可以使用gcc来把一份C代码编译成为Linux上的可执行程序, 如: $ gcc -o main.out main.c 而Android平台提供了NDK工具包来交叉编译可以运行 ...
- 如何在命令行下使用Android NDK交叉编译工具
我们知道,在Linux下可以使用gcc来把一份C代码编译成为Linux上的可执行程序, 如: $ gcc -o main.out main.c 而Android平台提供了NDK工具包来交叉编译可以运行 ...
最新文章
- 图解 navicat for oracle 的使用
- html5 抽奖程序,真正的网页抽奖-JavaScript HTML5代码
- Leetcode--152. 乘积最大子序列
- ubuntu 14.04 LTS(64bit) Anacoda2环境下安装gensim
- PHP如何获取文件行数
- CDM是什么?和CDP有什么区别?
- LeetCode-151 Reverse Worlds in a String
- android 抽屉式滑动demo
- linux apache配置虚拟主机,linux环境apache多端口配置虚拟主机的方法
- 【Clover】服务器环境中通过Clover boot引导黑群晖DSM(Linux)+Win系统的解决方案与常见bug排查
- windows下cmd下载文件
- 基于AM5728 DSP+ARM平台的嵌入式车牌识别
- AP计算机从懵懵懂懂到突飞猛进-----加利福尼亚学生满分经验分享!
- css图片横向压缩,【css样式生成 图片合并压缩工具】Sprite,你值得拥有
- mysql多表sumif_MySQL与EXCEL sum sumif sumifs 函数结合_品牌汇总_20161101
- 好用的图片翻译器有哪些?这3个工具你们不能不知道
- python作者怎么读_python 读键盘
- Matlab基础之矩阵循环
- 【北航软院+保研复习】计算机网络复习笔记
- 树莓派Bullseye系统安装QT
热门文章
- 90后与老爸老妈斗智斗勇
- 解决echarts设置x轴、y轴刻度起始值、终止值以及步长
- Android GUI系统之SurfaceFlinger(01)显示系统框架
- 实战内容(3)- 未能打开文稿“*.mp4”,文件与QuickTime Player不兼容
- RF连接Mysql数据库
- 分分钟实现梦想 —— 两种快速打造App的方法
- Unity的2.5D效果探究
- 移动端vant input框点击时禁止弹出手机键盘
- 创建SDE企业级数据库——在 Oracle 中配置 extproc 以访问 ST_Geometry
- 具有DPP-4抑制活性的新型氨甲基联苯衍生物