1. 程式人生 > >FFmpeg In Android - JNI文件-2

FFmpeg In Android - JNI文件-2

8 Additional JNI Features

8.1 JNI and Threads

Java VM支援多執行緒的併發執行,併發大大增加了複雜度,之前單執行緒時從沒遇到過的.多執行緒可能同時訪問同一個物件,同一個檔案描述符,簡單來說就是臨界資源.為了最有效地使用本節,你應該事先對多執行緒的概念較為熟悉.你應該知道怎樣用Java語言使用多執行緒,同步訪問臨界資源等,可以參考Concurrent Programming in Java.

8.1.1 Constraints

原生代碼執行在多執行緒環境時,有些約束條件必須牢記。理解了這些約束,不管本地函式有多少執行緒在併發,程式碼執行都會很安全。

  • JNIEnv指標不能跨執行緒使用,不能將其快取起來又在其他執行緒使用;
  • 區域性引用只在建立它的執行緒中有效,不能將區域性引用從一個執行緒傳遞到另外一個執行緒,如果需要這樣做,要將區域性引用轉換為全域性引用。

8.1.2 Monitor Entry and Exit

監視器是Java平臺上的原始同步機制,每個物件都可以與監視器動態關聯.JNI允許你使用監視器進行同步,這樣相當於Java語言的程式碼塊同步:

synchronized (obj) {
... // synchronized block
}

Java VM確保一個執行緒執行程式碼塊之前首先與監視器關聯,這樣在一個時刻內最多隻有一個執行緒持有監視器並執行程式碼塊。等待其他執行緒退出監視器時,一個執行緒會阻塞。
原生代碼可以使用JNI函式

在JNI引用上實現同步功能,使用MonitorEnter進入監視器,MonitorExit退出監視器:

if ((*env)->MonitorEnter(env, obj) != JNI_OK) {
	... /* error handling */
}
... /* synchronized block */
if ((*env)->MonitorExit(env, obj) != JNI_OK) {
	... /* error handling */
};

如果執行緒尚未持有監視器就呼叫MonitorExit,會出現IllegalMonitorStateException異常。JNI函式

可能會呼叫失敗,必須檢查返回值。MonitorEnter, MonitorExit與jclass, jstring, jarray等型別一起使用,這些都是jobject型別。

8.1.3 Monitor Wait and Notify

Java API包含其他一些對執行緒同步很有用的方法,Object.wait, Object.notify, Object.notifyAll,JNI沒有對應的函式,而是使用回撥Java API的辦法來代替:

/* precomputed method IDs */
static jmethodID MID_Object_wait;
static jmethodID MID_Object_notify;
static jmethodID MID_Object_notifyAll;
void JNU_MonitorWait(JNIEnv *env, jobject object, jlong timeout)
{
	(*env)->CallVoidMethod(env, object, MID_Object_wait,
	timeout);
}
void JNU_MonitorNotify(JNIEnv *env, jobject object)
{
	(*env)->CallVoidMethod(env, object, MID_Object_notify);
}
void JNU_MonitorNotifyAll(JNIEnv *env, jobject object)
{
	(*env)->CallVoidMethod(env, object, MID_Object_notifyAll);
}

上面假設methodID分別對應了Object.wait, Object.notify, Object.notifyAll並已經在其他地方初始化了。

8.1.4 Obtaining a JNIEnv Pointer in Arbitrary Contexts

前面說過JNIEnv指標只在單個執行緒有效,一般來說沒什麼問題,因為本地函式可以從第一個引數中獲得。但有時候有些函式不是Java VM直接呼叫的,其沒有JNIEnv引數,可以呼叫AttachCurrentThread為當前執行緒獲得JNIEnv指標:

