本文过一遍JNI基础。
gradle脚本如下:
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基础语法:
# 添加动态库
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})
so在加载时会调用JNI_OnLoad
方法,可以通过env->RegisterNatives
方法动态注册JNI实现
// 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,CMakeList中so命名为dynamic-lib
#include
// 静态注册
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;
}
查看源码,发现是在jni.h使用typedef
关键字重新定义类型。
/* 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 | jint |
long | jlong |
float | jfloat |
double | jdouble |
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;
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); // 回收
}
局部引用、全局引用、弱引用
env->DeleteLocalRef(temp)
释放局部引用;
env->NewGlobalRef(jclazz)
创建全局引用;
env->NewWeakGlobalRef(clazz)
创建弱引用;
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");
}
}
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);
}
pthread_create
创建线程
gVm->AttachCurrentThread(&env, nullptr)
创建当前线程的env对象
gVm->DetachCurrentThread();
使用完释放当前线程
#include
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;
}
两个线程依次打开1、2…100
#include
#include
#include
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);
}
再来一个命令,写完jni后,可以使用./gradlew externalNativeBuildDebug
命令编译native代码,在build/intermediates/cmake/debug
或 build/intermediates/transforms/mergeJniLibs/
下生成so。
也可通过find . -name "*.so"
找到so。
参考
NDK与JNI
Android深入理解JNI(二)类型转换、方法签名和JNIEnv
AndroidDevWithCpp
C++菜鸟教程
JNI原理