中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

Java中JNI的使用(下)

2018-10-08    來源:importnew

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

數(shù)組的操作

數(shù)組是一個很常用的數(shù)據(jù)類型,在但是在 JNI 中并不能直接操作 jni 數(shù)組(比如 jshortArray、jfloatArray)。使用方法是:

  1. 獲取數(shù)組長度:jsize GetArrayLength(jarray array)
  2. 創(chuàng)建新數(shù)組:?ArrayType New<PrimitiveType>Array(jsize length);
  3. 通過JNI數(shù)組獲取一個C/C++數(shù)組:<type>* Get<type>ArrayElements(jshortArray array, jboolean *isCopy)
  4. 指定原數(shù)組的范圍獲取一個C/C++數(shù)組(該方法只針對于原始數(shù)據(jù)數(shù)組,不包括Object數(shù)組):void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf);
  5. 設(shè)置數(shù)組元素:void Set<type>ArrayRegion(jshortArray array, jsize start, jsize len,const <type> *buf)。again,如果是Object數(shù)組需要使用:void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
  6. 使用完之后,釋放數(shù)組:void Release<type>ArrayElements(jshortArray array, jshort *elems, jint mode)

有點要說明的:

1、上面的3中的 isCopy:當你調(diào)用 getArrayElements 時 JVM(Runtime)可以直接返回數(shù)組的原始指針,或者是 copy 一份,返回給你,這是由 JVM 決定的。所以 isCopy 就是用來記錄這個的。他的值是?JNI_TURE?或者?JNI_FALSE。

2、6釋放數(shù)組。一定要釋放你所獲得數(shù)組。其中有一個mode參數(shù),其有三個可選值,分別表示:

  • 0
    • 原始數(shù)組:允許原數(shù)組被垃圾回收。
    • copy: 數(shù)據(jù)會從get返回的buffer copy回去,同時buffer也會被釋放。
  • JNI_COMMIT
    • 原始數(shù)組:什么也不做
    • copy: 數(shù)據(jù)會從get返回的buffer copy回去,同時buffer不會被釋放。
  • JNI_ABORT
    • 原始數(shù)組:允許原數(shù)組被垃圾回收。之前由JNI_COMMIT提交的對數(shù)組的修改將得以保留。
    • copy: buffer會被釋放,同時buffer中的修改將不會copy回數(shù)組!

關(guān)于引用與垃圾回收

比如上面有個方法傳了一個 jobject 進來,然后我把她保存下來,方便以后使用。這樣做是不行噠!因為他是一個 LocalReference,所以不能保證 jobject 指向的真正的實例不被回收。也就是說有可能你用的時候那個指針已經(jīng)是個野指針的。然后你的程序就直接 Segment Fault 了,呵呵。

在JNI中提供了三種類型的引用:

  1. Local Reference:即本地引用。在JNI層的函數(shù),所有非全局引用對象都是Local Reference, 它包括函數(shù)調(diào)用是傳入的jobject和JNI成函數(shù)創(chuàng)建的jobject。Local Reference的特點是一旦JNI層的函數(shù)返回,這些jobject就可能被垃圾回收。
  2. Glocal Reference:全局引用,這些對象不會主動釋放,永遠不會被垃圾回收。
  3. Weak Glocal Reference:弱全局引用,一種特殊的Global Reference,在運行過程中有可能被垃圾回收。所以使用之前需要使用jboolean IsSameObject(jobject obj1, jobject obj2)判斷它是否已被回收。

Glocal Reference:
1. 創(chuàng)建:jobject NewGlobalRef(jobject lobj);
2. 釋放:void DeleteGlobalRef(jobject gref);

Local Reference:
LocalReference也有一個釋放的函數(shù):void DeleteLocalRef(jobject obj),他會立即釋放Local Reference。 這個方法可能略顯多余,其實也是有它的用處的。剛才說Local Reference會再函數(shù)返回后釋放掉,但是假如函數(shù)返回前就有很多引用占了很多內(nèi)存,最好函數(shù)內(nèi)就盡早釋放不必要的內(nèi)存。

關(guān)于JNI_OnLoad

開頭提到 JNI_OnLoad 是 Java1.2 中新增加的方法,對應(yīng)的還有一個 JNI_OnUnload,分別是動態(tài)庫被 JVM 加載、卸載的時候調(diào)用的函數(shù)。有點類似于 Windows 里的 DllMain。
前面提到的實現(xiàn)對應(yīng) native 的方法是實現(xiàn) javah 生成的頭文件中定義的方法,這樣有幾個弊端:

  1. 函數(shù)名太長。很長,相當長。
  2. 函數(shù)會被導(dǎo)出,也就誰說可以在動態(tài)庫的導(dǎo)出函數(shù)表里面找到這些函數(shù)。這將有利于別人對動態(tài)庫的逆向工程,因此帶來安全問題。

