前言
長文預(yù)警,本文是JNI開發(fā)的基礎(chǔ)知識介紹和使用經(jīng)驗(yàn)總結(jié),基本上涵蓋了Android JNI開發(fā)的大多數(shù)知識點(diǎn),因此文章較長。
1. NDK介紹
1.1 NDK簡介
NDK,即原生開發(fā)套件 (Native Development Kit),它是一套工具集,讓我們能夠在 Android 應(yīng)用中使用 C/C++代碼。Android Studio首先使用NDK 將 C/C++ 代碼編譯成原生庫,然后使用集成構(gòu)建系統(tǒng) Gradle 將原生庫打包到 APK 中。Java 代碼隨后可以通過JNI(Java原生接口)框架調(diào)用原生庫中的C/C++函數(shù)。
NDK主要包括了:
- 從C / C++生成原生代碼庫所需要的工具和構(gòu)建文件。
- 支持Android平臺的一系列原生系統(tǒng)(C/C++、JNI)的頭文件和庫。
1.2 NDK的使用場景
我們知道,C/C++是比java更底層的語言,C/C++的執(zhí)行效率更高、延遲更小,因此在某些情況下我們可能會(huì)使用NDK,通過Java代碼來調(diào)用C/C++代碼,完成一些工作。例如:
- 進(jìn)一步提升設(shè)備性能,以降低延遲,或運(yùn)行計(jì)算密集型應(yīng)用,如游戲或物理模擬。
- 重復(fù)使用已有的 C 或 C++ 庫。
- 圖像、音頻、視頻處理。
1.3 下載 NDK 和工具
要進(jìn)行NDK開發(fā),我們需要下載以下組件:
- Android 原生開發(fā)套件 (NDK):編譯C/C++
- CMake:C/C++構(gòu)建工具,類似Makefile,比Makefile使用更加方便。可與 Gradle 搭配使用來構(gòu)建原生庫。
- LLDB:Android Studio 用于調(diào)試原生代碼的調(diào)試程序。
我們可以直接在SDKManager中下載這三個(gè)組件。
2. JNI簡介
JNI (Java Native Interface),即java本地接口,它是為java語言和其它語言交互調(diào)用而設(shè)計(jì)的標(biāo)準(zhǔn)應(yīng)用程序接口。JNI允許JVM中運(yùn)行的Java代碼與用其他編程語言(例如C,C ++和匯編)編寫的應(yīng)用程序和庫進(jìn)行交互操作。
實(shí)際使用中,JNI的一個(gè)重要的用途是讓java通過JNI調(diào)用C/C++函數(shù)。C/C++函數(shù)編譯后就存放在庫文件中,如大家所知,庫文件在Windows平臺是DLL文件,在UNIX/Linux平臺上是SO文件。而庫文件和本地環(huán)境是強(qiáng)依賴的,是不具備跨平臺特性的。因此,想要實(shí)現(xiàn)跨平臺,就必須提供在所有的目標(biāo)平臺能運(yùn)行的庫文件。例如實(shí)現(xiàn)常見的android平臺,一般需要提供這些版本的庫文件:arm64-v8a, armeabi, armeabi-v7a, mips, mips64, x86, x86_64。
3. JNI版Hello world
和學(xué)習(xí)所有編程語言一樣,我們首先來寫一個(gè)JNI版的Hello world。
3.1 手動(dòng)編譯和使用JNI
雖然很繁瑣,但是為了完整的了解JNI的開發(fā)流程,我們還是要先來說說手動(dòng)編譯和使用JNI的一般流程:
- a. 編寫Test.java,添加native方法
package com.qxt;
public class Test {
public static native String sayHello();
}
- b. 使用javac命令將Test.java編譯生成Test.class:
javac Test.java
- c. 使用javah命令將Test.class編譯生成com_qxt_Test.h頭文件。注意要指定正確的classpath。
javah com.qxt.Test
生成的com_qxt_Test.h頭文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_qxt_Test */
#ifndef _Included_com_qxt_Test
#define _Included_com_qxt_Test
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_qxt_Test
* Method: sayHello
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_qxt_Test_sayHello
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
-
d. 編寫com_qxt_Test.cpp實(shí)現(xiàn)頭文件,并實(shí)現(xiàn)具體的函數(shù)。
com_qxt_Test.cpp文件:
#include <jni.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_qxt_Test_sayHello(
JNIEnv* env,
jclass clazz) {
return env->NewStringUTF("Hello world from JNI");
}
- e. 使用Makefile或者Cmake構(gòu)建和使用NDK編譯com_qxt_Test.cpp或者com_qxt_Test.c源文件,生成libtest.so庫文件
Application.mk:
APP_ABI := armeabi-v7a arm64-v8a
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_C_INCLUDES := ./
LOCAL_SRC_FILES := ./com_qxt_Test.cpp
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
NDK編譯命令:
ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk APP_BUILD_SCRIPT=Android.mk
- f. 在Test.java中使用靜態(tài)代碼塊加載庫文件:
static {
System.loadLibrary("test");
}
可以看到手動(dòng)編譯和使用JNI的流程是比較麻煩的,不禁讓人想起大學(xué)時(shí)代初學(xué)java,老師讓大家用記事本手撕java項(xiàng)目的恐懼。不過幸運(yùn)的是google的Android Studio已經(jīng)可以幫我們完成這些工作。接下來將介紹使用Android Studio開發(fā)JNI。
3.2 使用Android Studio開發(fā)JNI
我們只要在Android Studio中選擇 File > New Project,在Select a Project Template界面選擇Native C++,一路Next往下,一個(gè)JNI項(xiàng)目就創(chuàng)建好了,如圖:
創(chuàng)建好的項(xiàng)目如上圖所示。我們無需再生成頭文件以及手動(dòng)寫Makefile了,Android Studio已經(jīng)幫我們完成了這一切,并且新版的Android Studio已經(jīng)用使用上更加方便的cmake替換了Makefile了。開發(fā)時(shí)我們只要編輯java文件和cpp文件、配置好CMakeLists.txt、在build.gradle中指定CMakeLists.txt就可以了。在項(xiàng)目編譯時(shí),Android Studio會(huì)自動(dòng)編譯cpp文件,并將生成的庫文件打包到apk或者aab文件當(dāng)中。我們來看看主要文件的代碼:
MainActivity.java:
package com.qxt.test;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
native-lib.cpp
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_qxt_myapp_MainActivity_stringFromJNI(
JNIEnv* env,
jobject ) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
CMakeLists.txt:(為了看起來比較清晰,已刪除了無效的注釋)
cmake_minimum_required(VERSION 3.4.1)
add_library( native-lib
SHARED
native-lib.cpp )
find_library( log-lib
log )
target_link_libraries( native-lib
${log-lib} )
剛剛我們已經(jīng)創(chuàng)建了一個(gè)簡單的JNI項(xiàng)目。
我們注意一下,在native-lib.cpp中本地函數(shù)stringFromJNI的參數(shù)中有個(gè)JNIEnv* env。JNIEnv,顧名思義就是JNI環(huán)境變量或者叫JNI接口指針。JNIEnv非常重要,JNI中所有的接口函數(shù)都是通過JNIEnv來調(diào)用的。
在native-lib.cpp中我們還導(dǎo)入了一個(gè)叫jni.h頭文件,這個(gè)頭文件也非常重要。jni.h中定義了JNI的數(shù)據(jù)類型、數(shù)據(jù)結(jié)構(gòu)、接口函數(shù)、回調(diào)函數(shù)以及常量等等。在接下來的章節(jié)中會(huì)逐一介紹相關(guān)的重要內(nèi)容,并舉例說明。在最后還會(huì)簡單介紹cmake的使用。
另外,由于native、method、function等單詞翻譯的問題,JNI和Java的概念經(jīng)常容易搞混,因此我們在接下來的章節(jié)中,有如下命名約定:
- native方法指代Java層的native方法。
- 本地函數(shù)指代JNI層的C/C++函數(shù)。
4. JNI的數(shù)據(jù)類型和數(shù)據(jù)結(jié)構(gòu)
4.1 基本類型
JNI包括了許多與Java基本類型相對應(yīng)的基本類型。具體如下表:
4.2 引用類型
JNI包括了許多與Java引用類型相對應(yīng)的引用類型。具體如下表:
4.3 字段和方法ID
除了Java中常用的基本類型和引用類型,JNI還定義了jfieldID和jmethodID。
jfieldID和jmethodID是常規(guī)的C指針類型,它們的聲明如下:
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID *jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID *jmethodID; /* method IDs */
在JNI中調(diào)用java對象的變量或者方法時(shí)常常會(huì)用到j(luò)fieldID和jmethodID。
4.4 jvalue類型
除了以上介紹的數(shù)據(jù)類型,JNI還定義了jvalue聯(lián)合類型,jvalue被用作自變量數(shù)組的元素類型。jvalue的聲明如下:
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
jvalue在本地函數(shù)調(diào)用java方法時(shí),經(jīng)常會(huì)被用做參數(shù)類型。
4.5 類型簽名
JNI同樣使用了JVM的類型簽名表示。具體如下表:
例如,對于Java方法:
long f (int n, String s, int[] arr);
它的類型簽名為:
(ILjava/lang/String;[I)J
使用javap命令查看類型簽名
在接下來的章節(jié)中會(huì)介紹JNI函數(shù)的動(dòng)態(tài)注冊,在動(dòng)態(tài)注冊時(shí),就需要用到類型簽名,而類型簽名可以通過javap命令來查看,例如,查看Test.java的類型簽名,先將Test.java編譯成Test.class。然后:
javap -s Test.class
5. JNI的引用
在了解完JNI的數(shù)據(jù)類型之后,我們接下來繼續(xù)說說JNI的引用。JNI的引用分為四種:
- 全局引用(GlobalReferences),全局引用全局有效,JVM無法釋放和回收全局引用,全局引用必須通過調(diào)用DeleteGlobalRef()顯式釋放。
分配全局引用:
jobject NewGlobalRef(JNIEnv *env, jobject obj);
釋放全局引用:
void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
- 弱全局引用(WeakGlobalReferences),弱全局引用是一種特殊的全局引用,與全局引用不同,JVM可以對它進(jìn)行垃圾回收。弱全局引用可以在使用全局或局部引用的任何情況下使用。當(dāng)進(jìn)行垃圾回收時(shí),如果一個(gè)對象僅被弱全局引用所引用,則它將被釋放。指向該對象的弱全局引用將指向NULL。因此,使用弱全局引用前需要進(jìn)行非空判斷。我們還可以通過IsSameObject將弱引用與NULL進(jìn)行比較,來檢測弱全局引用是否指向NULL。
分配弱全局引用:
jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);
釋放弱全局引用:
void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);
- 局部引用(LocalReferences),局部引用在本地方法調(diào)用期間有效,局部引用在本地方法返回后自動(dòng)釋放。每個(gè)局部引用都要消耗一定數(shù)量的JVM資源。因此,使用時(shí)需要確保本地方法不會(huì)分配過多的局部引用。盡管在本地方法返回Java之后會(huì)自動(dòng)釋放局部引用,但是分配過多的局部引用可能會(huì)導(dǎo)致JVM在執(zhí)行本機(jī)方法期間耗盡內(nèi)存(OOM)。
分配局部引用:
jobject NewLocalRef(JNIEnv *env, jobject ref);
釋放局部引用:
void DeleteLocalRef(JNIEnv *env, jobject localRef);
查詢局部引用容量:
jint EnsureLocalCapacity(JNIEnv *env, jint capacity);
- 無效引用(InvalidReferences),無效引用一般情況下沒有什么用,不展開介紹。
總的來說,JNI的引用并不復(fù)雜,但使用時(shí)我們?nèi)孕枰3至己玫木幊塘?xí)慣:
a. 一個(gè)引用不管能不能被JVM釋放和回收,不再使用后立即顯示釋放。
b. 在不需要額外引用的情況下,絕不分配新的引用。
6. JNI使用類和對象
6.1 類
DefineClass
/*
* @param env: JNI接口指針.
* @param name: 要定義的類或接口的名稱。該字符串以修改后的UTF-8編碼。
* @param loader: 分配給已定義類的類加載器。
* @param buf: 包含.class文件數(shù)據(jù)的緩沖區(qū)。
* @param bufLen: 緩沖區(qū)長度。
* @return 返回Java類對象,如果發(fā)生錯(cuò)誤,則返回NULL。
*/
jclass DefineClass(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize bufLen);
FindClass
/*
* @param env: JNI接口指針。
* @param name: 完全限定的類名(即,包名,以“ /” 分隔,后跟類名,例如java.lang.String:“java/lang/String”)。
* 如果名稱以“ [”(數(shù)組簽名字符)開頭,則返回?cái)?shù)組類。該字符串以修改后的UTF-8編碼。
* @return 返回完全限定的名稱的類的對象,如果找不到該類,則返回NULL。
*/
jclass FindClass(JNIEnv *env, const char *name);
GetSuperclass
/*
* @param env: JNI接口指針。
* @param clazz: 一個(gè)Java類對象。
* @return 返回以clazz所屬類的超類或者NULL。
*/
jclass GetSuperclass(JNIEnv *env, jclass clazz);
IsAssignableFrom
/*
* @param env: JNI接口指針。
* @param clazz1: 第一個(gè)類參數(shù)。
* @param clazz2: 第二個(gè)類參數(shù)。
* @return 如果以下任一條件為真,則返回JNI_TRUE:
* 第一個(gè)類參數(shù)和第二個(gè)類參數(shù)引用相同的Java類。
* 第一個(gè)類參數(shù)是第二個(gè)類參數(shù)的子類。
* 第二個(gè)類參數(shù)為接口,第一個(gè)類參數(shù)實(shí)現(xiàn)了該接口。
*/
jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);
類的使用實(shí)例將在后面的小節(jié)中和對象一起介紹。
6.2 對象
AllocObject
/*
* 分配新的Java對象,而無需調(diào)用該對象的任何構(gòu)造函數(shù)。返回對該對象的引用。clazz參數(shù)不能為任何數(shù)組類。
* @param env: JNI接口指針。
* @param clazz: 一個(gè)Java類對象。
* @return 返回Java對象,無法構(gòu)造該對象則返回NULL。
*/
jobject AllocObject(JNIEnv *env, jclass clazz);
NewObject
/*
* 構(gòu)造一個(gè)新的Java對象。methodID指定要調(diào)用的構(gòu)造方法。必須通過GetMethodID()獲取構(gòu)造方法的methodID。
* GetMethodID獲取構(gòu)造方法的methodID時(shí)方法名為<init>,返回類型為void(V)。clazz參數(shù)不能引用數(shù)組類。
* @param env: JNI接口指針。
* @param clazz: 一個(gè)Java類對象。
* @param methodID:構(gòu)造函數(shù)的methodID。
* @param ...:構(gòu)造函數(shù)的參數(shù)。
* @param args:構(gòu)造函數(shù)的參數(shù)數(shù)組。
* @param args:構(gòu)造函數(shù)參數(shù)的va_list。
* @return 返回Java對象,無法構(gòu)造該對象則返回NULL。
*/
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);
GetObjectClass
/*
* 獲取對象所屬的類
* @param env: JNI接口指針。
* @param obj:一個(gè)Java對象(必須不是NULL)。
* @return 返回一個(gè)Java類對象。
*/
jclass GetObjectClass(JNIEnv *env, jobject obj);
GetObjectRefType
/*
* 獲取obj的引用類型 。該參數(shù)obj可以是局部引用,全局引用或弱全局引用。
* @param env: JNI接口指針。
* @param obj: 局部引用、全局引用或者弱全局引用。
* @return 返回以下枚舉值之一:
* 如果obj不是有效的引用,則返回JNIInvalidRefType = 0。
* 如果obj是局部引用類型,則返回JNILocalRefType = 1。
* 如果obj是全局引用類型,則返回JNIGlobalRefType = 2。
* 如果obj是弱全局引用類型,則返回JNIWeakGlobalRefType = 3。
*/
jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj);
IsInstanceOf
/*
* 判斷對象是否是類的實(shí)例。
* @param env: JNI接口指針。
* @param obj:一個(gè)Java對象
* @return obj可以強(qiáng)制轉(zhuǎn)換為clazz返回JNI_TRUE; 否則返回JNI_FALSE。一個(gè)NULL對象可以強(qiáng)制轉(zhuǎn)換為任何類。
*/
jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);
IsSameObject
/*
* 測試兩個(gè)引用是否引用相同的Java對象。
* @param env: JNI接口指針。
* @param ref1:一個(gè)Java對象。
* @param ref2:一個(gè)Java對象。
* @return 如果ref1和ref2引用相同的Java對象,或者兩者均為NULL,返回JNI_TRUE; 否則返回JNI_FALSE。
*/
jboolean IsSameObject(JNIEnv *env, jobject ref1, jobject ref2);
6.3 類和對象的應(yīng)用實(shí)例
調(diào)用類的構(gòu)造方法創(chuàng)建一個(gè)對象。
MainActivity.java:
public native Object testClass(int value);
native-lib.cpp
extern "C" JNIEXPORT jobject JNICALL Java_com_qxt_myapp_MainActivity_testClass(JNIEnv *env, jobject thiz, jint value) {
jclass clazz = env->FindClass("java/lang/Integer");
if (clazz != nullptr) {
jmethodID integerConstructID = env->GetMethodID(clazz, "<init>", "(I)V");
return env->NewObject(clazz, integerConstructID, value);
}
return NULL;
}
6.4 調(diào)用實(shí)例字段和方法
6.4.1 調(diào)用實(shí)例字段
GetFieldID
/*
* 返回類的實(shí)例(非靜態(tài))字段的字段ID。該字段由其名稱和簽名指定。
* Get<type>Field和Set<type>Field系列函數(shù)使用字段ID檢索對象字段。
* GetFieldID() 將使未初始化的類被初始化。GetFieldID()無法用于獲取數(shù)組的長度,獲取數(shù)組長度請使用GetArrayLength()代替。
*
* @param env: JNI接口指針。
* @param clazz:一個(gè)Java類對象。
* @param name:字段名稱,以\0結(jié)尾的UTF-8字符串。
* @param sig:字段簽名,以\0結(jié)尾的UTF-8字符串。
* @return 返回字段ID,如果操作失敗返回NULL。
*/
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
Get<type>Field
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
jobject GetObjectField(JNIEnv*, jobject, jfieldID);
jboolean GetBooleanField(JNIEnv*, jobject, jfieldID);
jbyte GetByteField(JNIEnv*, jobject, jfieldID);
jchar GetCharField(JNIEnv*, jobject, jfieldID);
jshort GetShortField(JNIEnv*, jobject, jfieldID);
jint GetIntField(JNIEnv*, jobject, jfieldID);
jlong GetLongField(JNIEnv*, jobject, jfieldID);
jfloat GetFloatField(JNIEnv*, jobject, jfieldID);
jdouble GetDoubleField(JNIEnv*, jobject, jfieldID);
Set<type>Field
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
void SetObjectField(JNIEnv*, jobject, jfieldID, jobject);
void SetBooleanField(JNIEnv*, jobject, jfieldID, jboolean);
void SetByteField(JNIEnv*, jobject, jfieldID, jbyte);
void SetCharField(JNIEnv*, jobject, jfieldID, jchar);
void SetShortField(JNIEnv*, jobject, jfieldID, jshort);
void SetIntField(JNIEnv*, jobject, jfieldID, jint);
void SetLongField(JNIEnv*, jobject, jfieldID, jlong);
void SetFloatField(JNIEnv*, jobject, jfieldID, jfloat);
void SetDoubleField(JNIEnv*, jobject, jfieldID, jdouble);
6.4.2 調(diào)用實(shí)例方法
GetMethodID
/*
* 返回類或接口的實(shí)例(非靜態(tài))方法的方法ID。該方法可以在clazz的超類之一中定義,并由繼承clazz。該方法由其名稱和簽名確定。
* 調(diào)用 GetMethodID() 將使未初始化的類被初始化。
* 要獲取構(gòu)造函數(shù)的方法ID,請?zhí)峁?<init>作為方法名稱,并提供 void(V)作為返回類型。
*
* @param env: JNI接口指針。
* @param clazz:一個(gè)Java類對象。
* @param name:方法名稱,以\0結(jié)尾的UTF-8字符串。
* @param sig:方法簽名,以\0結(jié)尾的UTF-8字符串。
* @return 返回方法ID,如果操作失敗返回NULL。
*/
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
Call<type>Method
NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
這三個(gè)操作族中的方法用于從本地函數(shù)中調(diào)用Java實(shí)例方法,它們的區(qū)別僅僅是傳參機(jī)制不同。
/*
* 調(diào)用實(shí)例方法
* GetMethodID獲取構(gòu)造方法的methodID時(shí)方法名為<init>,返回類型為void(V)。clazz參數(shù)不能引用數(shù)組類。
* @param env: JNI接口指針。
* @param jobject: 一個(gè)Java對象。
* @param methodID:java函數(shù)的methodID, 必須通過調(diào)用GetMethodID()來獲得。
* @param ...:java函數(shù)的參數(shù)。
* @param args:java函數(shù)的參數(shù)數(shù)組。
* @param args:java函數(shù)參數(shù)的va_list。
* @return 返回Java對象,無法構(gòu)造該對象則返回NULL。
*/
jobject CallObjectMethod(JNIEnv*, jobject, jmethodID, ...);
jobject CallObjectMethodV(JNIEnv*, jobject, jmethodID, va_list);
jobject CallObjectMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jboolean CallBooleanMethod(JNIEnv*, jobject, jmethodID, ...);
jboolean CallBooleanMethodV(JNIEnv*, jobject, jmethodID, va_list);
jboolean CallBooleanMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jbyte CallByteMethod(JNIEnv*, jobject, jmethodID, ...);
jbyte CallByteMethodV(JNIEnv*, jobject, jmethodID, va_list);
jbyte CallByteMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jchar CallCharMethod(JNIEnv*, jobject, jmethodID, ...);
jchar CallCharMethodV(JNIEnv*, jobject, jmethodID, va_list);
jchar CallCharMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jshort CallShortMethod(JNIEnv*, jobject, jmethodID, ...);
jshort CallShortMethodV(JNIEnv*, jobject, jmethodID, va_list);
jshort CallShortMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jint CallIntMethod(JNIEnv*, jobject, jmethodID, ...);
jint CallIntMethodV(JNIEnv*, jobject, jmethodID, va_list);
jint CallIntMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jlong CallLongMethod(JNIEnv*, jobject, jmethodID, ...);
jlong CallLongMethodV(JNIEnv*, jobject, jmethodID, va_list);
jlong CallLongMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jfloat CallFloatMethod(JNIEnv*, jobject, jmethodID, ...);
jfloat CallFloatMethodV(JNIEnv*, jobject, jmethodID, va_list);
jfloat CallFloatMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jdouble CallDoubleMethod(JNIEnv*, jobject, jmethodID, ...);
jdouble CallDoubleMethodV(JNIEnv*, jobject, jmethodID, va_list);
jdouble CallDoubleMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
void CallVoidMethod(JNIEnv*, jobject, jmethodID, ...);
void CallVoidMethodV(JNIEnv*, jobject, jmethodID, va_list);
void CallVoidMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
CallNonvirtual<type>Method
NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);
NativeType CallNonvirtual<type>MethodA(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args);
NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args);
這三個(gè)操作族中的方法用于從本地函數(shù)中調(diào)用Java實(shí)例方法,它們的區(qū)別僅僅是傳參機(jī)制不同。
CallNonvirtual<type>Method族方法和Call<type Method族方法的區(qū)別在于:
Call<type>Method基于對象的類來調(diào)用方法,而CallNonvirtual<type>Method基于由clazz參數(shù)指定的類來調(diào)用方法,并從中獲取方法ID。方法ID必須從對象的真實(shí)類或其超類之一獲得。
jobject CallNonvirtualObjectMethod(JNIEnv*, jobject, jclass, jmethodID, ...);
jobject CallNonvirtualObjectMethodV(JNIEnv*, jobject, jclass, jmethodID, va_list);
jobject CallNonvirtualObjectMethodA(JNIEnv*, jobject, jclass, jmethodID, const jvalue*);
jboolean CallNonvirtualBooleanMethod(JNIEnv*, jobject, jclass, jmethodID, ...);
jboolean CallNonvirtualBooleanMethodV(JNIEnv*, jobject, jclass, jmethodID, va_list);
jboolean CallNonvirtualBooleanMethodA(JNIEnv*, jobject, jclass, jmethodID, const jvalue*);
jbyte CallNonvirtualByteMethod(JNIEnv*, jobject, jclass, jmethodID, ...);
jbyte CallNonvirtualByteMethodV(JNIEnv*, jobject, jclass, jmethodID, va_list);
jbyte CallNonvirtualByteMethodA(JNIEnv*, jobject, jclass, jmethodID, const jvalue*);
jchar CallNonvirtualCharMethod(JNIEnv*, jobject, jclass, jmethodID, ...);
jchar CallNonvirtualCharMethodV(JNIEnv*, jobject, jclass, jmethodID, va_list);
jchar CallNonvirtualCharMethodA(JNIEnv*, jobject, jclass, jmethodID, const jvalue*);
jshort CallNonvirtualShortMethod(JNIEnv*, jobject, jclass, jmethodID, ...);
jshort CallNonvirtualShortMethodV(JNIEnv*, jobject, jclass, jmethodID, va_list);
jshort CallNonvirtualShortMethodA(JNIEnv*, jobject, jclass, jmethodID, const jvalue*);
jint CallNonvirtualIntMethod(JNIEnv*, jobject, jclass, jmethodID, ...);
jint CallNonvirtualIntMethodV(JNIEnv*, jobject, jclass, jmethodID, va_list);
jint CallNonvirtualIntMethodA(JNIEnv*, jobject, jclass, jmethodID, const jvalue*);
jlong CallNonvirtualLongMethod(JNIEnv*, jobject, jclass, jmethodID, ...);
jlong CallNonvirtualLongMethodV(JNIEnv*, jobject, jclass, jmethodID, va_list);
jlong CallNonvirtualLongMethodA(JNIEnv*, jobject, jclass, jmethodID, const jvalue*);
jfloat CallNonvirtualFloatMethod(JNIEnv*, jobject, jclass, jmethodID, ...);
jfloat CallNonvirtualFloatMethodV(JNIEnv*, jobject, jclass, jmethodID, va_list);
jfloat CallNonvirtualFloatMethodA(JNIEnv*, jobject, jclass, jmethodID, const jvalue*);
jdouble CallNonvirtualDoubleMethod(JNIEnv*, jobject, jclass, jmethodID, ...);
jdouble CallNonvirtualDoubleMethodV(JNIEnv*, jobject, jclass, jmethodID, va_list);
jdouble CallNonvirtualDoubleMethodA(JNIEnv*, jobject, jclass, jmethodID, const jvalue*);
void CallNonvirtualVoidMethod(JNIEnv*, jobject, jclass, jmethodID, ...);
void CallNonvirtualVoidMethodV(JNIEnv*, jobject, jclass, jmethodID, va_list);
void CallNonvirtualVoidMethodA(JNIEnv*, jobject, jclass, jmethodID, const jvalue*);
6.4.3 對象的字段和方法使用實(shí)例
MainActivity.java:
public int age = 20;
public String getAge(String name) {
return "Hello " + name + ", I'm java method getAge";
}
public native void testObject();
native-lib.cpp:
extern "C" JNIEXPORT void JNICALL Java_com_qxt_myapp_MainActivity_testObject(JNIEnv *env, jobject thiz) {
jclass clazz = env->FindClass("com/qxt/myapp/MainActivity");
if (clazz != nullptr) {
//Access object field
jfieldID ageID = env->GetFieldID(clazz, "age", "I");
jint ageInt = (jint) env->GetIntField(thiz, ageID);
//Access object method
jmethodID getAgeID = env->GetMethodID(clazz, "getAge", "(Ljava/lang/String;)Ljava/lang/String;");
jstring nameStr = env->NewStringUTF("JNI");
jstring msgStr = (jstring) env->CallObjectMethod(thiz, getAgeID, nameStr);
//Use string, convert jstring to char sequence
char *name = (char *) env->GetStringUTFChars(nameStr, NULL);
char *msg = (char *) env->GetStringUTFChars(msgStr, NULL);
LOGD("[testObject] message:%s; age:%d", msg, ageInt);
env->ReleaseStringUTFChars(nameStr, name);
env->ReleaseStringUTFChars(msgStr, msg);
}
env->DeleteLocalRef(clazz);
}
6.5 調(diào)用靜態(tài)的字段和方法
6.5.1 調(diào)用靜態(tài)字段
GetStaticFieldID
/*
* 返回類的靜態(tài)字段的字段ID。該字段由其名稱和簽名指定。
* GetStatic<type>Field和SetStatic<type>Field系列函數(shù)使用字段ID檢索靜態(tài)字段。
* GetFieldID() 將使未初始化的類被初始化。
*
* @param env: JNI接口指針。
* @param clazz:一個(gè)Java類對象。
* @param name:字段名稱,以\0結(jié)尾的UTF-8字符串。
* @param sig:字段簽名,以\0結(jié)尾的UTF-8字符串。
* @return 返回字段ID,如果操作失敗返回NULL。
*/
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
GetStatic<type>Field
NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);
jobject GetStaticObjectField(JNIEnv*, jclass, jfieldID);
jboolean GetStaticBooleanField(JNIEnv*, jclass, jfieldID);
jbyte GetStaticByteField(JNIEnv*, jclass, jfieldID);
jchar GetStaticCharField(JNIEnv*, jclass, jfieldID);
jshort GetStaticShortField(JNIEnv*, jclass, jfieldID);
jint GetStaticIntField(JNIEnv*, jclass, jfieldID);
jlong GetStaticLongField(JNIEnv*, jclass, jfieldID);
jfloat GetStaticFloatField(JNIEnv*, jclass, jfieldID);
jdouble GetStaticDoubleField(JNIEnv*, jclass, jfieldID);
SetStatic<type>Field
void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);
void SetStaticObjectField(JNIEnv*, jclass, jfieldID, jobject);
void SetStaticBooleanField(JNIEnv*, jclass, jfieldID, jboolean);
void SetStaticByteField(JNIEnv*, jclass, jfieldID, jbyte);
void SetStaticCharField(JNIEnv*, jclass, jfieldID, jchar);
void SetStaticShortField(JNIEnv*, jclass, jfieldID, jshort);
void SetStaticIntField(JNIEnv*, jclass, jfieldID, jint);
void SetStaticLongField(JNIEnv*, jclass, jfieldID, jlong);
void SetStaticFloatField(JNIEnv*, jclass, jfieldID, jfloat);
void SetStaticDoubleField(JNIEnv*, jclass, jfieldID, jdouble);
6.5.2 調(diào)用靜態(tài)方法
GetStaticMethodID
/*
* 返回類或接口的靜態(tài)方法的方法ID。該方法由其名稱和簽名確定。
* 調(diào)用 GetStaticMethodID() 將使未初始化的類被初始化。
* 要獲取構(gòu)造函數(shù)的方法ID,請?zhí)峁?<init>作為方法名稱,并提供 void(V)作為返回類型。
*
* @param env: JNI接口指針。
* @param clazz:一個(gè)Java類對象。
* @param name:方法名稱,以\0結(jié)尾的UTF-8字符串。
* @param sig:方法簽名,以\0結(jié)尾的UTF-8字符串。
* @return 返回方法ID,如果操作失敗返回NULL。
*/
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
CallStatic<type>Method
NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);
NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
jobject CallStaticObjectMethod(JNIEnv*, jclass, jmethodID, ...);
jobject CallStaticObjectMethodV(JNIEnv*, jclass, jmethodID, va_list);
jobject CallStaticObjectMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jboolean CallStaticBooleanMethod(JNIEnv*, jclass, jmethodID, ...);
jboolean CallStaticBooleanMethodV(JNIEnv*, jclass, jmethodID, va_list);
jboolean CallStaticBooleanMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jbyte CallStaticByteMethod(JNIEnv*, jclass, jmethodID, ...);
jbyte CallStaticByteMethodV(JNIEnv*, jclass, jmethodID, va_list);
jbyte CallStaticByteMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jchar CallStaticCharMethod(JNIEnv*, jclass, jmethodID, ...);
jchar CallStaticCharMethodV(JNIEnv*, jclass, jmethodID, va_list);
jchar CallStaticCharMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jshort CallStaticShortMethod(JNIEnv*, jclass, jmethodID, ...);
jshort CallStaticShortMethodV(JNIEnv*, jclass, jmethodID, va_list);
jshort CallStaticShortMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jint CallStaticIntMethod(JNIEnv*, jclass, jmethodID, ...);
jint CallStaticIntMethodV(JNIEnv*, jclass, jmethodID, va_list);
jint CallStaticIntMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jlong CallStaticLongMethod(JNIEnv*, jclass, jmethodID, ...);
jlong CallStaticLongMethodV(JNIEnv*, jclass, jmethodID, va_list);
jlong CallStaticLongMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jfloat CallStaticFloatMethod(JNIEnv*, jclass, jmethodID, ...);
jfloat CallStaticFloatMethodV(JNIEnv*, jclass, jmethodID, va_list);
jfloat CallStaticFloatMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jdouble CallStaticDoubleMethod(JNIEnv*, jclass, jmethodID, ...);
jdouble CallStaticDoubleMethodV(JNIEnv*, jclass, jmethodID, va_list);
jdouble CallStaticDoubleMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
void CallStaticVoidMethod(JNIEnv*, jclass, jmethodID, ...);
void CallStaticVoidMethodV(JNIEnv*, jclass, jmethodID, va_list);
void CallStaticVoidMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
6.5.3 靜態(tài)的字段和方法使用實(shí)例
MainActivity.java:
public static String LOG_TAG = "MainActivity";
public static String getLogTag(String name) {
return "Hello " + name + ", I'm java static method getLogTag";
}
public native void testStatic();
native-lib.cpp:
extern "C" JNIEXPORT void JNICALL Java_com_qxt_myapp_MainActivity_testStatic(JNIEnv *env, jobject thiz) {
jclass clazz = env->FindClass("com/qxt/myapp/MainActivity");
if (clazz != nullptr) {
//Access static field
jfieldID logTagID = env->GetStaticFieldID(clazz, "LOG_TAG", "Ljava/lang/String;");
jstring logTagStr = (jstring) env->GetStaticObjectField(clazz, logTagID);
//Access static method
jmethodID getLogTagID = env->GetStaticMethodID(clazz, "getLogTag", "(Ljava/lang/String;)Ljava/lang/String;");
jstring nameStr = env->NewStringUTF("JNI");
jstring msgStr = (jstring) env->CallStaticObjectMethod(clazz, getLogTagID, nameStr);
//Use string, convert jstring to char sequence
char *logTag = (char *) env->GetStringUTFChars(logTagStr, NULL);
char *name = (char *) env->GetStringUTFChars(nameStr, NULL);
char *msg = (char *) env->GetStringUTFChars(msgStr, NULL);
LOGD("[testStatic] message:%s; logTag:%s", msg, logTag);
env->ReleaseStringUTFChars(logTagStr, logTag);
env->ReleaseStringUTFChars(nameStr, name);
env->ReleaseStringUTFChars(msgStr, msg);
}
env->DeleteLocalRef(clazz);
}
7. JNI的字符串
7.1 API介紹
NewString
/*
* 創(chuàng)建unicode字符串
*/
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize len);
GetStringLength
/*
* 獲取字符串長度
*/
jsize GetStringLength(JNIEnv *env, jstring string);
GetStringChars
/*
* 將字符串轉(zhuǎn)換成字符數(shù)組
*/
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
ReleaseStringChars
/*
* 釋放字符串
*/
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
GetStringChars和ReleaseStringChars通常成對使用。
GetStringRegion
/*
* 從字符串中的指定位置復(fù)制指定長度的字符到字符數(shù)組中
*/
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);
NewStringUTF
/*
* 創(chuàng)建UTF-8字符串
*/
jstring NewStringUTF(JNIEnv *env, const char *bytes);
GetStringUTFLength
/*
* 獲取UTF-8字符串長度
*/
jsize GetStringUTFLength(JNIEnv *env, jstring string);
GetStringUTFChars
/*
* 將UTF-8字符串轉(zhuǎn)換成字符數(shù)組
*/
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
ReleaseStringUTFChars
/*
* 釋放UTF-8字符串
*/
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
GetStringUTFChars和ReleaseStringUTFChars通常成對使用。
GetStringUTFRegion
/*
* 從UTF-8字符串中的指定位置復(fù)制指定長度的字符到字符數(shù)組中
*/
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);
7.2 字符串的使用實(shí)例
MainActivity.java:
public native String testString(String s);
native-lib.cpp:
extern "C" JNIEXPORT jstring JNICALL Java_com_qxt_myapp_MainActivity_testString(JNIEnv *env, jobject thiz, jstring s) {
//Get java string
char *msg = (char *) env->GetStringUTFChars(s, NULL);
std::string hello = msg;
hello.append("\n");
hello.append("Hello java");
env->ReleaseStringUTFChars(s, msg);
//New java string
return env->NewStringUTF(hello.c_str());
}
8. JNI的數(shù)組
8.1 基本類型數(shù)組
New<PrimitiveType>Array
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
jbooleanArray NewBooleanArray(JNIEnv*, jsize);
jbyteArray NewByteArray(JNIEnv*, jsize);
jcharArray NewCharArray(JNIEnv*, jsize);
jshortArray NewShortArray(JNIEnv*, jsize);
jintArray NewIntArray(JNIEnv*, jsize);
jlongArray NewLongArray(JNIEnv*, jsize);
jfloatArray NewFloatArray(JNIEnv*, jsize);
jdoubleArray NewDoubleArray(JNIEnv*, jsize);
Get<PrimitiveType>ArrayElements
NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
jboolean* GetBooleanArrayElements(JNIEnv*, jbooleanArray, jboolean*);
jbyte* GetByteArrayElements(JNIEnv*, jbyteArray, jboolean*);
jchar* GetCharArrayElements(JNIEnv*, jcharArray, jboolean*);
jshort* GetShortArrayElements(JNIEnv*, jshortArray, jboolean*);
jint* GetIntArrayElements(JNIEnv*, jintArray, jboolean*);
jlong* GetLongArrayElements(JNIEnv*, jlongArray, jboolean*);
jfloat* GetFloatArrayElements(JNIEnv*, jfloatArray, jboolean*);
jdouble* GetDoubleArrayElements(JNIEnv*, jdoubleArray, jboolean*);
Release<PrimitiveType>ArrayElements
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);
void ReleaseBooleanArrayElements(JNIEnv*, jbooleanArray, jboolean*, jint);
void ReleaseByteArrayElements(JNIEnv*, jbyteArray, jbyte*, jint);
void ReleaseCharArrayElements(JNIEnv*, jcharArray, jchar*, jint);
void ReleaseShortArrayElements(JNIEnv*, jshortArray, jshort*, jint);
void ReleaseIntArrayElements(JNIEnv*, jintArray, jint*, jint);
void ReleaseLongArrayElements(JNIEnv*, jlongArray, jlong*, jint);
void ReleaseFloatArrayElements(JNIEnv*, jfloatArray, jfloat*, jint);
void ReleaseDoubleArrayElements(JNIEnv*, jdoubleArray, jdouble*, jint);
Get<PrimitiveType>ArrayRegion
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf);
void GetBooleanArrayRegion(JNIEnv*, jbooleanArray, jsize, jsize, jboolean*);
void GetByteArrayRegion(JNIEnv*, jbyteArray, jsize, jsize, jbyte*);
void GetCharArrayRegion(JNIEnv*, jcharArray, jsize, jsize, jchar*);
void GetShortArrayRegion(JNIEnv*, jshortArray, jsize, jsize, jshort*);
void GetIntArrayRegion(JNIEnv*, jintArray, jsize, jsize, jint*);
void GetLongArrayRegion(JNIEnv*, jlongArray, jsize, jsize, jlong*);
void GetFloatArrayRegion(JNIEnv*, jfloatArray, jsize, jsize, jfloat*);
void GetDoubleArrayRegion(JNIEnv*, jdoubleArray, jsize, jsize, jdouble*);
Set<PrimitiveType>ArrayRegion
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, const NativeType *buf);
void SetBooleanArrayRegion(JNIEnv*, jbooleanArray, jsize, jsize, const jboolean*);
void SetByteArrayRegion(JNIEnv*, jbyteArray, jsize, jsize, const jbyte*);
void SetCharArrayRegion(JNIEnv*, jcharArray, jsize, jsize, const jchar*);
void SetShortArrayRegion(JNIEnv*, jshortArray, jsize, jsize, const jshort*);
void SetIntArrayRegion(JNIEnv*, jintArray, jsize, jsize, const jint*);
void SetLongArrayRegion(JNIEnv*, jlongArray, jsize, jsize, const jlong*);
void SetFloatArrayRegion(JNIEnv*, jfloatArray, jsize, jsize, const jfloat*);
void SetDoubleArrayRegion(JNIEnv*, jdoubleArray, jsize, jsize, const jdouble*);
8.2 引用類型數(shù)組
NewObjectArray
/*
* 構(gòu)造一個(gè)新的數(shù)組,它包含 elementClass 類的對象。數(shù)組中所有元素的初值都設(shè)置為initialElement。
*
* @param env: JNI接口指針。
* @param length:數(shù)組長度。
* @param elementClass:數(shù)組元素類。
* @param initialElement:數(shù)組元素初值。
* @return 返回Java數(shù)組對象,無法構(gòu)造數(shù)組則返回NULL。
*/
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
GetObjectArrayElement
/*
* 返回Object數(shù)組的元素。
*
* @param env: JNI接口指針。
* @param array:一個(gè)Java數(shù)組對象。
* @param index:數(shù)組索引。
* @return 返回一個(gè)Java對象。
*/
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
SetObjectArrayElement
/*
* 設(shè)置Object數(shù)組的元素。
*
* @param env: JNI接口指針。
* @param array:一個(gè)Java數(shù)組對象。
* @param index:數(shù)組索引。
* @param value:新值。
*/
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
8.3 獲取數(shù)組長度
GetArrayLength
/*
* 返回?cái)?shù)組中元素的數(shù)量。
*
* @param env: JNI接口指針。
* @param array:一個(gè)Java數(shù)組對象。
* @return 返回?cái)?shù)組的長度。
*/
jsize GetArrayLength(JNIEnv *env, jarray array);
8.4 數(shù)組的使用實(shí)例
MainActivity.java:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
int[] arr1 = {1, 1};
String[] arr2 = {"java value"};
String s = "result:" + Arrays.toString(testArray(arr1, arr2))
+ "\n" + "arr1:" + Arrays.toString(arr1)
+ "\n" + "arr2:" + Arrays.toString(arr2);
tv.setText(s);
}
public native int[] testArray(int[] arr1, String[] arr2);
native-lib.cpp:
extern "C" JNIEXPORT jintArray JNICALL Java_com_qxt_myapp_MainActivity_testArray(JNIEnv *env, jobject thiz, jintArray arr1, jobjectArray arr2) {
//Update primitive type array item
jint* _arr1 = env->GetIntArrayElements(arr1, NULL);
int length1 = env->GetArrayLength(arr1);
for (int i = 0; i < length1; i++) {
_arr1[i] = 2;
}
env->ReleaseIntArrayElements(arr1, _arr1, 0);
//Update object array
jstring _arr2 = (jstring) env->GetObjectArrayElement(arr2, 0);
const char* s = env->GetStringUTFChars(_arr2, NULL);
LOGD("[testArray] old arr2[0]:%s", s);
jstring newArr2 = env->NewStringUTF("JNI value");
env->SetObjectArrayElement(arr2, 0, newArr2);
//create new array
int array[2] = {3, 3};
jintArray dst = env->NewIntArray(2);
env->SetIntArrayRegion(dst, 0, 2, array);
return dst;
}
9. JNI的異常
9.1 API介紹
Throw
/*
* 拋出一個(gè) java.lang.Throwable對象。
*
* @param env: JNI接口指針。
* @param obj:java.lang.Throwable對象。
* @return 成功返回0,否則返回負(fù)數(shù)。
*/
jint Throw(JNIEnv *env, jthrowable obj);
ThrowNew
/*
* 使用指定的消息從指定的類構(gòu)造一個(gè)異常對象,并拋出該異常。
*
* @param env: JNI接口指針。
* @param clazz:java.lang.Throwable的子類
* @param message:用于構(gòu)造java.lang.Throwable對象的消息。該字符串以修改后的UTF-8編碼。
* @return 成功返回0,否則返回負(fù)數(shù)。
*/
jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
ExceptionOccurred
/*
* 確定是否引發(fā)異常。在本地代碼調(diào)用ExceptionClear()或Java代碼處理該異常之前,該異常將一直被拋出 。
*
* @param env: JNI接口指針。
* @return 返回當(dāng)前正在拋出的異常對象,如果當(dāng)前沒有拋出異常則返回NULL。
*/
jthrowable ExceptionOccurred(JNIEnv *env);
ExceptionDescribe
/*
* 將異常和堆棧的回溯打印到系統(tǒng)錯(cuò)誤報(bào)告通道,例如stderr。這是為調(diào)試提供的便利例程。
*
* @param env: JNI接口指針。
*/
void ExceptionDescribe(JNIEnv *env);
ExceptionClear
/*
* 清除當(dāng)前引發(fā)的任何異常。如果當(dāng)前未引發(fā)任何異常,則該工作不生效。
*
* @param env: JNI接口指針。
*/
void ExceptionClear(JNIEnv *env);
FatalError
/*
* 引發(fā)致命錯(cuò)誤,并且不希望VM恢復(fù)。此函數(shù)不返回。
*
* @param env: JNI接口指針。
* @param msg:錯(cuò)誤消息。該字符串以修改后的UTF-8編碼。
*/
void FatalError(JNIEnv *env, const char *msg);
ExceptionCheck
/*
* JNI提供的一種便利功能,可以檢查正在拋出的異常,而無需創(chuàng)建對異常對象的本地引用。
*
* @param env: JNI接口指針。
* @return 有正在拋出的異常時(shí)返回JNI_TRUE;否則返回JNI_FALSE。
*/
jboolean ExceptionCheck(JNIEnv *env);
9.2 異常使用實(shí)例
MainActivity.java:
public native void throwException();
native-lib.cpp:
extern "C" JNIEXPORT void JNICALL Java_com_qxt_myapp_MainActivity_throwException(JNIEnv *env, jobject thiz) {
jclass clazz = env->FindClass("java/lang/UnsupportedOperationException");
if (clazz != nullptr) {
env->ThrowNew(clazz, "Sorry, device is unsupported.");
}
env->DeleteLocalRef(clazz);
}
10. 注冊native方法
java 中的native方法和JNI中的本地函數(shù),需要建立起對應(yīng)關(guān)系才能正常調(diào)用。建立對應(yīng)關(guān)系的方式有兩種:
- 靜態(tài)注冊
- 動(dòng)態(tài)注冊
10.1 靜態(tài)注冊
本地函數(shù)靜態(tài)注冊的格式為:
extern "C" JNIEXPORT [JNI參數(shù)類型] JNICALL Java_[包名][類名][方法名](JNIEnv* env, jobject, [JNI參數(shù)類型,參數(shù)名])
其中,包名、類名、方法名之間的點(diǎn)用下劃線代替,包名、類名、方法名都必須與java文件中聲明的native方法完全一致,返回類型和參數(shù)類型,根據(jù)第三節(jié)的Java和JNI的參數(shù)對照表,一一對應(yīng)。從第4節(jié)中的代碼截圖可以看到,用Android Studio剛剛創(chuàng)建的這個(gè)項(xiàng)目,Android Studio已經(jīng)自動(dòng)為我們選擇了自動(dòng)靜態(tài)注冊的方式。
extern "C" JNIEXPORT jstring JNICALL Java_com_qxt_myapp_MainActivity_stringFromJNI(JNIEnv* env, jobject ) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
靜態(tài)注冊的缺點(diǎn):
編寫不方便,JNI 方法名字必須遵循固定的規(guī)則且名字很長,在包名和類名較長的情況下,會(huì)顯得非常惡心,對于有代碼規(guī)范強(qiáng)迫癥的人,這一點(diǎn)很難忍受。
程序運(yùn)行效率不高,首次調(diào)用native方法時(shí)需要根據(jù)方法名在JNI中查找對應(yīng)的本地函數(shù)并建立對應(yīng)關(guān)系,這個(gè)過程是比較耗時(shí)的。
靜態(tài)注冊的優(yōu)點(diǎn):
使用方便,本來按手動(dòng)流程,靜態(tài)注冊使用起來是很麻煩的,但是現(xiàn)在Android Studio已經(jīng)完美的幫我們解決了這個(gè)問題。現(xiàn)在靜態(tài)注冊使用起來非常方便,靜態(tài)注冊的native方法和本地函數(shù)之間可以像普通java方法一樣進(jìn)行跳轉(zhuǎn)查看。
10.2 動(dòng)態(tài)注冊
在調(diào)用 System.loadLibrary的加載庫文件時(shí),JNI會(huì)回調(diào)一個(gè)叫 JNI_OnLoad()的函數(shù),在JNI_OnLoad函數(shù)中做一些初始化相關(guān)的工作。JNI還提供一個(gè)叫RegisterNatives的函數(shù),用于注冊native方法。它的定義為:
jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
因此,我們可以在JNI_OnLoad()的函數(shù)中調(diào)用RegisterNatives函數(shù)注冊native方法。 這樣提前建立native方法和本地函數(shù)的對應(yīng)關(guān)系,優(yōu)化掉靜態(tài)注冊首次調(diào)用需要查找的耗時(shí)。動(dòng)態(tài)注冊整體流程如下:
- a. 編寫cpp實(shí)現(xiàn)JNI_Onload()方法。
- b. 將Java 方法和 C/C++方法通過簽名信息一一對應(yīng)起來,可以使用javap -s xx.class查看簽名信息。
- c. 使用類名和對應(yīng)起來的方法作為參數(shù),調(diào)用RegisterNatives函數(shù)注冊native方法。
這里已經(jīng)寫了一個(gè)比較標(biāo)準(zhǔn)的模板,可以直接拷貝使用,需要注冊新函數(shù)時(shí)只需要在nativeMethods數(shù)組中填寫相應(yīng)的native方法名、簽名信息、本地函數(shù)名即可。
jstring stringJNI(JNIEnv *env, jobject) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
static int registerNatives(JNIEnv *env) {
//要注冊的java類的路徑(完整的包名和類名)
const char *className = "com/qxt/myapp/MainActivity";
/*
* 要注冊的函數(shù)列表
* 參數(shù):
* 1.java中用native關(guān)鍵字聲明的函數(shù)名
* 2.函數(shù)簽名,格式:(參數(shù)類型)返回類型, 可以使用javap -s xx.class查看
* 3.C/C++中對應(yīng)函數(shù)的函數(shù)名(地址)
* */
const JNINativeMethod nativeMethods[] = {
{"stringFromJNI", "()Ljava/lang/String;", (void *) stringJNI},
};
jclass clazz = nullptr;
clazz = env->FindClass(className);
if (clazz == nullptr) {
return JNI_FALSE;
}
int methodsCount = sizeof(nativeMethods) / sizeof(nativeMethods[0]);
//注冊函數(shù) 參數(shù):java類名, 要注冊的函數(shù)數(shù)組 ,要注冊函數(shù)的數(shù)量
if (env->RegisterNatives(clazz, nativeMethods, methodsCount) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
assert(env != nullptr);
//registerNatives -> env->RegisterNatives
if (!registerNatives(env)) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
動(dòng)態(tài)注冊的缺點(diǎn):
- 使用不方便,與靜態(tài)注冊相對的,動(dòng)態(tài)注冊的本地函數(shù)目前在Android Studio中是無法進(jìn)行跳轉(zhuǎn)查看的,使用起來相對不方便。
動(dòng)態(tài)注冊的優(yōu)點(diǎn):
- 流程更加清晰可控。
- 效率更高,提前建立了對應(yīng)關(guān)系,首次調(diào)用無需查找。
11. cmake的簡單使用介紹
在介紹cmake的使用之前,我們先來回顧一下使用Makefile構(gòu)建和編譯C/C++源代碼,一般情況下我們需要在Android.mk文件中:
- 定義頭文件路徑。
- 定義依賴的庫文件路徑。
- 定義源代碼路徑。
- 定義相關(guān)的FLAG,ABI等等。(這里只簡單介紹cmake的使用,這項(xiàng)不展開講,有需要的可以去cmake官網(wǎng)看一下。)
- 定義需要鏈接的庫。
對應(yīng)到CMakeLists.txt也是一樣的套路,以下是一個(gè)比較常見的cmake使用的例子,包含導(dǎo)入頭文件、庫文件、鏈接庫文件等等:
#定義支持的cmake的最小版本
cmake_minimum_required(VERSION 3.4.1)
#導(dǎo)入的庫對應(yīng)的頭文件的路徑
include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/include)
#定義導(dǎo)入的庫
add_library(#庫名稱
opencv_java3
#庫的類型,可以為SHARED或者STATIC,根據(jù)導(dǎo)入的庫填寫,.so為SHARED, .a為STATIC
SHARED
#聲明是導(dǎo)入的
IMPORTED)
#定義導(dǎo)入的庫文件的路徑
set_target_properties(#庫名稱
opencv_java3
PROPERTIES
#庫路徑
IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libopencv_java3.so)
#定義要編譯的庫
add_library( #庫名稱
native-lib
#庫的類型,可以為SHARED或者STATIC,一般為SHARED
SHARED
#C/C++源文件,需要完整的路徑和名稱,源文件可以有多個(gè),每個(gè)以空格隔開
native-lib.cpp )
find_library( log-lib
log )
#鏈接
target_link_libraries( #編譯的庫
native-lib
#需要依賴的庫,可以有多個(gè),每個(gè)以空格隔開
opencv_java3
${log-lib} )
cmake就簡單介紹一下,我們只需要知道它是用來替代Makefile用來構(gòu)建和編譯C/C++源文件的,并且了解CMakeLists.txt的一般配置,應(yīng)付一般的JNI項(xiàng)目開發(fā)就完全沒有問題了。如果需要了解更多cmake的使用,請看官網(wǎng)教程:https://cmake.org/cmake/help/latest/guide/tutorial/index.html
12. 參考:
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html
https://blog.csdn.net/qq_20404903/article/details/80662316
https://www.oschina.net/p/android+ndk?hmsr=aladdin1e1
感謝幾位原作者辛勤付出。
歡迎交流、點(diǎn)贊、轉(zhuǎn)載,碼字不易,轉(zhuǎn)載請注明出處。