Android基础 JNI基础

前言

本文过一遍JNI基础。

1. CMakeList.txt

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})


2. 静态注册和动态注册

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;
}

3. jni数据类型转换

查看源码,发现是在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); // 回收
}

4. JNI引用类型

局部引用、全局引用、弱引用

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");
    }
}

5. JNI处理异常

  1. JNI捕获异常
  2. JNI抛出异常
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. 创建子线程

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;
}

7. 线程安全锁使用

两个线程依次打开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);
}

8. gradle命令

再来一个命令,写完jni后,可以使用./gradlew externalNativeBuildDebug命令编译native代码,在build/intermediates/cmake/debugbuild/intermediates/transforms/mergeJniLibs/下生成so。
也可通过find . -name "*.so"找到so。



参考
NDK与JNI
Android深入理解JNI(二)类型转换、方法签名和JNIEnv
AndroidDevWithCpp
C++菜鸟教程
JNI原理

你可能感兴趣的:(Android,Java等基础知识,jni,ndk)