現(xiàn)在有了JNI_OnLoad,情況好多了。你不光能在其中完成動態(tài)注冊 native 函數(shù)的工作還可以完成一些初始化工作。Java 對應(yīng)的有了?jint RegisterNatives(jclass clazz, const JNINativeMethod *methods,jint nMethods)函數(shù)。參數(shù)分別是:

  • jclass clazz,于native層對應(yīng)的java class
  • const JNINativeMethod *methods這是一個數(shù)組,數(shù)組的元素是JNI定義的一個結(jié)構(gòu)體JNINativeMethod
  • 上面的數(shù)組的長度

JNINativeMethod:代碼中的定義如下

/*
 * used in RegisterNatives to describe native method name, signature,
 * and function pointer.
 */

typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;

所以他有三個字段,分別是

于是現(xiàn)在你可以不用導(dǎo)出 native 函數(shù)了,而且可以隨意給函數(shù)命名,唯一要保證的是參數(shù)及返回值的統(tǒng)一。然后需要一個?const JNINativeMethod *methods?數(shù)組來完成映射工作。

看起來大概是這樣的:

//只需導(dǎo)出JNI_OnLoad和JNI_OnUnload(這個函數(shù)不實現(xiàn)也行)
/**
 * These are the exported function in this library.
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved);
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved);

//為了在動態(tài)庫中不用導(dǎo)出函數(shù),全部聲明為static
//native methods registered by JNI_OnLoad
static jint native_newInstance (JNIEnv *env, jclass);

//實現(xiàn)native方法
/*
* Class:     com_young_soundtouch_SoundTouch
* Method:    native_newInstance
* Signature: ()I
*/
static jint native_newInstance
(JNIEnv *env, jclass ) {
	int instanceID = ++sInstanceIdentifer;
	SoundTouchWrapper *instance = new SoundTouchWrapper();
	if (instance != NULL) {
		sInstancePool[instanceID] = instance;
		++sInstanceCount;
	}
	LOGDBG("create new SouncTouch instance:%d", instanceID);
	return instanceID;
}

//構(gòu)造JNINativeMethod數(shù)組
static JNINativeMethod gsNativeMethods[] = {
		{
			"native_newInstance",
			"()I",
			reinterpret_cast<void *> (native_newInstance)
		}
};
//計算數(shù)組大小
static const int gsMethodCount = sizeof(gsNativeMethods) / sizeof(JNINativeMethod);

//JNI_OnLoad,注冊native方法。
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
	JNIEnv* env;
	jclass clazz;
	LOGD("JNI_OnLoad called");
	if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
		return -1;
	}
	//FULL_CLASS_NAME是個宏定義,定義了對應(yīng)java類的全名(要把包名中的點(.)_替換成斜杠(/))
	clazz = env->FindClass(FULL_CLASS_NAME);
	LOGDBG("register method, method count:%d", gsMethodCount);
	//注冊JNI函數(shù)
	env->RegisterNatives(clazz, gsNativeMethods,
		gsMethodCount);
	//必須返回一個JNI_VERSION_1_1以上(不含)的版本號,否則直接加載失敗
	return JNI_VERSION_1_6;
}

實戰(zhàn)技巧篇

這里主要是巧用 C 中的宏來減少重復(fù)工作:

迅速生成全名

//修改包名時只需要改以下的宏定義即可
#define FULL_CLASS_NAME "com/young/soundtouch/SoundTouch"
#define func(name) Java_ ## com_young_soundtouch_SoundTouch_ ## name
#define constance(cons) com_young_soundtouch_SoundTouch_ ## cons

比如func(native_1newInstance)展開成:Java_com_young_soundtouch_SoundTouch_native_1newInstance即JNI中需要導(dǎo)出的函數(shù)名(不過用動態(tài)注冊方式?jīng)]太大用了)

constance(AUDIO_FORMAT_PCM16)展開成com_young_soundtouch_SoundTouch_AUDIO_FORMAT_PCM16這個著實有用。

而且如果包名改了也可以很方便的適應(yīng)之。

安卓的log

//define __USE_ANDROID_LOG__ in makefile to enable android log
#if defined(__ANDROID__) && defined(__USE_ANDROID_LOG__)
#include <android/log.h>
#define LOGV(...)   __android_log_print((int)ANDROID_LOG_VERBOSE, "ST_jni", __VA_ARGS__)
#define LOGD(msg)  __android_log_print((int)ANDROID_LOG_DEBUG, "ST_jni_dbg", "line:%3d %s", __LINE__, msg)
#define LOGDBG(fmt, ...) __android_log_print((int)ANDROID_LOG_DEBUG, "ST_jni_dbg", "line:%3d " fmt, __LINE__, __VA_ARGS__)
#else
#define LOGV(...) 
#define LOGD(fmt) 
#define LOGDBG(fmt, ...) 
#endif