JavaVM *jvm; /* already set */
f()
{
	JNIEnv *env;
	(*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
	... /* use env */
}

當執行緒Attach到Java VM後,AttachCurrentThread返回,就獲得了當前執行緒的JNIEnv指標了。有很多方式獲取到JavaVM指標: 建立Java VM時記錄下來,使用JNI_GetCreatedJavaVMs查詢,呼叫JNI函式GetJavaVM,或者在JNI_OnLoad中定義.跟JNIEnv指標不同,JavaVM指標可以快取在全域性引用,跨執行緒使用。
Java 2 SDK release 1.2 提供了新的函式GetEnv來檢查當前執行緒是否已經Attach到Java VM了,如果是,返回當前執行緒的JNIEnv指標。如果已經Attach到Java VM,GetEnvAttachCurrentThread的作用是相同的。

8.1.5 Matching the Thread Models

Suppose that native code to be run in multiple threads accesses a global resource. Should the native code use JNI functions MonitorEnter and MonitorExit, or use the native thread synchronization primitives in the host environment (such as mutex_lock on Solaris)? Similarly, if the native code needs to create a new thread,should it create a java.lang.Thread object and perform a callback of Thread.start through the JNI, or should it use the native thread creation primitive in the host environment (such as thr_create on Solaris)?
The answer is that all of these approaches work if the Java virtual machine implementation supports a thread model that matches that used by the native code. The thread model dictates how the system implements essential thread operations such as scheduling, context switching, synchronization, and blocking in system calls. In a native thread model the operating system manages all the essential thread operations. In a user thread model, on the other hand, the application code implements the thread operations. For example, the “Green thread” model shipped with JDK and Java 2 SDK releases on Solaris uses the ANSI C functions setjmp and longjmp to implement context switches.
Many modern operating systems (such as Solaris and Win32) support a native thread model. Unfortunately, some operating systems still lack native thread support. Instead, there may be one or many user thread packages on these operating
systems.
If you write application strictly in the Java programming language, you need not worry about the underlying thread model of the virtual machine implementation. The Java platform can be ported to any host environment that supports the
required set of thread primitives. Most native and user thread packages provide the necessary thread primitives for implementing a Java virtual machine.
JNI programmers, on the other hand, must pay attention to thread models. The application using native code may not function properly if the Java virtual implementation and the native code have a different notion of threading and synchronization. For example, a native method could be blocked in a synchronization operation in its own thread model, but the Java virtual machine, running in a different thread model, may not be aware that the thread executing the native method is blocked. The application deadlocks because no other threads will be scheduled.
The thread models match if the native code uses the same thread model as the Java virtual machine implementation. If the Java virtual machine implementation uses native thread support, the native code can freely invoke thread-related primitives in the host environment. If the Java virtual machine implementation is based on a user thread package, the native code should either link with the same user thread package or rely on no thread operations at all. The latter may be harder to achieve than you think: most C library calls (such as I/O and memory allocation functions) perform thread synchronization underneath. Unless the native code performs pure computation and makes no library calls, it is likely to use thread primitives indirectly.
Most virtual machine implementations support only a particular thread model for JNI native code. Implementations that support native threads are the most flexible, hence native threads, when available, are typically preferred on a given host environment. Virtual machine implementations that rely on a particular user thread package may be severely limited as to the type of native code with which they can operate.
Some virtual machine implementations may support a number of different thread models. A more flexible type of virtual machine implementation may even allow you to provide a custom thread model implementation for virtual machine’s
internal use, thus ensuring that the virtual machine implementation can work with your native code. Before embarking on a project likely to require native code, you should consult the documentation that comes with your virtual machine implementation for thread model limitations.

8.2 Writing Internationalized Code

//TODO

8.3 Registering Native Methods

程式執行一個本地函式之前需要先做兩步工作,1,載入包含了本地實現函式的本地庫, 2,連結本地庫:

  1. System.loadLibrary定位和載入給定名稱的本地庫;
  2. Java VM在已經載入了的多個本地庫中的其中一個本地庫定位到本地實現函式。
    本節介紹另外一種方式實現第二步的工作,不是依賴Java VM去已經載入的本地庫搜尋本地實現函式,而是JNI程式設計人員手動通過用class reference, method name,and method descriptor來註冊函式指標的辦法來連結本地函式。
JNINativeMethod nm;
nm.name = "g";
/* method descriptor assigned to signature field */
nm.signature = "()V";
nm.fnPtr = g_impl;
(*env)->RegisterNatives(env, cls, &nm, 1);

上面的程式碼註冊了本地函式g_impl來作為Foo.g的本地實現。
void JNICALL g_impl(JNIEnv *env, jobject self);
本地函式g_impl既不需要遵循JNI命名轉換規則,也不需要用JNIEXPORT從本地庫匯出,不過仍然需要用JNICALL。RegisterNatives對以下幾個目的很有用:

  • 有大量本地實現函式時,用這個方式效能更高;
  • 可以多次呼叫RegisterNatives,在執行時允許更新本地實現函式;
  • 在一個本地應用嵌入Java VM時,需要連結一個在本地應用的本地實現函式,RegisterNatives特別有用。Java VM這時無法自動搜尋本地實現函式,因為它只會搜尋本地庫,而不會搜尋本地應用的函式。

8.4 Load and Unload Handlers

載入和解除安裝處理程式允許本機庫匯出兩個函式:一個在System.loadLibrary載入本地庫時呼叫,另外一個在Java VM解除安裝本地庫時呼叫,從Java 2 SDK release 1.2開始添加了這個特性。

8.4.1 The JNI_OnLoad Handler

當System.loadLibrary載入一個本地庫,Java VM從本地庫搜尋下面的入口:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved);
可以在JNI_Onload內呼叫任意的JNI函式,JNI_Onload典型的作用是快取JavaVM指標,class引用,method and field IDs:

