前语
咱们都知道Google最初为了让Java开发者能快速介入到Android开发,把linux操作系统中c/c++完成的各种系统才能运用Java封装起来,此举的确招引了大量的Java开发者转战移动途径,终究极大的丰富了Android的生态,所以直到今天Java(kotlin)仍然是Android的首要开发言语。Java的确让开发者更简略的开发移动应用程序,可是也把开发者隔离在Android系底层才能之外,可是言语层面并不是没有从Java到c++的途径,JAVA Native Interface(JNI)便是最重要的一种办法。
什么是JNI
JNI全称Java Native Interface。Java本地接口。这是Java设计的一套Java与c/c++进行交流的接口标准。
咱们先想象一下,一个Java的调用办法想要进入C++中需求什么?Java需求什么?c++需求什么?
比方两段代码
java办法
package com.example.applicationnative
public class NativeFun {
// native要害字表明这是一个本地办法
native string getContent(int age,string name);
}
cpp办法
// native-lib.cpp
// cpp ==>complie ==> xxx.so
JNIEXPORT jstring JNICALL c_get_content(JNIEnv *env,jobject jb,jint a,jstring n ){
...
...
return env->NewStringUTF(content.c_str());
}
我分别写了Java办法和cpp办法,我经过在Java中把办法声明为native,向VM表明我期望这个办法终究调用到cpp中对应的某个办法。可是VM也是两眼一抹黑,只知道Java native办法,可是不知道它想找哪个cpp函数。 因而,想要正常的让Java办法和cpp函数建立起映射过联系,大约需求做好三件事:
- 函数建立映射联系
- 供给参数的数据类型的转化
- 供给对数据的操作函数(以及一些其他函数)
而这三点,恰恰是JNI标准的首要内容。
函数映射
静态注册
静态注册本质上是经过函数名之间建立起对应联系,因为Java办法所处的全途径+类名+函数名一般就能定位某个办法了,因而,假定咱们把cpp中的函数名写成如下这种:
// Java_包名_类名_办法名
JNIEXPORT jstring JNICALL Java_com_example_applicationnative_NativeFun_getContent(JNIEnv *env,jobject jb,jint a,jdouble s){
...
...
return env->NewStringUTF(content.c_str());
}
依据上面函数名的命名规矩,cpp就能够找到对应的办法在哪个位置,这种标准类似于一种约好。当然,这是一个简化的版本,假设是重载的办法,就会同名的状况,因而完好版的cpp函数命名办法应该是这样的:
// Java_包名_类名_办法名
JNIEXPORT jstring JNICALL Java_com_example_applicationnative_NativeFun_getContent__ILjava_lang_String_2(JNIEnv *env,jobject jb,jint a,jdouble s){
...
...
return env->NewStringUTF(content.c_str());
}
I表明参数int,java_lang_string_2表明string类型,这样,就能够真正唯一确认某个办法的位置了。不过一般咱们界说native重载办法不是很多,所以一般用简化版即可。
动态注册
有静态注册天然有动态注册,静态注册需求cpp函数名有必定的规矩,因而写起来还比较繁琐,可是动态注册对cpp函数名就没有约束了。
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {
...
...
// getContent便是Java的native办法,c_get_content则是对应的cpp函数的指针
JNINativeMethod cMthods[] = {{"getContent","()Ljava/lang/String;",(jstring *)c_get_content}};
const char *className = "com/example/applicationnative/NativeDemo";
// 取得env指针 env是指向指针的指针
if ((vm->GetEnv((void **) &env,JNI_VERSION_1_6))!=JNI_OK){
return result;
}
if (env == nullptr){
return result;
}
//获取到对应的类
jclass currentClass = env->FindClass(className);
if (currentClass == nullptr){
return result;
}
// env->RegisterNatives 手动注册,建立起c_get_content与getContent的映射联系
jint registerResult = env->RegisterNatives(currentClass,cMthods, getArrayLength(cMthods));
if (registerResult<0){
return result;
}
}
上面的代码能看懂注释即可,其实便是调用了JNI标准中界说的一个接口RegisterNatives函数,这个函数需求完成动态注册的功用。
数据类型转化
有了注册的才能,JNI就能够初步建立起Java与cpp的对应联系。不过交流需求数据的传递,Java的数据类型和cpp的数据类型是不同的,Java中有的根本类型cpp或许能够直接对应,可是很多是不行的。因而由JNI一致为Java的数据类型在cpp层面界说对应的一套数据类型的映射联系。
这种数据类型能够分为两种:原始类型和引证类型。
原始类型的映射
Java中的char和cpp中的char就无法相容,因编码不同导致所占的字节数不同,无法转化。就需求界说jchar
引证类型的映射
类型转化的问题
为了完成Java与cpp的数据传递,JNI在cpp层界说了一组数据类型来与Java层对应,可是这呈现了JNI数据类型与cpp原生数据类型之前的类型转化的问题,假设JNI函数需求与第三方cpp库打交道,那么这种数据类型的转化在所难免。
不过只需咱们能够依据数据类型的占位和编码的区别来进行类型转化,就不会出大问题,作为JNI的初步引导等级的文章在此就不持续延伸了。
操作函数表
有了办法的映射和数据类型的映射,可是还缺少相同东西,那便是对数据的操作接口。JNI中界说了大量的函数接口,不仅有对cpp层的映射数据类型的操作接口,还有对Java层的类和办法的操作接口。具体的接口能够看官方文档
我也大致对相关的接口进行了分类总结:
这些参数都界说在一个JNINativeInterface的结构体中,然后给它取了一个别号JNIEnv指针
// JNIEnv仅仅类型别号,函数指针数组界说再结构体JNINativeInterface中
typedef const struct JNINativeInterface *JNIEnv;
所以JNIEnv类型的指针指向了这个结构体,然后能够拜访结构体中界说的一切函数
而在咱们界说的JNI函数中,JNIEnv这个指针类型的参数是每个被映射的cpp函数中传入的榜首个参数,也就便利咱们能够运用JNI界说的操作函数。
有了办法映射,数据类型映射,以及函数操作表这三部分标准之后,咱们才能够说JNI完好完成了Java和cpp的的彼此交流机制。
JNI的实际操作
上半部分从概念上讲了JNI到底是一个怎样的接口标准,它首要界说了什么内容,当然JNI除了上文讲的三个部分之外,还界说了诸如引证的创立和开释,异常的处理,jvm的操作等,这些部分是对JNI的完善,并不影响咱们了解JNI标准的结构。纸上得来终觉浅,假设不运用来个实际的demo来运转一下,就难以加深了解。
环境初始化
在编写cpp代码模块代码之前,咱们往往需求在Android studio中做好相关的装备,在此就不多赘述了,建议能够直接参考Android官网的进程
Java代码
假定在此之前,你现现已过官网主动创立了一个c++项目,里边会包括一个默许的JNI函数,咱们能够不去管它 咱们在Android工程中界说一个类
package com.example.applicationnative;
public class NativeFun {
public native String callFromJava(String content,int v);
public native String callFromJavaAdd(int x,int y);
public void callFromCpp(String content){
Log.i("JNI_TEST",content);
}
}
然后再咱们的MainActivity中创立这个类的目标,并且调用前两个办法。
cpp 代码
假设咱们按照官网的进程进行初始化,则会产生一个默许的cpp文件,里边有一个默许的映射的办法,能够不用管。cpp的目录结构大约如下图
CMakeLists.txt是cmake读取用来构建makefile构建文档,构建部分咱们放在后边来讲,在此按下不表。native-lib.cpp是Android给咱们默许创立的cpp文件,咱们能够不改,里边默许的代码咱们也能够不管,直接在后边写咱们自己的demo代码。
依据前文描绘咱们知道,完成java与cpp的交流咱们需求谨记三个要害点,函数的映射,数据类型的映射,JNI函数表。JNI的衔接需求函数映射和数据包类型的映射,而本地功用的具体完成则需求依靠函数表。
首要咱们需求成功建立起Java与cpp的链接。
// 榜首部分
#include <jni.h>
#include <android/log.h>
// 第二部分
#define LOG_TAG "native_demo"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
// 第三部分
//映射 callFromJavaAdd
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_applicationnative_NativeFun_callFromJavaAdd(JNIEnv *env, jobject thiz, jint x,
jint y) {
return env->NewStringUTF("hello callFromJavaAdd from native");
}
// 第四部分
// 映射callFromJavaStr办法
JNIEXPORT jstring JNICALL c_callFromJavaStr(JNIEnv *env, jobject thiz, jstring content, jint v) {
return env->NewStringUTF("hello callFromJavaStr from native");
}
JNINativeMethod cNativeMthods[] = {{"callFromJavaStr", "(Ljava/lang/String;I)Ljava/lang/String;", (jstring *) c_callFromJavaStr}};
const char *className = "com/example/applicationnative/NativeFun";
// 界说一个获取数组长度的办法
template<class T>
JNIEXPORT jint JNICALL getArrayLength(T &arr) {
return sizeof(arr) / sizeof(arr[0]);
}
代码剖析
上面的代码是需求和上层Java native办法形成映射的cpp函数,咱们期望一起运用动态注册和静态注册,咱们能够简略的解释一下这个cpp文件中代码的意义:
预编译指令
榜首部分 #include是预编译指令,包括对应的头文件,为了完成对外的接口露出,cpp运用头文件的办法,把类,函数,变量的声明放在头文件中,而对应的界说则放在对应的源文件中,其他cpp代码想要引证该代码的才能时,就把对应的头文件包括进来即可。目前咱们先包括了jni.h和log.h。
宏界说指令
第二部分 #define同样是预编译指令,是cpp的宏界说,宏界说的原理便是粗暴的文本替换,比方 #define LOG_TAG “native_demo”指令,在接下来运用LOG_TAG 的当地就会在预编译阶段直接替换成 “native_demo”。__android_log_print是log.h中声明的一个log函数,能够协助咱们把log打到Androidstudio的log窗口。
静态注册的函数
第三部分 界说了一个cpp函数,它是NativeFun类的callFromJava函数在cpp层的映射函数,虚拟机经过它有规矩的命名办法就能建立其Java和cpp的函数映射联系。可是关于函数体能够简略解释一下:
extern : extern要害字直接放在变量或许函数之前,意思便是向编译器表明:我保证这个变量(函数)现已界说过了;
extern "C" 后边跟"C" 然后再跟着函数或变量之前,意思是向编译器表明 我期望这个函数(变量)能够按照C的办法进行编译,而不是cpp的办法编译。这两者的差别首要在于cpp为了支撑多态区分同名的函数,会把同名的函数的函数名解析成函数名+参数类型相关的函数名,比方int f(int a); =>f_int类似这种形式,而C不会对函数名做修改,所以假定在C代码中调用c++的函数,在链接阶段会呈现函数名找不到的状况(因为两者解析函数名的办法不相同)。假定你的代码里函数名不重要,这个修饰符不加也行。可是咱们现在便是要依靠固定规矩的函数名来完成映射联系,所以这个extern "C"不行或缺。
JNIEXPORT :从名称上也能大约了解,咱们是否想要把这个JNI接口调用露出出去,要的话就要加上这个。
JNICALL :JNI的调用标准。
假设点击代码跳转,咱们会发现这两个都是宏界说,
#define JNIEXPORT __attribute__ ((visibility ("default"))) // defalut便是露出接口对外可见,假设期望设置不行见用hidden
#define JNICALL // JNICALL界说为空(linux途径),假设不加这个其实也行
动态注册逻辑
第四部分是cpp函数以及一些变量的界说,c_callFromJavaStr是咱们期望与Java层的callFromJavaStr形成映射的函数,可是咱们能看到这没有按照JNI静态注册时要求的函数命名规矩来界说函数,因而咱们就只能运用动态注册,即手动建立起Java办法与cpp函数的映射联系。
一般而言愈加引荐运用动态注册的办法来建立Java与cpp的映射联系。
想要经过动态注册的办法来完成函数映射,依靠函数表中的注册函数:
// clazz 是Java办法对应的class目标
// JNINativeMethod 是一个结构体,也能够了解为一个目标,包括Java办法与JNI函数的一些信息,
// 注意JNINativeMethod * 指着类型的是表明JNINativeMethod 数组
// nMethods 表明当前JNINativeMethod 数组有几个元素
// 从上面的参数界说能够看出,RegisterNatives能够一次性界说多个办法
// 该函数的回来值是jint类型,对应了Java的int类型,回来0是成功,小于0是失利
jint RegisterNatives(jclass clazz,const JNINativeMethod *methods, jint nMethods); //动态注册接口
// JNINAtiveMethod的结构界说
typedef struct {
char *name; // Java函数名 字符串类型
char *signature; // 函数签名的指针 字符串类型
void *fnPtr; // cpp对应的函数指针,原本void *能够表明恣意类型的指针,可是JNI文档中指出咱们应该填入一个函数指针
} JNINativeMethod; // 依据这个结构体界说了一个JNINativeMethod类型
动态注册完成
依据上面的文档,咱们大约了解了怎样调用注册函数,接下来的问题是,什么时分调用这个函数?明显咱们需求依靠JNI标准中界说的某些生命周期类型的回调函数。刚好就有两个JNI_Onload,JNI_OnUnload,顾名思义,前者是在对应的Android native library加载时,调用这个函数;后者则是本地库被卸载整理时调用。明显咱们需求前者这个回调函数。
所以咱们持续在cpp文件的第四部分代码后边增加如下代码
extern "C"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {
jint result = -1;
//中间是动态注册的逻辑
result = JNI_VERSION_1_6;
return result;
}
咱们能看到JNI_OnLoad的参数并不是咱们上面看到的JNIEnv *env最初,而是JavaVM *vm,可是不用慌,经过JavaVM指针咱们就能拿到JNIEnv的指针。
jint GetEnv(void **env, jint version);
Java Signature
结构参数中,还有个signature不算太长久,指的是函数签名
Java签名表明Java中对字段/办法的描绘办法。即向虚拟机描绘某个字段(办法)长什么姿态。Java签名常常被用于反射,字节码等技术中。 在Java字段中的根本类型,引证类型都有特定的表述办法来描绘它们
而Java办法的签名描绘办法是固定:
// param_list 多个办法参数之前不需求区隔,直接连在一起
(param_list)return_type
咱们来举个例子:
class A{
string getRealContent(String origin,int index){
return "";
}
}
//那么getRealContent的办法签名便是如下
(Ljava/lang/String;I)Ljava/lang/String;
结构注册参数
经过咱们对Java签名的了解之后,咱们再回看RegisterNatives的参数要求也不奇怪,想要唯一确认一个办法的位置,首要需求直到它在哪个类里,其次需求直到它的办法名,然后需求直到这个办法长什么姿态(防止同名的办法无法分辨的状况),也便是签名。所以需求jclass,JNINativeMethod.
// cNativeMthods中咱们界说了一个元素,包括了callFromJavaStr的描绘和对应映射的cpp函数
JNINativeMethod cNativeMthods[] = {{"callFromJavaStr", "(Ljava/lang/String;I)Ljava/lang/String;", (jstring *) c_callFromJavaStr}};
// 界说了Java办法地点的全类名,协助咱们找到对应的class
const char *className = "com/example/applicationnative/NativeFun";
// 界说了一个获取数组的大小的办法模板,
template<class T>
JNIEXPORT jint JNICALL getArrayLength(T &arr) {
return sizeof(arr) / sizeof(arr[0]);
}
extern "C"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {
JNIEnv *env;
jint result = -1;
LOGI("JNI_OnLoad 函数开端 ============》 ");
// 取得env指针 env是指向指针的指针
if ((vm->GetEnv((void **) &env, JNI_VERSION_1_6)) != JNI_OK) {
LOGI("获取环境指针 失利了");
return result;
}
if (env == nullptr) {
LOGI("env=null ============》 ");
return result;
}
//获取到对应的类
jclass currentClass = env->FindClass(className);
if (currentClass == nullptr) {
LOGI("查找对应的类失利了============》 ");
return result;
}
// 手动注册办法映射
jint registerResult = env->RegisterNatives(currentClass, cNativeMthods,
getArrayLength(cNativeMthods));
//查看注册成果
if (registerResult < 0) {
LOGI("注册失利了============》 ");
return result;
}
LOGI("注册成功!!!============》 ");
result = JNI_VERSION_1_6;
return result;
}
经过手动注册函数的办法,咱们也建立起了办法的映射。理论上现在打包运转之后就能够完成Java与c++的交流了。
cpp反向调用
可是无论是静态注册仍是动态注册,本质上都是Java层主动的调用cpp层的办法,交流有必要是彼此的,那么cpp必定也需求有能主动调用Java的办法。而调用Java办法首要依靠CallMethod办法。而调用这个目标的办法明显需求目标实例,以及办法的名称、签名等一些信息来定位。
JNIEXPORT void JNICALL call_java_method(JNIEnv *env,jobject obj) {
// 仍然是"com/example/applicationnative/NativeFun"
jclass clazz = env->FindClass(className);
if (clazz == nullptr){
return;
}
// 经过class,以及办法的标识信息来找到对应的办法id
jmethodID method_id = env->GetMethodID(clazz,"callFromCpp","(Ljava/lang/String;)V");
// 想要把信息传递到Java层,有必要运用映射的数据类型。
jstring content = env->NewStringUTF("来自cpp的问好");
// 调用Java办法
env->CallVoidMethod(obj,method_id,content);
// 用完删除部分引证是个好习惯,防止内存泄漏
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(content);
}
然后咱们在适宜的机遇调用call_java_method即可,因为需求获取jobject,咱们能够挑选在Java办法调用过来时再调用该函数
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_applicationnative_NativeFun_callFromJavaAdd(JNIEnv *env, jobject thiz, jint x,
jint y) {
LOGI("x=%d y=%d",x,y);
// thiz便是当前调用了callFromJavaAdd本地办法的目标实例
call_java_method(env,thiz); //调用Java办法
return env->NewStringUTF("hello callFromJavaAdd from native");
}
完好代码
咱们简略整理一下代码,cpp代码在调用函数时,函数有必要现已声明过或许界说过了,也便是说cpp里某段代码想要调用某个函数,那这个函数有必要在这段代码之前现已声明或许界说过,在这段代码之后都无法辨认,因而咱们一般会把一个文件里的函数声明都一致放在一个头文件里,然后再cpp文件的顶部进行引证即可。本例中我就界说了一个native.h头文件。
以下是JNI的完好代码:
// native.h 头文件
// 预编译 条件判断指令,防止头文件被重复引证
#ifndef APPLICATIONNATIVE_NATIVE_H
#define APPLICATIONNATIVE_NATIVE_H
// 模板函数
template<class T>
JNIEXPORT jint JNICALL getArrayLength(T &arr) {
return sizeof(arr) / sizeof(arr[0]);
}
//提前声明这个函数
void call_java_method(JNIEnv *env,jobject obj);
#endif //APPLICATIONNATIVE_NATIVE_H
// native-lib.cpp 文件
#include <jni.h>
#include <string>
#include <android/log.h>
#include "native.h"
#define LOG_TAG "wuzhao"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
const char *className = "com/example/applicationnative/NativeFun";
JNIEXPORT jstring JNICALL c_other_str(JNIEnv *env, jobject jb) {
Test::Test data_decrpty;
std::string content = "other str";
LOGI("原始数据内容: %s", content.c_str());
data_decrpty.encrpty(content);
LOGI("加密后的数据内容: %s", content.c_str());
data_decrpty.decrpty(content);
LOGI("解密后的数据内容: %s", content.c_str());
return env->NewStringUTF(content.c_str());
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_applicationnative_NativeFun_callFromJavaAdd(JNIEnv *env, jobject thiz, jint x,
jint y) {
LOGI("x=%d y=%d",x,y);
call_java_method(env,thiz);
return env->NewStringUTF("hello callFromJavaAdd from native");
}
extern "C"
JNIEXPORT jstring JNICALL c_callFromJavaStr(JNIEnv *env, jobject thiz, jstring content, jint v) {
const char *c = env->GetStringUTFChars(content,NULL);
LOGI("receive content %s %d",c,v);
return env->NewStringUTF("hello callFromJavaStr from native");
}
JNINativeMethod cNativeMthods[] = {{"callFromJavaStr", "(Ljava/lang/String;I)Ljava/lang/String;", (void *) c_callFromJavaStr}};
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *unused) {
JNIEnv *env;
jint result = -1;
LOGI("JNI_OnLoad 函数开端 ============》 ");
// 取得env指针 env是指向指针的指针
if ((vm->GetEnv((void **) &env, JNI_VERSION_1_6)) != JNI_OK) {
LOGI("获取环境指针 失利了");
return result;
}
if (env == nullptr) {
LOGI("env=null ============》 ");
return result;
}
//获取到对应的类
jclass currentClass = env->FindClass(className);
if (currentClass == nullptr) {
LOGI("查找对应的类失利了============》 ");
return result;
}
// 注册本地防范
jint registerResult = env->RegisterNatives(currentClass, cNativeMthods,
getArrayLength(cNativeMthods));
//查看注册成果
if (registerResult < 0) {
LOGI("注册失利了============》 ");
return result;
}
LOGI("注册成功!!!============》 ");
result = JNI_VERSION_1_6;
return result;
}
JNIEXPORT void JNICALL call_java_method(JNIEnv *env,jobject obj) {
jclass clazz = env->FindClass(className);
if (clazz == nullptr){
return;
}
jmethodID method_id = env->GetMethodID(clazz,"callFromCpp","(Ljava/lang/String;)V");
jstring content = env->NewStringUTF("来自cpp的问好");
env->CallVoidMethod(obj,method_id,content);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(content);
}
// NativeFun.java
package com.example.applicationnative;
import android.util.Log;
public class NativeFun {
native String callFromJavaAdd(int x,int y);
native String callFromJavaStr(String content,int v);
public void callFromCpp(String content){
Log.i("wuzhao","java-callFromCpp "+content);
}
}
以上三个文件是完成JNI机制相关的代码了,这个时分咱们就能够运转项目来测试代码运转是否如预期相同。
当然还有一个静态代码块中加载so的操作,这个在代码生成时就有,理论上咱们能够把它加在咱们以为适宜的当地
static{
System.loadLibrary("applicationnative") // 是咱们在CMakeLists.txt中界说的名字,见下文
}
一些工程编译的问题
cpp的编译打包流程
不过在简略介绍cmake之前,或许还得先从cpp的编译流程说起: cpp的代码的编译打包进程大约能够分为三个进程:
- 预编译处理
- 在编译之前,编译器会对代码中以#最初的指令进行预处理。处理往后,这些指令就没有了。
- 代码编译
- 编译器是一个文件接着一个文件的进行编译的,不同的代码文件彼此不行见,编译后生成中间代码保存在.o或.obj文件中。
- 链接
- 把一切的中间代码文件链接成一个可执行文件。
CMake
一切的编译型的编程言语的大型工程都需求考虑一个问题,便是怎么编译只要部分代码改动的工程,咱们都知道编译器应该只编译修改正的代码文件,而不是整个工程。因而再c/cpp工程中协助开发者处理这个问题的便是makefile指令文件以及make指令。makefile文件中,能够界说工程项目中各个模块的依靠联系,然后协助编译器完成最小范围的重新编译。但是makefile在大型工程中直接编写也非常复杂,因而就呈现了cmake这个用简略指令生成makefile的东西。而cmake也需求一个装备文件来生成makefile文件,这个装备文件便是CMakeLists.txt.
简略来说,cmake便是生成makefile文件的东西,用来协助开发者办理大型工程。从某种程度上说,cmake有点像Android开发中的gradle脚本。 [图片上传失利…(image-986863-1693065339978)]
关于开发者而言,能够把cmake和MakeLists.txt视作工程编译构建的核心,因为它对工程构建进行了更高层次的抽象,使得装备变得愈加简略。
CMakeList.txt
CMakelists.txt是cmake执行需求读取的装备文件,里边首要界说了项目名,编译的源代码和构建类型,以及链接的库
# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)
# 项目名
project("applicationnative")
# SHARED 表明生成动态链接库so文件 随后跟着源代码文件,假设多个文件能够直接跟在后边用空格分隔
# (${CMAKE_PROJECT_NAME} 是终究生成的so的名字,这儿咱们直接运用项目名,也能够自行界说
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
native-lib.cpp )
# cpp代码中假设用到了其他的动态链接库里的功用,就需求在这儿链接进来
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log)
# demo中,咱们运用了Android供给的log库
CMakeLists.txt需求装备在gradle脚本中,首要是为了便利咱们运用gradle直接编译cpp文件,而不需求咱们自己手动调用cmake make指令了。
cmake还有很多指令,读者能够自行去官网学习