通過這樣的宏定義在打 LOGD 或者 LOGDBG 的時候還能自動加上行號!調(diào)試起來爽多了!

C++中清理內(nèi)存的方式

由于 C++ 里面需要手動清除內(nèi)存,因此我的解決方案是定義一個 map,給每個實例一個 id,用 id 把 Java 中的對象和 native 中的對象綁定起來。在 Java 層定義一個?release?方法,用來釋放本地的對象。 本地的 KEY-對象 映射?static std::map<int, SoundTouchWrapper*> sInstancePool;

關(guān)于NDK

因為安卓的約定是把本地代碼放到 jni 目錄下面,但是假如有多個 jni lib 的時候會比較混亂,所以方案是每一個 lib 都在 jni 里面建一個子目錄,然后 jni 里面的 Android.mk 就可以去構(gòu)建子目錄中的 lib 了。

jni/Android.mk 如下(超級簡單):

LOCAL_PATH := $(call my-dir)
include $(call all-subdir-makefiles)

然后在子目錄 soundtouch_module 中的 Android.mk 就可以像一般的 Android.mk 一樣書寫規(guī)則了。

同時記錄一下在 Andoroid.mk 中使用 makefile 內(nèi)建函數(shù)?wildcard?的方法。 有時候源文件是一個目錄下的所有 .cpp/.c 文件,這時候?wildcard?來統(tǒng)配會很方便。但是 Android.mk 與普通的 Makefile 不同在于:

  1. 調(diào)用 Android.mkmingling 的 ${CWD} 并不是 Android.ml 所在的目錄。所以 Android.mk 中有一個變量?LOCAL_PATH := $(call my-dir)?來記錄當前 Android.mk 所在的目錄。
  2. 同時還會把所有的?LOCAL_SRC_FILES?前面加上?$(LOCAL_PATH)。這樣寫 makefile 的時候就可以用相對路徑了,提供了方便。但是這也導(dǎo)致了坑!

因為1,直接使用相對路徑會導(dǎo)致wildcard匹配不到源文件。所以最好這么寫?FILE_LIST := $(wildcard $(LOCAL_PATH)/soundtouch_source/source/SoundTouch/*.cpp)。然而又因為2,這樣還是不行的。所以還需要匹配之后把$(LOCAL_PATH)的部分去掉,因此還得這樣?$(FILE_LIST:$(LOCAL_PATH)/%=%).

還有個小tip:LOCAL_CFLAGS?中最好加上這個定義?-fvisibility=hidden?這樣就不會在動態(tài)庫中導(dǎo)出不必要的函數(shù)了。

附錄簽名

Java 中的函數(shù)簽名包括了函數(shù)的參數(shù)類型,返回值類型。因此即使是重載了的函數(shù),其函數(shù)簽名也不一樣。java編譯器就會根據(jù)函數(shù)簽名來判斷你調(diào)用的到地址哪個方法。 簽名中表示類型是這樣的

1.基本類型都對應(yīng)一個大寫字母,如下:

2. 如果是類則是: L + 類全名(報名中的點(.)用(/)代替)+ ; 比如java.lang.String 對應(yīng)的是?Ljava/lang/String;
3. 如果是數(shù)組,則在前面加[然后加類型簽名,幾位數(shù)組就加幾個[?比如int[]對應(yīng)[I,boolean[][] 對應(yīng)?[[Z,java.lang.Class[]對應(yīng)[Ljava/lang/Class;

可以通過 javap 命令來獲取簽名(javah 生成的頭文件注釋中也有簽名):javap -x -p <類全名>?坑爹的是java中并不能通過反射來獲取方法簽名,需要自己寫一個幫助類。 (其實我還寫了個小程序可以自動生成簽名,和 JNI_OnLoad 中注冊要用到的?JNINativeMethod?數(shù)組,從此再也不用糟心的去寫那該死的數(shù)組了。LOL~~~)

參考資料

  1. Oracle Java SE documents
  2. 深入理解Android 卷 1?第二章 ,鄧凡平著,機械工業(yè)出版社
  3. Google Android documents – JNI Tips

標簽: Google 安全 代碼

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:Spring Boot基礎(chǔ)教程 ( 一 ) :基礎(chǔ)項目構(gòu)建,引入web模塊,完成一個簡單的RESTful API

下一篇:Java中JNI的使用(上)