文章目录
Android的官方文档详细的描述了JNI的使用,建议先阅读官方文档。
签名(signature)
我们在调用这类Get<Static>MethodID
、Get<Static><TYPE>Field
、Call<Static><TYPE>Method
接口的时候都需要填入signature,signature用于表示描述Java类型对应C/C++类型。基本类型使用单字符表示,结构体使用L + 包名 + 结构名 + ;
表示,因为JNI需要知道结构体的完整包名才能找到对应的类型。
类型 | signature |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
object | L |
String | Ljava/lang/String; |
Context | Landroid/content/Context; |
引用
JNI的引用分为三种Local References、Global References和Weak Global References,他们的关系可用简单理解为C++的堆、栈,引用。
- Local References
大部分JNI方法返回的引用类型都是local引用(例如FindClass),local引用仅仅在此native方法中有效,当native方法返回时此local引用会被自动释放掉,也可以调用DeleteLocalRef
手动释放。local引用的个数是有限制的,所以建议当不使用的时候就手动释放一下。 - Global References
Global引用在整个生命周期中都是有效的(直到手动释放它),同样它的引用个数也是有限的,所以在不需要的时候需要手动释放一下。使用NewGlobalRef
创建一个Global引用,使用DeleteGlobalRef
删除一个Global引用。 - Weak Global References
Weak Global如其名,所以在使用的时候需要判断一下这个应用对象是否还可用IsSameObject(o, nullptr)
,同样它的引用个数也是有限的,所以在不需要的时候需要手动释放一下。使用NewWeakGlobalRef
创建一个Weak Global引用,使用DeleteWeakGlobalRef
删除一个Weak Global引用。
JNIEnv
JNIEnv是线程相关的数据结构,每一个Java线程存储一个对应的JNIEnv对象(pthread_setspecific
),JNIEnv是Java和C/C++交互的桥梁。Native方法中的JNIEnv参数是从调用线程中获取的,所以不同线程调用这个JNIEnv是不同的。
- C/C++线程如何绑定JNIEnv
C/C++创建的线程是不含JNIEnv,如果想要让这个线程跟Java层交互,我们需要让C/C++线程绑定一个JNIEnv。以下是WebRTC内部的一个实现,其实Android源码里面的实现也是一样的。我简单说明一下,当一个线程绑定了一个JNIEnv,我们可以通过GetEnv
获取对应的JNIEnv,当一个线程没有绑定JNIEnv,我们可以通过AttachCurrentThread
为当前线程绑定一个JNIEnv。那么当线程退出的时候我们如何释放这个JNIEnv呢?通过pthread_setspecific
把这个JNIEnv存到线程中,当线程退出的时候通过pthread_getspecific
取出,然后释放掉它就好了。
// Return a |JNIEnv*| usable on this thread or NULL if this thread is detached.
JNIEnv* GetEnv()
{
void* env = nullptr;
jint status = g_jvm->GetEnv(&env, JNI_VERSION_1_6);
RTC_CHECK(((env != nullptr) && (status == JNI_OK)) ||
((env == nullptr) && (status == JNI_EDETACHED)))
<< "Unexpected GetEnv return: " << status << ":" << env;
return reinterpret_cast<JNIEnv*>(env);
}
// Return thread ID as a string.
static std::string GetThreadId()
{
char buf[21]; // Big enough to hold a kuint64max plus terminating NULL.
RTC_CHECK_LT(snprintf(buf, sizeof(buf), "%ld", static_cast<long>(syscall(__NR_gettid))), sizeof(buf))
<< "Thread id is bigger than uint64??";
return std::string(buf);
}
// Return the current thread's name.
static std::string GetThreadName() {
char name[17] = {0};
if (prctl(PR_GET_NAME, name) != 0)
return std::string("<noname>");
return std::string(name);
}
JNIEnv* AttachCurrentThreadIfNeeded()
{
JNIEnv* jni = GetEnv();
if (jni)
return jni;
RTC_CHECK(!pthread_getspecific(g_jni_ptr)) << "TLS has a JNIEnv* but not attached?";
std::string name(GetThreadName() + " - " + GetThreadId());
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6;
args.name = &name[0];
args.group = nullptr;
// Deal with difference in signatures between Oracle's jni.h and Android's.
#ifdef _JAVASOFT_JNI_H_ // Oracle's jni.h violates the JNI spec!
void* env = nullptr;
#else
JNIEnv* env = nullptr;
#endif
RTC_CHECK(!g_jvm->AttachCurrentThread(&env, &args)) << "Failed to attach thread";
RTC_CHECK(env) << "AttachCurrentThread handed back NULL!";
jni = reinterpret_cast<JNIEnv*>(env);
RTC_CHECK(!pthread_setspecific(g_jni_ptr, jni)) << "pthread_setspecific";
return jni;
}
检查异常
理论上来说调用大部分JNI接口都需要在调用之后判断一下是否有错误,如果不检查是否存在错误,那么会在下一个调用的时候直接奔溃,不利于问题定位。一般来说这类接口都需要在调用之后判断一下:Get<Static>MethodID
、Get<Static><TYPE>Field
、Call<Static><TYPE>Method
、NewGlobalRef
、DeleteGlobalRef
、NewStringUTF
等。
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
获取枚举字段
class
内的其他class
或者enum
都使用$
拼接
- 通过字段名获取
jclass c = env->FindClass("android/graphics/Bitmap$Config");
jfieldID id = env->GetStaticFieldID(c,"ARGB_8888", "Landroid/graphics/Bitmap$Config;");
jobject o = env->GetStaticObjectField(c, id);
- 通过索引获取
jclass c = env->FindClass("android/graphics/Bitmap$Config");
jmethodID m = env->GetStaticMethodID(c, "values", "()Landroid/graphics/Bitmap$Config;");
jobjectArray arr = static_cast<jobjectArray>(env->CallStaticObjectMethod(c, m));
jobject o = env->GetObjectArrayElement(arr, index);
获取枚举值名(字符串)
jclass c = env->FindClass("android/graphics/Bitmap$Config");
jmethodID m = env->GetStaticMethodID(c, "name", "()Ljava/lang/String;");
jstring name = reinterpret_cast<jstring>(env->CallObjectMethod(j_enum, m));
调用构造函数
构造函数的方法名是<init>
jclass c = env->FindClass(env, "org/stone/WebView");
jmethodID m = jni->GetMethodID(c, "<init>", "(JLjava/lang/String;Landroid/content/Context;II)V");
jobject o = env->NewObject(c, m , reinterpret_cast<intptr_t>(this), env->NewStringUTF("ThreadName", j_content, width, height));
调用静态方法
jclass c = env->FindClass(env, "org/stone/Utility");
jmethodID m = jni->GetStaticMethodID(c, "DrawBitmap", "(JLandroid/graphics/Bitmap;Landroid/os/Handler;Landroid/view/View;)V");
env->CallStaticVoidMethod(c, m, reinterpret_cast<intptr_t>(this), j_bitmap, j_handler, j_view);
调用对象方法
jmethodID m = jni->GetMethodID(c, "goForward","()V");;
env->CallVoidMethod(o, m);
Java String转换为std::string
std::string JavaToStdString(JNIEnv* jni, const jstring& j_string)
{
const jclass string_class = GetObjectClass(jni, j_string);
const jmethodID get_bytes = GetMethodID(jni, string_class, "getBytes", "(Ljava/lang/String;)[B");
const jstring charset_name = jni->NewStringUTF("ISO-8859-1");
const jbyteArray j_byte_array = (jbyteArray)jni->CallObjectMethod(j_string, get_bytes, charset_name);
const size_t len = jni->GetArrayLength(j_byte_array);
std::vector<char> buf(len);
jni->GetByteArrayRegion(j_byte_array, 0, len, reinterpret_cast<jbyte*>(&buf[0]));
return std::string(buf.begin(), buf.end());
}
std::map转换为Java HashMap
jobject StdMapStringToJava(std::map<std::string, std::string> additional_http_headers)
{
jclass c = jni->FindClass("java/util/HashMap");
jobject o = jni->NewObject(c, jni->GetMethodID(c, "<init>", "()V"));
jmethodID m = jni->GetMethodID(c, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
for (const auto& it : additional_http_headers) {
jni->CallObjectMethod(o, m, jni->NewStringUTF(it.first.c_str()), jni->NewStringUTF(it.second.c_str()));
}
return o;
}
Java Map转换为std::map
std::map<std::string, std::string> JavaToStdMapStrings(JNIEnv* jni, jobject j_map)
{
jclass map_class = jni->FindClass("java/util/Map");
jclass set_class = jni->FindClass("java/util/Set");
jclass iterator_class = jni->FindClass("java/util/Iterator");
jclass entry_class = jni->FindClass("java/util/Map$Entry");
jmethodID entry_set_method = jni->GetMethodID(map_class, "entrySet", "()Ljava/util/Set;");
jmethodID iterator_method = jni->GetMethodID(set_class, "iterator", "()Ljava/util/Iterator;");
jmethodID has_next_method = jni->GetMethodID(iterator_class, "hasNext", "()Z");
jmethodID next_method = jni->GetMethodID(iterator_class, "next", "()Ljava/lang/Object;");
jmethodID get_key_method = jni->GetMethodID(entry_class, "getKey", "()Ljava/lang/Object;");
jmethodID get_value_method = jni->GetMethodID(entry_class, "getValue", "()Ljava/lang/Object;");
jobject j_entry_set = jni->CallObjectMethod(j_map, entry_set_method);
jobject j_iterator = jni->CallObjectMethod(j_entry_set, iterator_method);
std::map<std::string, std::string> result;
while (jni->CallBooleanMethod(j_iterator, has_next_method)) {
jobject j_entry = jni->CallObjectMethod(j_iterator, next_method);
jstring j_key = static_cast<jstring>(jni->CallObjectMethod(j_entry, get_key_method));
jstring j_value = static_cast<jstring>(jni->CallObjectMethod(j_entry, get_value_method));
result[JavaToStdString(jni, j_key)] = JavaToStdString(jni, j_value);
}
return result;
}
Native方法定义
native方法的定义规则是:Java_包名_方法名(JNIEnv*, jclass/jobject, ...)
,包名使用下划线_
划分,静态方法第二个参数是jclass
类型,非静态方法第二个参数jobject
类型。Native方法的参数类型要和Java层保持一致,否则会出现野指针或者值不对的情况(根本原因是因为参数的传递是通过栈传递的,比如Java传递的类型占用8个字节,Native接收的类型占用4个字节,那么JVM会从栈上取4个字节强制转换为此类型,结果就是从这个参数及之后的参数内容都不对了)。
- 静态
extern "C" JNIEXPORT void Java_org_stone_Utility_DrawBitmap(JNIEnv* jni, jclass clz, int64_t func_ptr, jobject j_bitmap, int32_t x, int32_t y, int32_t width, int32_t height);
- 非静态
extern "C" JNIEXPORT void Java_org_stone_Utility_DrawBitmap(JNIEnv* jni, jobject obj, int64_t func_ptr, jobject j_bitmap, int32_t x, int32_t y, int32_t width, int32_t height);