在kotlin中使用externl关键字声明jni方法:
class NativeDemo { external fun stringFromJNI(): String; }
有两种方式可以实现对应的native代码:
(1)静态注册
在cpp目录下,新建native\_lib.cpp,添加对应的native实现:
#include <jni.h> #include
// JNIEXPORT JNICALL、参数里的前两个参数JNIEnv* env,jobject obj等是固定格式;固定参数中的jobject obj表示this extern “C” JNIEXPORT jstring JNICALL Java_com_bc_sample_NativeDemo_stringFromJNI( JNIEnv* env, jobject obj) { std::string hello = “Hello from C++”; return env->NewStringUTF(hello.c_str());
// static的native方法,参数默认是jclass extern “C” JNIEXPORT void JNICALL Java_com_bc_sample_NativeDemo_stringFromJNI( JNIEnv* env, jclass clazz) {
}
(2)动态注册
在cpp目录下,新建native\_lib.cpp,在JNI\_OnLoad时调用env->RegisterNatives进行注册(JNI\_OnLoad是在动态库被加载时由系统进行调用):
// 需要注册jni方法所在的类 static const char *jniClassName = “com/bc/sample/NativeDemo”;
// 需要注册的jni方法,分别表示java方法名、方法签名、native函数指针 // 方法签名可以用javap命令查看: // javap -s /Users/bc/Demo/app/build/intermediates/javac/debug/classes/com/bc/sample/NativeDemo.class static JNINativeMethod methods[] = {{“stringFromJNI”, “()Ljava/lang/String”, (jstring *) native_stringFromJNI}};
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env = NULL; jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
if (!registerNatives(env))
return JNI_ERR;
result = JNI_VERSION_1_6;
return result;
}
static int registerNatives(JNIEnv *env) { jclass clazz = env->FindClass(jniClassName); if (clazz == NULL) return JNI_FALSE;
jint methodSize = sizeof(methods) / sizeof(methods[0]);
if (env->RegisterNatives(clazz, methods, methodSize) < 0)
return JNI_FALSE;
return JNI_TRUE;
}
// native实现 jstring native_stringFromJNI(JNIEnv *env, jobject obj) { return env->NewStringUTF(“hello”); }
4.1 JavaVM 和 JNIEnv
-------------------
JNI 定义了两个关键数据结构“JavaVM”和“JNIEnv”,两者本质上都是指向函数表的二级指针。
Android中每个进程只允许有一个JavaVM。JNIEnv作用域为单个线程,可通过JavaVM的getEnv来获得当前线程的JNIEnv,JNIEnv可通过GetJavaVM来获得JavaVM。
// 保存JavaVM,方便在子线程中获取 static JavaVM *_my_jvm;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->GetJavaVM(&_my_jvm) JNIEnv *env = NULL; jint result = -1; if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; return result; }
4.2 线程与JavaVM
-------------
在java代码中,可以通过Thread.start()启动一个线程;
对于在native代码中通过pthread\_create() 或 std::thread 启动的线程,是没有JNIEnv的,也就无法调用JNI,可以使用 AttachCurrentThread() 或 AttachCurrentThreadAsDaemon() 函数将JavaVM附加到线程,附加后的线程可以调用JNI代码:
// 保存JavaVM,方便在子线程中获取 static JavaVM *_my_jvm; // 保存java层obj,方便在子线程获取后回调结果 static java_obj;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->GetJavaVM(&_my_jvm) JNIEnv *env = NULL; jint result = -1; if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; return result; }
jstring native_stringFromJNI(JNIEnv *env, jobject obj) { java_obj = env->NewGlobalRef(obj); }
void native_callback() { JNIEnv *env = NULL; _my_jvm->AttachCurrentThread(&env, nullptr); jmethodId method_call_back = env->GetMethodId(user_class, “callback”, “()V”); env->CallVoidMethod(java_obj, method_call_back); // 使用完后需要detach _my_jvm->DetachCurrentThread(); }
4.3 JNI数据类型
-----------
### 4.3.1 基础数据类型
/* jni.h源码 */
/* Primitive types that match up with Java equivalents. / / jni基础数据类型与java一一对应,二者可直接转换 / 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 */
/* “cardinal indices and sizes” */ typedef jint jsize;
#ifdef __cplusplus /*
- Reference types, in C++
*/ 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;
#else /* not __cplusplus */
/*
- Reference types, in C.
/ typedef void jobject; typedef jobject jclass; typedef jobject jstring; typedef jobject jarray; typedef jarray jobjectArray; typedef jarray jbooleanArray; typedef jarray jbyteArray; typedef jarray jcharArray; typedef jarray jshortArray; typedef jarray jintArray; typedef jarray jlongArray; typedef jarray jfloatArray; typedef jarray jdoubleArray; typedef jobject jthrowable; typedef jobject jweak;
#endif /* not __cplusplus */
### 4.3.2 string类型
extern “C” JNIEXPORT jstring JNICALL Java_com_bc_sample_NativeDemo_stringFromJNI( JNIEnv* env, jobject obj, jstring str) { jstring loacl_str = env->NewStringUTF(“local_str”); const char * c = env->GetStringUTFChars(str, false); // 删除避免内存泄漏 env->ReleaseStringUTFChars(str, c); return env->NewStringUTF(hello.c_str()); }
### 4.3.3 引用类型
例如:java中定义了一个User类如下:
package com.bc.sample;
class User { public String userName; public static String TAG;
public void setName() {
}
public static void show() {
}
}
在jni中调用User相关方法的方式如下:
extern “C” JNIEXPORT jobject JNICALL Java_com_bc_sample_NativeDemo_objectFromJNI( JNIEnv* env, jobject obj, jobject user) { jclass user_class = env->GetObjectClass(user); // 1.1 变量 jfieldID user_name_id = env->GetFiledId(user_class, “userName”, “Ljava/lang/String”); jstring name = env->NewStringUTF(“jack”); env->SetObjectFiled(user, user_name_id, name); // 1.2 静态变量 env->GetStaticFieldID(user_class, “TAG”, “Ljava/lang/String”);
// 2.1 方法
jmethodId method_set_name = env->GetMethodId(user_class, "setName", "()V");
env->CallVoidMethod(user, method_set_name);
// 2.2 静态方法
jmethodId method_show = env->GetStaticMethodId(user_class, "show", "()V");
env->CallStaticObjectMethod(user_class, method_show);
// 3 构造函数,用<init>表示
jclass usr_class = env->FindClass("com/bc/sample/User");
jmethodId method_init = env->GetMethodId(user_class, "<init>", "()V");
jobject usr = env->NewObject(usr_class, method_init);
jobjectArray usr_array = env->NewObjectArray(5, usr_class, nullptr);
// 局部引用超过512会报错local reference table overflow (max=512);所以用完后要释放
env->DeleteLocalRef(user_class);
}
### 4.3.4 数组类型
不管是基础类型的数组jintArray、jbooleanArray还是引用类型的数组jobjectArray,都是继承了jarray,是一个引用类型。
extern “C” JNIEXPORT jobject JNICALL Java_com_bc_sample_NativeDemo_objectFromJNI( JNIEnv* env, jobject obj, jintArray int_array, jstringArray str_array, jobjectArray user_array) { int len = env->GetArrayLength(user_array); // 1 基本数据类型数组 jint n = env->GetIntArrayElement(int_array, 0); // 2 string数据类型数组 jstring str = static_cast(env->GetObjectArrayElement(str_array, 0)); const char * c = env->GetStringUTFChars(str, false); env->ReleaseStringUTFChars(str, c);
// 3 引用类型数组;获得jobject后可按4.3.3中获得user数据
jobject user = env->GetObjectArrayElement(user_array, 0);
env->DeleteLocalRef(user);
}
4.4 java签名
----------
可以使用javap命令生成类的方法及参数签名:
javap -s java.lang.String
java类型及签名对应关系如下:
基本类型
V void 一般用于表示方法的返回值 Z boolean B byte C char S short I int J long F float D double
引用类型前要加L
String Ljava/lang/String Class Ljava/lang/Class
数组前要加[
int[] [I Object[] [Ljava/lang/Object
方法签名:括号内表示参数,括号后表示返回类型(引用类型后要用;分隔)
String fun() () Ljava/lang/String; int fun( int i, String str) (ILjava/lang/String;)I
4.5 JNI全局引用 局部引用 弱引用
--------------------
static java_obj;
jstring native_stringFromJNI(JNIEnv *env, jobject obj) { // 局部引用 jclass str_clazz = env->FindClass(“Ljava/lang/String”); env->DeleteLocalRef(str_clazz); // 全局引用,手动调用DeleteGlobalRef后才释放 java_obj = env->NewGlobalRef(obj); env->DeleteGlobalRef(obj); // 弱引用,使用前需要判断是否为null java_obj = env->NewWeakGlobalRef(obj); jboolean is_null = env->IsSameObject(java_obj, nullptr); }
4.6 JNI异常捕获
-----------
假设java中的user类中定义如下
class User { public void setName() throws RuntimeException { }
public native void stringFromJNI();
}
在native层调用java代码发生异常时,可按如下方式捕获:
jstring native_stringFromJNI(JNIEnv *env, jobject obj) { jclass user_class = env->GetObjectClass(user); jmethodId method_set_name = env->GetMethodId(user_class, “setName”, “()V”); env->CallVoidMethod(user, method_set_name); // 1.如果调用java方法setName时发生异常;可使用如下方法判断是否发生crash并捕获;否则会直接crash jthrowable throwable_occurred = env->ExceptionOccurred(); if (throwable_occurred) { env->ExceptionDescribe(); env->ExceptionClear(); }
// 2.或者当异常发生时,native层可以向java层抛出一个异常,在java层完成try-catch
jclass throwable_clazz = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(throwable_clazz, "exception occurred");
}
4.7 JNI线程
---------
native开发常用的线程库:
1. Posix API:<pthread.h>;
2. c++11支持的<thread.h>(本文不介绍);
#include<pthread.h> // 线程互斥锁 pthread_mutex_t mutex; // 线程条件变量 pthread_cond_t cont;
// 需要在子线程中执行的方法 void *sayHello(void * arg) { pthread_mutex_lock(&mutex); jstring local_arg = static_cast (arg); LOG(local_arg); pthread_mutex_unlock(&mutex); return nullptr; }
jstring native_stringFromJNI(JNIEnv *env, jobject obj) { // 1 创建线程 pthread_t handle; jstring loacl_str = env->NewStringUTF(“local_str”); int result = pthread_create(&handle, nullptr, sayHello, loacl_str);
// 2 线程同步常用方法
// pthread_mutex_lock() pthread_mutex_unlock()
pthread_mutex_init(&mutex, nullptr);
pthread_mutex_destroy(&mutex);
// pthread_cond_wait() pthread_cond_signal()
pthread_cond_init(&cond, nullptr);
pthread_cond_destroy(&cond);
总结
最后小编想说:不论以后选择什么方向发展,目前重要的是把Android方面的技术学好,毕竟其实对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
这里附上我整理的几十套腾讯、字节跳动,京东,小米,头条、阿里、美团等公司19年的Android面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](
)**
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
技术进阶之路很漫长,一起共勉吧~
本文已被腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录,自学资源及系列文章持续更新中… 阿里、美团等公司19年的Android面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
[外链图片转存中…(img-1Lxz33Z2-1631187511236)]
**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](
)**
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
技术进阶之路很漫长,一起共勉吧~
本文已被腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录,自学资源及系列文章持续更新中…
|