What is JNI
JNI是Java Native Interface的縮寫,主要是提供了一系列API,讓你能在其它語言中寫Java。
What JNI can bring us
JNI最大的好處就是,額,Java你懂的,跑在JVM里面,雖然有著一處編譯,到處運行的優勢(,方便啊),但是它的效率。。。至少相對于c和C艸來說,比較低下,而且,正是由于這個能一處編譯,到處運行的原因,Java極容易被反編譯。Java中一般用的加密方式就是混淆了,然而其實并沒有太大的作用。你還是開源吧。。。因為不開源也會被反編譯的。。。
PS:并沒有貶低Java的意思,個人還是挺喜歡用Java的
然后,相反的,JNI由于是用C或者C艸寫,效率很高,可以用來處理一些底層的東西,比如解碼或者TCP/IP有關的。編譯過后跟C(艸)編譯的結果是一樣的,在Android里面是.so文件。然后,因為是C(艸),所以需要針對不同的平臺,不同的處理器進行編譯。所以,使用JNI,你需要在編譯的時候生成許多個平臺的版本,否則,Java跨平臺這個優點相當于直接被廢了。還有就是JNI的調試會非常蛋疼。
How to use JNI
Hello World
我用的Android Studio,有各種一鍵生成(x),要看手擼的話,網上應該能搜到,本文主要是介紹那些遇到的坑。
AS生成的main.cpp長這樣:
#include <jni.h>
#include <string>
extern "C"
jstring
Java_com_helloworld_jnidemo_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
分析一下:
- 幾個include,其中jni.h是JNI必需的,其他的可以添加C(艸)中的,比如
stdio.h
什么的 -
extern C
,這個我也不是特別理解,自我修養里面說是聲明為C語言,然而刪掉過后就炸了 -
jstring
,返回值類型 -
Java_com_helloworld_jnidemo_MainActivity_stringFromJNI
,Java_包名類名方法名,這是函數聲明的規范 -
JNIEnv *env, jobject /* this */
,JNIEnv里面有巨量的函數,后面就知道了,jobject就是this -
std::string hello = "Hello from C++";
,C艸 -
env->NewStringUTF(hello.c_str())
,這兒就出現了env的其中一個函數,這個函數會經常在后面用到,char*轉String,沒錯,他們不一樣!
然后我自己寫了一個HelloWordl和求和的函數:
extern "C"
jstring
Java_com_helloworld_jnidemo_MainActivity_helloworld(JNIEnv *env, jobject /* this */) {
return env->NewStringUTF("Hello World");
}
extern "C"
jint
Java_com_helloworld_jnidemo_MainActivity_sum(JNIEnv *env, jobject /* this */, jint a, jint b) {
return a + b;
}
Java中該這樣寫:
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
public native String helloworld();
其中,System.loadLibrary("native-lib");
這句是加載庫,static語句塊中的內容只會被執行一次。native-lib
為庫的名稱,聲明方法時使用native
關鍵字。
CMakeLists.txt:
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp )
find_library(
log-lib
log )
target_link_libraries(
native-lib
${log-lib} )
其中,native-lib
可以隨便改,對應System.loadLibrary("native-lib");
里面的。但是有個玄學問題,不能改成test。。。被坑了。。。
src/main/cpp/native-lib.cpp
里面的文件名可以隨便改,只要與你寫的文件對應。
好的,JNI入門了的樣子。
Learn More
寫出來了Hello World,該繼續深入研究了。在繼續之前,我們還應該了解一下jstring
,jint
這些是啥,這兒有個表,展示了JNI和Java里面的屬性的關系:
- jint --> int
- jbyte --> byte
- jshort --> short
- jlong --> long
- jfloat --> float
- jdouble --> double
- jchar --> char
- jboolean --> boolean
- jclass --> java.lang.Class
- jstring --> java.lang.String
- jarray --> Array
- jxxxArray --> xxx[]
- jobject --> Object
- ...
注意最后一個,一切皆為對象。
使用JNI,你應該實現Java的基本功能:
- new對象
- call方法
- 獲取屬性
學會了以上三個操作,就可以用JNI代替Java中70%以上的操作了。讓我們一個一個來看。
new對象 & Call方法
沒錯,new對象就是通過調用構造方法實現的。
extern "C"
jobject
Java_com_helloworld_asdf_MainActivity_newObject(JNIEnv *env, jobject /* this */) {
jclass clazz = env->FindClass("java/lang/Object");
jmethodID init = env->GetMethodID(clazz, "<init>", "()V");
jobject result = env->NewObject(clazz, init);
return result;
}
步驟:
- 找到class,用/代替.,
FindClass
的參數為所在包名 - 找到對應構造方法
- 調用
newObject
,傳入class和構造方法id。
再看看一般的方法調用:
extern "C"
jint
Java_com_helloworld_asdf_MainActivity_stringLen(JNIEnv *env, jobject /* this */, jstring str) {
jclass clazz = env->GetObjectClass(str);
jmethodID lenId = env->GetMethodID(clazz, "length", "()I");
jint result = env->CallIntMethod(str, lenId);
return result;
}
GetObjectClass
可以直接從object中拿到class。
調用方法用CallxxxMethod
,xxx為返回值類型。CallxxxMethod的第一個參數是jobject,不是jclass,這個與NewObject
不同。前面有jxxxArray,然而并沒有CallxxxArrayMethod
哎,該怎么辦呢?一切都是對象,用CallObjectMethod
再強轉就可以了。
比如:
extern "C"
jstring
Java_com_helloworld_asdf_MainActivity_toString(JNIEnv *env, jobject /* this */, jobject object) {
jclass clazz = env->GetObjectClass(object);
jmethodID lenId = env->GetMethodID(clazz, "toString", "()Ljava/lang/String;");
jstring result = (jstring) env->CallObjectMethod(object, lenId);
return result;
}
方法簽名:
簡直有毒,反人類
- construction --> <init>
- void --> V
- boolean --> Z
- byte --> B
- char --> C
- short --> S
- int --> I
- long --> J
- float --> F
- double --> D
- x[] --> [x
- x[][] --> [[x
- java.lang.String --> L/java/lang/String;
總結一下:
- 每個基本類型都有對應的簽名,基本法
- 數組用[
- 構造方法規定為<init>
- 其它類為L類;,注意:分號不能丟,分號不能丟,分號不能丟
獲取Field
extern "C"
jint
Java_com_helloworld_asdf_MainActivity_getX(JNIEnv *env, jobject /* this */, jobject test) {
jclass clazz = env->GetObjectClass(test);
jfieldID lenId = env->GetFieldID(clazz, "x", "I");
jint result = env->GetIntField(test, lenId);
return result;
}
static
static
的屬性和方法與普通的有一些區別,例如CallStaticObjectMethod
的第一個參數是jclass
。這些在熟悉了上面的操作過后都沒有太大的問題了。
分享一點經驗
- 一切都是object
- Java里的String和C(艸)里的是不一樣的,要記得
NewStringUTF
,被坑過 L/java/lang/String;
-
java/util/List
和java/util/ArrayList
是不一樣的。。。要看清方法的參數。。。