JavaVM *cached_jvm;
jclass Class_C;
jmethodID MID_C_g;

NIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
	JNIEnv *env;
	jclass cls;
	cached_jvm = jvm; /* cache the JavaVM pointer */
	if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)) {
		return JNI_ERR; /* JNI version not supported */
	}
	cls = (*env)->FindClass(env, "C");
	if (cls == NULL) {
		return JNI_ERR;
	}
	/* Use weak global ref to allow C class to be unloaded */
	Class_C = (*env)->NewWeakGlobalRef(env, cls);
	if (Class_C == NULL) {
		return JNI_ERR;
	}
	/* Compute and cache the method ID */
	MID_C_g = (*env)->GetMethodID(env, cls, "g", "()V");
	if (MID_C_g == NULL) {
		return JNI_ERR;
	}
	return JNI_VERSION_1_2;
}

// 獲取JNIEnv指標的工具函式
JNIEnv *JNU_GetEnv()
{
	JNIEnv *env;
	(*cached_jvm)->GetEnv(cached_jvm,
	(void **)&env,
	JNI_VERSION_1_2);
	return env;
}

8.4.2 The JNI_OnUnload Handler

直覺上,Java VM在解除安裝本地庫時呼叫JNI_OnUnload處理器,然而,這還不夠精確。VM決定什麼時候解除安裝本地庫呢?哪個執行緒呼叫JNI_OnUnload處理器呢?規則如下:

  • 某個類C呼叫System.loadLibrary,C被類載入器L載入,Java VM將L與本地庫關聯起來;
  • Java VM在某時刻決定類載入器L不再是一個活動物件時,就呼叫JNI_OnUnload和解除安裝本地庫,因為一個類載入器涉及到所有它載入的類,這意味著類C也會被解除安裝;
  • JNI_OnUnload處理器在清理器finalizer中執行,要麼同步被java.lang.System.runFinalization呼叫,要麼非同步被Java VM呼叫。
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *jvm, void *reserved)
{
	JNIEnv *env;
	if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)) {
	return;
	}
	(*env)->DeleteWeakGlobalRef(env, Class_C);
	return;
}

JNI_OnUnload刪除在JNI_OnLoad定義的弱全域性引用,我們不需要刪除MID_C_g,因為解除安裝類C時會自動回收。
這裡解釋下為什麼用弱全域性引用來快取類C(Class_C),而不用全域性引用。全域性引用會保持C一直存活,相應地保持了它的類載入器也存活,這樣JNI_OnUnload就不可能被呼叫了。
JNI_OnUnload處理器在清理器finalizer中執行,因為它在一個未知的執行緒上下文執行,為了避免潛在的死鎖問題,在JNI_OnUnload中應當避免複雜的同步和鎖定操作,典型的是做一些簡單任務比如清理資源的操作。
在JNI_OnUnload中不能再使用類相關的東西,比如Class_C等,因為類被解除安裝了,所有類定義的東西都不再存在。

8.5 Reflection Support

反射Reflection通常意味著在執行時操縱語言層的建構函式,例如,反射允許你在執行時得到任意物件的名稱還有它成員和方法。Java語言提供了java.lang.reflect,還有一些方法定義在java.lang.Object, java.lang.Class, 儘管可以呼叫Java API的方法,JNI也提供了下面的函式,使常用的反射操作更方便快捷:

  • GetSuperclass返回一個給定類的父類
  • IsAssignableFrom檢查一個 Class 物件所表示的類或介面與指定的 Class 引數所表示的類或介面是否相同,或是否是其超類或超介面
  • GetObjectClass返回給定的jobject對應的class
  • IsInstanceOf檢查一個jobject是否給定class的例項
  • FromReflectedField, ToReflectedField允許原生代碼轉換filed IDS到java.lang.reflect.Field物件
  • FromReflectedMethod,ToReflectedMethod允許原生代碼轉換method IDs到java.lang.reflect.Method物件,和java.lang.reflect.Constructor物件。