Preface
This article goes through JNI basics.
1. CMakeList.txt
The gradle script is as follows:
android{
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
}
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version "3.6.0" // 3.6.0版本日志输出,3.10.2无日志输出
}
}
}
CMakeList basic syntax:
# 添加动态库
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
# 从某路径下找到动态库并命名
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)
# 关联动态库,native-lib关联log-lib
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${
log-lib})
2. Static registration and dynamic registration
So will call the JNI_OnLoad
method when loading , which can be achieved by env->RegisterNatives
dynamically registering JNI through the method
// Java代码
public class DynamicLoad {
static {
System.loadLibrary("dynamic-lib");
}
public native int sum(int x, int y); // JNI直接生成该方法的native方法即静态注册
public native String getNativeString(); // 在JNI_OnLoad时动态注册
}
dynamic.cpp, so named as dynamic-lib in CMakeList
#include <jni.h>
// 静态注册
extern "C"
JNIEXPORT jint JNICALL
Java_com_baiiu_jnitest_dynamicLoad_DynamicLoad_sum(JNIEnv *env, jobject thiz, jint x, jint y) {
return x + y;
}
jstring nativeGetString(JNIEnv *env, jobject thiz) {
return env->NewStringUTF("message from dynamic loader");
}
// so加载时(dlpen)调用该方法,进行动态注册
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;
}
const char *className = "com/baiiu/jnitest/dynamicLoad/DynamicLoad";
JNINativeMethod methods[] = {
{
"getNativeString", "()Ljava/lang/String;", (void *) nativeGetString}
};
jclass clazz = env->FindClass(className);
env->RegisterNatives(clazz, methods, 2);
return JNI_VERSION_1_6;
}
3. jni data type conversion
Looking at the source code, it was found that typedef
keywords were used to redefine the type in jni.h.
The correspondence between the basic types is as follows:
/* Primitive types that match up with Java equivalents. */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
Java | Native |
---|---|
boolean | jboolean |
byte | jbyte |
char | jchar |
short | jshort |
int | jit |
long | jlong |
float | jfloat |
double | jdouble |
The reference type correspondence is as follows:
Java Reference | Native |
---|---|
All objects | jobject |
java.lang.Class | jobject |
java.lang.String | jstring |
java.lang.Throwable | jthrowable |
Object[] | jobjectArray |
boolean[] | jbooleanArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double[] | jdoubleArray |
class _jobject {
};
class _jclass : public _jobject {
};
class _jstring : public _jobject {
};
class _jarray : public _jobject {
};
class _jobjectArray : public _jarray {
};
class _jbooleanArray : public _jarray {
};
class _jbyteArray : public _jarray {
};
class _jcharArray : public _jarray {
};
class _jshortArray : public _jarray {
};
class _jintArray : public _jarray {
};
class _jlongArray : public _jarray {
};
class _jfloatArray : public _jarray {
};
class _jdoubleArray : public _jarray {
};
class _jthrowable : public _jobject {
};
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;
Pay attention to recycling when using reference types
extern "C"
JNIEXPORT void JNICALL
Java_com_baiiu_jnitest_string_StringTestFragment_callNativeString(JNIEnv *env, jobject thiz, jstring jStr, jintArray jarr) {
// jstring需要转化为 nativeString 才能使用
const char *str = env->GetStringUTFChars(jStr, JNI_FALSE);
env->ReleaseStringUTFChars(jStr, str); // 回收
int *arr = env->GetIntArrayElements(jarr, JNI_FALSE);
env->ReleaseIntArrayElements(jarr, arr, 0); // 回收
}
4. JNI Reference Type
Local reference, global reference, weak reference
env->DeleteLocalRef(temp)
Release local references;
env->NewGlobalRef(jclazz)
create global references;
env->NewWeakGlobalRef(clazz)
create weak references;
extern "C"
JNIEXPORT void JNICALL
Java_com_baiiu_jnitest_reference_ReferenceFragment_localReference(JNIEnv *env, jobject thiz) {
// 局部引用,方法执行完后结束
jclass jclazz = env->FindClass("java/lang/String");
for (int i = 0; i < 1000; ++i) {
jclass jclazzTemp = env->FindClass("java/lang/String");
env->DeleteLocalRef(jclazzTemp); // 可以手动释放
}
// 全局引用
static jclass stringClass = nullptr;
if (stringClass == nullptr) {
jclass jclazz = env->FindClass("java/lang/String");
stringClass = static_cast<jclass>(env->NewGlobalRef(jclazz)); // 创建全局引用
env->DeleteLocalRef(jclazz); // 释放局部引用
} else {
LOGD("cached");
}
}
5. JNI handling exception
extern "C"
JNIEXPORT void JNICALL
Java_com_baiiu_jnitest_exception_ExceptionFragment_nativeInvokeJavaException(JNIEnv *env, jobject thiz) {
// JNI调用Java方法
jclass jclazz = env->GetObjectClass(thiz);
jmethodID mid = env->GetMethodID(jclazz, "error", "()I");
jint result = env->CallIntMethod(thiz, mid);
// 捕获异常,有异常发生时catch住
jthrowable thr = env->ExceptionOccurred();
if (thr) {
env->ExceptionDescribe();
env->ExceptionClear();
}
// 抛出异常,处理一波后抛出
jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(clazz, "native throw exception");
LOGD("result: %d", result);
}
6. Create a child thread
pthread_create
Create thread
gVm->AttachCurrentThread(&env, nullptr)
Create env object of
gVm->DetachCurrentThread();
current thread release current thread after use
#include <pthread.h>
JavaVM *gVm; // 保存vm
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;
}
gVm = vm;
return JNI_VERSION_1_6;
}
jobject threadObject;
jclass threadClazz;
jmethodID threadMethod;
extern "C"
JNIEXPORT void JNICALL
Java_com_baiiu_jnitest_thread_ThreadFragment_nativeThreadCallBack(JNIEnv *env, jobject thiz, jobject call_back) {
threadObject = env->NewGlobalRef(call_back);
threadClazz = env->GetObjectClass(call_back);
threadMethod = env->GetMethodID(threadClazz, "onCallBack", "()V");
// 创建线程
pthread_t handle;
pthread_create(&handle, nullptr, threadCallBack, nullptr);
}
/*
AttachCurrentThread、DetachCurrentThread需要成对调用
*/
void *threadCallBack(void *) {
JNIEnv *env;
// 创建当前线程的env对象
if (gVm->AttachCurrentThread(&env, nullptr) == JNI_OK) {
env->CallVoidMethod(threadObject, threadMethod);
gVm->DetachCurrentThread();
}
return 0;
}
7. Use of thread-safe locks
Two threads open 1, 2...100 sequentially
#include <jni.h>
#include <log.h>
#include <pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
int count;
bool isOdd(int num) {
return (num & 1) == 1;
}
void *print1(void *) {
while (true) {
pthread_mutex_lock(&mutex);
while (!isOdd(count)) {
pthread_cond_wait(&cond, &mutex);
}
LOGD("print1 count is: %d", count++);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
if (count >= 100) {
break;
}
}
LOGD("print1 end");
return 0;
}
void *print2(void *) {
while (true) {
pthread_mutex_lock(&mutex);
while (isOdd(count)) {
pthread_cond_wait(&cond, &mutex);
}
LOGD("print2 count is: %d", count++);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
if (count >= 100) {
break;
}
}
LOGD("print2 end");
return 0;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_baiiu_jnitest_thread_ThreadMutexFragment_print12to100(JNIEnv *env, jobject thiz) {
LOGD("count init: %d", count);
pthread_mutex_init(&mutex, nullptr);
pthread_cond_init(&cond, nullptr);
pthread_t handle1;
pthread_t handle2;
pthread_create(&handle1, nullptr, print1, nullptr);
pthread_create(&handle2, nullptr, print2, nullptr);
}
8. gradle command
Again a command, after writing JNI, can use the ./gradlew externalNativeBuildDebug
Command compiled native code, in build/intermediates/cmake/debug
or build/intermediates/transforms/mergeJniLibs/
generated so below.
You can also find . -name "*.so"
find so.
Refer to
NDK and JNI
Android to understand JNI (two) type conversion, method signature and JNIEnv
AndroidDevWithCpp
C++ rookie tutorial
JNI principle