一,CMakeLists
英文文档:https://cmake.org/documentation/
中文文档:https://www.zybuluo.com/khan-lau/note/254724
1,路径配置,在buildGradle的android层目中进行配置例如:
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
path配置cmake的引用路径,version配置cmake的版本
2,添加库,在cmakeList中调用add_library函数
里面传入三个参数
第一个是要引入的库别名,第二个是库的类型,是静态库还是动态库。在Android中是不能引用静态库的也就是.dll后缀的库,所以第二个参数只要是引入第三方的.so库都是写“SHARED”,第三个是通过什么样方式引入进来,第三方的一般都是通过包含进来,所以第三个参数基本也是固定的都是写“IMPORTED”。
add_library后,就要设置.so的详细路径了,通过set_target_properties()函数来设置;该函数也是要传入三参数来指定.so库的路径。第一个参数和add_library的第一个参数一样,不过这里的库别名要和add_library的库别名要一致,要不然在编译时会报找不到库的错误。第二个参数是固定的,都是写“ PROPERTIES IMPORTED_LOCATION”主要用来指定库的引入方式。都是通过本地引入。第三个就是库的具体路径,这个不能写错,如果写错了,编译时也同样会找不到库的。只要是引入第三方的库使用add_library就要使用set_target_propeties这个组合,所以它们是成对出现的。3,编译我们自己在项目中写的c/c++文件
调用方式和上面的add_library方法类似,前两个参数和上面传入的是一样的,最后一个参数写对应我们的c++的文件路径,可以写多个路径,如下:
add_library( # Sets the name of the library.
main-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
main-lib.cpp
runoobtest.cpp)
这里就不用set_target_propeties()
4,下面一个函数是find_library
这个方法是用来寻找系统库的,如果有用用到系统库就要调用这个函数
第一个参数是库的别名,第二个参数表示该库在系统ndk中的名字如下:
# 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)
5,下一个方法是target_link_libraries,依赖的库
如果是系统的库要用这个格式${库的名字}。
如下:
# 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.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib})
target_link_libraries( # Specifies the target library.
main-lib
# Links the target library to the log library
# included in the NDK.
${log-lib}
)二,java调用层C代码流程
第一种方式静态注册:
1,首先加载共享库,加载库一般是在在静态代码块中进行:
static {
System.loadLibrary("native-lib");
} 2,在Java中声明native方法
public native String stringFromJNI();
3,实现原生方法
#include <jni.h>
#include <string>
#include <android/log.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_test_jni_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
__android_log_print(ANDROID_LOG_ERROR,"TEST","HELLO ....... native lib");
return env->NewStringUTF(hello.c_str());
}
为了找到c代码中对应的方法,需要要Java关键字开头,在方法前面加上包名,并且以下划线隔开。上面的stringFromJjni声明在了com.test.jni这个包名的MainActivity里面。
这样在java代码里就可以调用stringFromJni这个方法了
第二种方式:动态注册
1,和静态注册一样,先加载共享库
static {
System.loadLibrary("dynamic-lib");
}
2,在java中声明native方法
public native int sum(int x, int y);
public native String getNativeString();
3,在c代码中中添加回调函数JNI_OnLoad方法,loadLibrary会执行该方法。
然后在JNI_OnLoad方法中调用方法RegisterNatives就完成注册了如下:
JNIEXPORT int JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_FALSE;
}
registerNativeMethonds(env,JAVA_CLASS,gMethods,2);
LOGE("jni onload call");
return JNI_VERSION_1_6;
}
int registerNativeMethonds(JNIEnv *env, const char *name,
JNINativeMethod *methods,jint nMethods) {
jclass jcls;
jcls = env->FindClass(name);
if (jcls == nullptr) {
return JNI_FALSE;
}
if (env->RegisterNatives(jcls, methods, nMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
static JNINativeMethod gMethods[] = {
{ "getNativeString","()Ljava/lang/String;",(void*)getMessage},
{ "sum","(II)I",(void*)plus}
};
RegisterNatives接收三个参数,
一地个参数,clazz就是native函数所在的类,可通过FindClass(将.换成/)获取;第二个单数methods是一个数组,其中包含注册信息,第三个参数nMethods是方法数量。上面的例子中我调用了本地的plus和sum两个方法所以传了一传了个2
FindClass里传入的就是类名,例如本例中我传入的宏定义JAVA_CLASS
#define JAVA_CLASS "com/example/nativetest/load/JNIDynamicLoad"
就是将.换成了/。
4,NINativeMethod是个结构体,里面有三个成员变量
第一个是java层的方法名,第二个是方法签名,第三个是C函数指针。这样三个参数就便成了一组注册信息。
5,然后在java层中直接调用就可以调用c层的代码了:
JNIDynamicLoad jniDynamicLoad = new JNIDynamicLoad();
jniDynamicLoad.getNativeString()
jniDynamicLoad.sum(5,6)
对应C层代码:
jstring getMessage(JNIEnv *env, jobject obj) {
return env->NewStringUTF("this is msg");
}
jint plus(JNIEnv *env, jobject jobject1, int x, int y) {
return x + y;
}
6,方法签名如下:


备注:long对应的类型的签名应该是J,有的博客博客写的是L可能笔误
括号里面表示函数方法参数,尾部表示返回值
例如:
方法 1):
public string addTail(String tail, int index)
其对应的签名如下:
(Ljava/util/String;I)Ljava/util/String;
方法 2):
public int addValue(int index, String value,int[] arr)
其对应的签名如下:
(ILjava/util/String;[I)I
无返回值类型: void set (byte[ ] bytes)
签名:([B)V
注意:引用类型后面要加分号
三,C调用Java代码
主要是利用反射,这样就能调用Java代码了。
1,先写一个java的方法
public void hello(){
Log.e("CallJava","hello I am from java");
}2,调用四部曲:
//调用public void hello()方法
void callJavaVoid(JNIEnv *env, jobject instance) {
jclass clazz = env->FindClass("com/test/jni/CallJava");
jmethodID method = env->GetMethodID(clazz, "hello", "()V");
jobject object = env->AllocObject(clazz);
env->CallVoidMethod(object, method);
}
a,获取Java中的class
b,获取对应的函数
c,实例化该class对应的实例
D,调用方法
获取Java中的class
第一步:使用FindClass方法,第二个参数,就是要调用的函数的类的完全限定名,但是需要把点换成/
获取对应的函数
第二步:使用GetMethodID方法,第二个参数就是刚得到的类的class,第三个就是方法名,第四个就是该函数的签名
实例化该class对应的实例
第三步:使用AllocObject方法,使用clazz创建该class的实例。
调用方法
第四步:使用CallVoidMethod方法,可以看到这个就是调用返回为void的方法,第二个参数就是第三步中创建的实例,第三个参数就是上边创建的要调用的方法。
有了这个四部就能在C中调用Java中的代码了。
而对于有参,有返回的方法,在这四部曲的基础上,只需要修改第二步获取方法的名字和签名,其中签名以及第四步的Call<Type>Method方法,Type可以是int,string,boolean,float等等。
提示:对于基本类型又个技巧,括号内依次是参数的类型的缩写,括号右边是返回类型的缩写,但是对于静态方法的调用就应该使用GetStaticMethodID和CallStaticVoidMethod了,而对于静态方法就不需要实例化对象,相对来说还少一步。 2,调用带参数和返回值的方法
Demon如下:
java中的方法:
//有参数的方法
public String hello(String name) {
Log.e("CallJava", "hello I am from java name is " + name);
return name;
}
C代码中调用方式:
//调用public 有参数 hello()方法
void callJavaMeathod(JNIEnv *env) {
jclass clazz = env->FindClass("com/test/jni/CallJava");
jmethodID method = env->GetMethodID(clazz, "hello", "(Ljava/lang/String;)Ljava/lang/String;");
jobject object = env->AllocObject(clazz);
jstring jmsg = env->NewStringUTF("lilei");
jstring nameStr = static_cast<jstring>(env->CallObjectMethod(object, method, jmsg));
const char* name = env->GetStringUTFChars(nameStr,0); //转化为c的char*类型,这样才能打印出来
__android_log_print(ANDROID_LOG_ERROR, "TEST", "call java return:%s",name);
env -> ReleaseStringUTFChars(nameStr,name); //释放
}3,下面是访问String的一些方法:
◆GetStringUTFChars将jstring转换成为UTF-8格式的char*
◆GetStringChars将jstring转换成为Unicode格式的char*
◆ReleaseStringUTFChars释放指向UTF-8格式的char*的指针
◆ReleaseStringChars释放指向Unicode格式的char*的指针
◆NewStringUTF创建一个UTF-8格式的String对象
◆NewString创建一个Unicode格式的String对象
◆GetStringUTFLengt获取UTF-8格式的char*的长度
◆GetStringLength获取Unicode格式的char*的长度
4,C调用Java中的其他类型的方法举例:
CallIntMethod举例:
java方法:
public int helloInt(){
Log.e("CallJava", "helloInt ");
return 3;
}
调用方式:
void callJavaIntMeathod(JNIEnv *env) {
jclass clazz = env->FindClass("com/test/jni/CallJava");
jmethodID method = env->GetMethodID(clazz, "helloInt", "()I");
jobject object = env->AllocObject(clazz);
jint jint1 = env->CallIntMethod(object,method);
__android_log_print(ANDROID_LOG_ERROR, "TEST", "callJavaIntMeathod return:%d",jint1);
}
CallStaticVoidMethod举例:
java侧方法:
public static void hellStatic(){
Log.e("CallJava", "hellStatic ");
}
调用方式:
void callJavaStaticVoidMeathod(JNIEnv *env) {
jclass clazz = env->FindClass("com/test/jni/CallJava");
jmethodID method = env->GetStaticMethodID(clazz, "hellStatic", "()V");
__android_log_print(ANDROID_LOG_ERROR, "TEST", "callJavaStaticVoidMeathod");
env->CallStaticVoidMethod(clazz,method);
}
其他方法类似,另外还有两种类型的调用方法如下:
CallVoidMethodA举例:
java方法:
public void helloVoidA(int a, boolean b, String c) {
Log.e("CallJava", "helloVoidA: " + a + " " + b + " " + c);
}
调用方式:
void callJavaVoidMethodA(JNIEnv *env) {
jvalue myJvalues[] ={};
myJvalues[0].i = 777;
myJvalues[1].z = true;
myJvalues[2].l = env -> NewStringUTF("FEfaeteatraret");
jclass clazz = env->FindClass("com/test/jni/CallJava");
jmethodID method = env->GetMethodID(clazz, "helloVoidA", "(IZLjava/lang/String;)V");
jobject object = env->AllocObject(clazz);
env->CallVoidMethodA(object, method, myJvalues);
}
其中第三个参数需要传入一个Jvalue的联合体数组,里面传入要写入得参数
CallVoidMethodV举例:
Java方法:
public void helloVoidV(int a, boolean b, String c) {
Log.e("CallJava", "helloVoidV: " + a + " " + b + " " + c);
}
调用方式:
void callJavaVoidMethodV(JNIEnv *env,...) {
va_list ap;
va_start(ap,env);
jclass clazz = env->FindClass("com/test/jni/CallJava");
jmethodID method = env->GetMethodID(clazz, "helloVoidA", "(IZLjava/lang/String;)V");
jobject object = env->AllocObject(clazz);
env->CallVoidMethodV(object,method,ap);
va_end(ap);
}
由于va_list的可变参数类型,所以我们 应该是写在一个可变参数的方法里面,调用该方法的时候可以就写成这样:
callJavaVoidMethodV(env,676,false, env -> NewStringUTF("AAAAAAA"));
四,C调用java的成员变量
获取普通成员变量的属性值:
在java中声明成员变量,
public String instanceField = "I am instanceField";
调用方式如下:
//调用java中的成员变量
void callJavaInstanceField(JNIEnv *env){
jclass clazz = env->FindClass("com/test/jni/CallJava");
jfieldID jfieldId = env->GetFieldID(clazz,"instanceField","Ljava/lang/String;");
jobject object = env->NewObject(clazz, env->GetMethodID(clazz, "<init>", "()V"));
jobject jobject1 = env->GetObjectField(object, jfieldId);
const char *strChar = env->GetStringUTFChars(static_cast<jstring>(jobject1), 0); //转化为c的char*类型,这样才能打印出来
__android_log_print(ANDROID_LOG_ERROR, "TEST", "callJavaInstanceField:%s",strChar);
}
1,首先调用GetFieldID获取jfield 第二个参数是成员变量名,第三个参数是签名
2,然后创建对象NewObject,第二个参数是构造方法的方法ID,获取构造方法ID时方法名统一是<init>
3,然后调用GetObjedtField就可以获取成员变量的属性值了
附文档下载地址:https://download.csdn.net/download/wxj280306451/12416024