Java Native Interface (JNI) 以下情况下使用jni:
-
标准 Java 类库不支持应用程序所需的平台相关特性。
-
您已经有一个用另一种语言编写的库,并希望通过 JNI 使其可供 Java 代码访问。
-
您想用较低级别的语言(例如汇编)实现一小部分时间要求严格的代码。 使用jni可以实现:
-
创建、检查和更新 Java 对象(包括数组和字符串)。
-
调用 Java 方法。
-
捕获并抛出异常。
-
加载类并获取类信息。
-
执行运行时类型检查。
JNI 接口指针只在当前线程中有效。因此,本机方法不得将接口指针从一个线程传递到另一个线程。
本机方法接收 JNI 接口指针作为参数。当 VM 从同一个 Java 线程多次调用本机方法时,它保证将相同的接口指针传递给本机方法。但是,本地方法可以从不同的 Java 线程调用,因此可能会接收不同的 JNI 接口指针。
加载本机方法
package pkg;
class Cls {
native double f(int i, String s);
static {
System.loadLibrary(“pkg_Cls”);
}
}
System.loadLibrary的参数是程序员任意选择的库名称。系统遵循标准但特定于平台的方法将库名称转换为本机库名称。例如,Solaris 系统将名称转换pkg_Cls为libpkg_Cls.so,而 Win32 系统将相同的pkg_Cls名称转换为pkg_Cls.dll.
程序员还可以调用 JNI 函数RegisterNatives()来注册与类关联的本地方法。该RegisterNatives()函数对静态链接函数特别有用。
解析本机方法名称
本机方法名称由以下组件连接:
-
前缀
Java_ -
完全限定的类名
-
下划线分隔符 ("_ ")
-
方法名称
-
对于重载的本机方法,两个下划线"__"后跟参数签名
使用下划线"_"字符代替完全限定类名中的斜杠(“/”)
由于名称或类型描述符从不以数字开头,因此我们可以使用_0, ..._9作为转义序列,如表所示:
| 转义序列 | 表示 |
|---|---|
_0XXXX | 一个 Unicode 字符XXXX。 请注意,小写用于 表示非 ASCII Unicode 字符,例如, _0abcd与 _0ABCD. |
_1 | _ |
_2 | ; |
_3 | [ |
本机方法参数
代码示例说明了使用 C 函数来实现本机方法f。本机方法f声明如下:
package pkg;
class Cls {
native double f(int i, String s);
...
}
具有长名称的 C 函数Java_pkg_Cls_f_ILjava_lang_String_2实现了本机方法f:
代码示例使用 C 实现本机方法
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
JNIEnv *env, /* 接口指针 */
jobject obj, /* "this" 指针 */
jin i, / * 参数 # 1 * /
jstring s) /* 参数 #2 */
{
/* 获取 Java 字符串的 C 副本 */
const char *str = (*env)->GetStringUTFChars(env, s, 0);
/* 处理字符串 */
...
/* 现在我们完成了 str */
(*env)->ReleaseStringUTFChars(env, s, str);
return ...
}
代码示例使用 C++ 实现本机方法:
extern "C" /* 指定 C 调用约定 */
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (
JNIEnv *env, /* 接口指针 */
jobject obj, /* "this" 指针 */
jin i, / * 参数 # 1 * /
jstring s) /* 参数 #2 */
{
const char *str = env->GetStringUTFChars(s, 0);
...
env->ReleaseStringUTFChars(s, str);
return ...
}
原始类型
| Java Type | Native Type | Description |
|---|---|---|
| boolean | jboolean | unsigned 8 bits |
| byte | jbyte | signed 8 bits |
| char | jchar | unsigned 16 bits |
| short | jshort | signed 16 bits |
| int | jint | signed 32 bits |
| long | jlong | signed 64 bits |
| float | jfloat | 32 bits |
| double | jdouble | 64 bits |
| void | void | N/A |
为方便起见,提供以下定义。
#define JNI_FALSE 0
#define JNI_TRUE 1
jsize整数类型用于描述基数索引和大小:
typedef jit jsize;
引用类型
在 C 中 ,所有其他 JNI 引用类型都被定义为与 jobject 相同。例如:
typedef jobject jclass;
在 C++ 中,JNI 引入了一组虚拟类来强制执行子类型关系。例如:
class _jobject {};
class _jclass : public _jobject {};
...
typedef _jobject *jobject;
typedef _jclass *jclass;
字段(Field)和方法 ID
方法和字段 ID 是常规的 C 指针类型:
struct _jfieldID; /* 不透明结构 */
typedef struct _jfieldID *jfieldID; /* 字段 ID */
struct _jmethodID; /* 不透明结构 */
typedef struct _jmethodID *jmethodID; /* 方法 ID */
值类型
用作参数数组中的元素类型。声明如下:
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
类型签名
JNI 使用 Java VM 的类型签名表示。表显示了这些类型签名。
表Java VM 类型签名
| 类型签名 | Java 类型 |
|---|---|
| Z | boolean |
| B | byte |
| C | char |
| S | short |
| I | int |
| J | long |
| F | float |
| D | double |
| L fully-qualified-class ; | fully-qualified-class |
[type | type[] |
| (arg-types) ret-type (参数)返回值 | method type |
例如,Java 方法:
long f (int n, String s, int[] arr);
具有以下类型签名:
(ILjava/lang/String;[I)J
接口功能表
| 标题 | |
|---|---|
| 获取版本 | jint GetVersion(JNIEnv *env); |
| 定义类 | jclass DefineClass(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize bufLen); |
| 查找类 | jclass FindClass(JNIEnv *env, const char *name); |
| 获取超类 | jclass GetSuperclass(JNIEnv *env, jclass clazz); |
| 是否可转换 | jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2); |
| Throw | jint Throw(JNIEnv *env, jthrowable obj); |
| ThrowNew | jint ThrowNew(JNIEnv *env, jclass clazz, const char *message); |
| 发生异常 | jthrowable ExceptionOccurred(JNIEnv *env); |
| 异常描述 | void ExceptionDescribe(JNIEnv *env); |
| 异常清除 | void ExceptionClear(JNIEnv *env); |
| 致命错误 | void FatalError(JNIEnv *env, const char *msg); |
| 异常检查 | jboolean ExceptionCheck(JNIEnv *env); |
| 创建全局引用 | jobject NewGlobalRef(JNIEnv *env, jobject obj); |
| 删除全局引用 | void DeleteGlobalRef(JNIEnv *env, jobject globalRef); |
| 删除本地引用 | void DeleteLocalRef(JNIEnv *env, jobject localRef); |
| 确保本地容量 | jint EnsureLocalCapacity(JNIEnv *env, jint capacity); |
| PushLocalFrame | jint PushLocalFrame(JNIEnv *env, jint capacity); |
| PopLocalFrame | jobject PopLocalFrame(JNIEnv *env, jobject result); |
| 创建本地引用 | jobject NewLocalRef(JNIEnv *env, jobject ref); |
| 创建弱全局引用 | jweak NewWeakGlobalRef(JNIEnv *env, jobject obj); |
| 删除弱全局引用 | void DeleteWeakGlobalRef(JNIEnv *env, jweak obj); |
| 分配对象 | jobject AllocObject(JNIEnv *env, jclass clazz); |
| 创建对象 | jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...); |
| 创建对象 | jobject NewObjectA(JNIEnv *env, jclass clazz,jmethodID methodID, const jvalue *args); |
| 创建对象 | jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); |
| 获取对象类 | jclass GetObjectClass(JNIEnv *env, jobject obj); |
| 获取对象引用类型 | jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj); |
| 是否是类的实例 | jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz); |
| 是否同一个对象 | jboolean IsSameObject(JNIEnv *env, jobject ref1, jobject ref2); |
| 获取字段ID | jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); |
| 获取字段ID的值 | NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID); |
| 设置字段ID的值 | void Set<type>Field (JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value); |
| 获取方法ID | jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig); |
| 调用java方法 | NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...); |
| 调用java方法 | NativeType Call<type>MethodA (JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); |
| 调用java方法 | NativeType Call<type>MethodV (JNIEnv *env, jobject obj, jmethodID methodID, va_list args); |
| 调用java方法的结果 | NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); |
| 调用java方法的结果 | NativeType CallNonvirtual<type>MethodA (JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args); |
| 调用java方法的结果 | NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); |
| 获取静态字段 ID | jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig); |
| 获取静态字段 ID的值 | NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID); |
| 设置静态字段的值 | void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value); |
| 获取静态方法 ID | jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig); |
| 获取静态方法 ID的值 | NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...); |
| 获取静态方法 ID的值 | NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args); |
| 获取静态方法 ID的值 | NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); |
| 创建字符串 | jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len); |
| 获取字符串长度 | jsize GetStringLength(JNIEnv *env, jstring string); |
| 获取字符串字符 | const jchar * GetStringChars(JNIEnv *env, jstring string,jboolean *isCopy); |
| 释放字符串字符 | void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars); |
| 创建字符串UTF | jstring NewStringUTF(JNIEnv *env, const char *bytes); |
| 获取字符串UTF长度 | jsize GetStringUTFLength(JNIEnv *env, jstring string); |
| 获取字符串UTF字符 | const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy); |
| 释放字符串UTF字符 | void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf); |
| 获取字符串区域 | void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf); |
| 获取字符串UTF区域 | void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf); |
| 获取字符串字符 | const jchar * GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy); |
| 释放字符串字符 | void ReleaseStringCritical(JNIEnv *env, jstring string, const jchar *carray); |
| 获取数组长度 | jsize GetArrayLength(JNIEnv *env, jarray array); |
| 创建对象数组 | jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement); |
| 获取对象数组元素 | jobject GetObjectArrayElement(JNIEnv *env,jobjectArray array, jsize index); |
| 设置对象数组元素 | void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value); |
| 创建原始java数组 | ArrayType New<PrimitiveType>Array (JNIEnv *env, jsize length); |
| 获得原始数组的函数 | NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy); |
| 释放原始数组的函数 | void Release<PrimitiveType>ArrayElements (JNIEnv *env, ArrayType array, NativeType* elems, jint mode); |
| 获取原始数组的区域 | void Get<PrimitiveType>ArrayRegion(JNIEnv *env,ArrayType array,jsize start, jsize len, NativeType *buf); |
| 设置原始数组的区域 | void Set<PrimitiveType>ArrayRegion (JNIEnv *env, ArrayType const NativeType array, jsize start, jsize len, *buf); |
| 获取原始数组的区域 | void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy); |
| 释放原始数组的区域 | void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode); |
| 注册本地方法 | jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods); |
| 注销本地方法 | jint UnregisterNatives(JNIEnv *env, jclass clazz); |
| 监视器输入 | jint MonitorEnter(JNIEnv *env, jobject obj); |
| 监视器退出 | jint MonitorExit(JNIEnv *env, jobject obj); |
| 创建ByteBuffer | jobject NewDirectByteBuffer(JNIEnv* env, void* 地址, jlong capacity); |
| 获取直接缓冲区地址 | void* GetDirectBufferAddress(JNIEnv* env, jobject buf); |
| 获取直接缓冲容量 | jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf); |
| 将对象转换为方法ID | jmethodID FromReflectedMethod(JNIEnv *env, jobject method); |
| 将字段转换为字段ID | jfieldID FromReflectedField(JNIEnv *env, jobject field); |
| 将方法ID转换为对象 | jobject ToReflectedMethod(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic); |
| 将字段ID转换为对象 | jobject ToReflectedField(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic); |
| 获取JavaVM | jint GetJavaVM(JNIEnv *env, JavaVM **vm); |
API调用
| 标题 | |
|---|---|
| 加载本机库 | jint JNI_OnLoad(JavaVM *vm, void *reserved); |
| 卸载本机库 | void JNI_OnUnload(JavaVM *vm, void *reserved); |
| 获得虚拟机的默认配置 | jint JNI_GetDefaultJavaVMInitArgs(void *vm_args); |
| 获得所有已创建的虚拟机 | jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize *nVMs); |
| 加载并初始化虚拟机 | jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args); |
| 销毁虚拟机 | jint DestroyJavaVM(JavaVM *vm); |
| 附加当前线程 | jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args); |
| 附加当前线程作为守护进程 | jint AttachCurrentThreadAsDaemon(JavaVM* vm, void** penv, void* args); |
| 分离当前线程 | jint DetachCurrentThread(JavaVM *vm); |
如何在jni中C/C++层打印log到logcat
-
- CmakeLists中配置
find_library(
log-lib
log)
target_link_libraries(
xxx
${log-lib})
-
- 在要使用LOG的cpp文件中加入:
#include <android/log.h>
- 在要使用LOG的cpp文件中加入:
-
- 直接使用
__android_log_print函数, 代码举例:
- 直接使用
char * name = "mronion";
__android_log_print(ANDROID_LOG_INFO, "lclclc", "my name is %s\n", name); //log i类型
-
- 改进
直接使用__android_log_print太麻烦了,我们可以定义一些log的方法,上述第二步改为:
#include <android/log.h>
#define TAG "projectname" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型
使用举例:
char * name = "mronion";
LOGD("my name is %s\n", name );