Android系統加載JNI Lib的方式
要想在Java中調用C的函數,必然要有一定的規則去映射二者的函數名,也就是加載JNI庫的方式,下面介紹這兩種方式。
JNI_OnLoad
當Android的VM(Virtual Machine)執行到C組件(即*so)裏的System.loadLibrary()函數時,
首先會去執行C組件裏的JNI_OnLoad()函數。這種方法有兩個優點,1. 可以通知VM此時native使用哪個版本的JNI,默認是最低版本;2. 使得native層在被加載的時候做一些初始化工作。
JNI_OnLoad()中聲明的JNI函數在進程空間中的起始地址被保存在ClassObject->directMethods中,也就是說在加載so的時候就記錄下來了JNI函數的地址,等到使用的時候直接從特定地址執行相關函數就可以了。
在JNI_OnLoad
函數中最重要的事就是調用RegisterNatives
函數完成動態庫中JNI函數的註冊,jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
這個函數的第二個參數JNINativeMethod
是如下的結構體:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
name
爲java中定義的native函數的函數名,signature
爲函數的簽名,fnPtr
爲對應的native C函數的函數指針。
函數簽名查看方法,比如有Example類,先編譯javac Example.java
,然後使用命令javap -s -p Example.class
可以查看函數和成員變量的簽名。
示例:JNI_OnLoad
假設Java中有 public class JniManager { public native String nGetStudentInfo(); }
方法,native C中定義的對應的函數爲jstring jniGetStudentInfo(JNIEnv *env, jobject object)
,具體的JNI_OnLoad
代碼如下所示:
static const JNINativeMethod gMethods[] = {
{"nGetStudentInfo", "()Ljava/lang/String", (void *) jniGetStudentInfo},
{"nHello", "()V", (void*) jniHello}
};
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return result;
}
jclass clazz = env->FindClass("com/lmshao/jniexample/JniManager");
if (clazz == NULL) {
return result;
}
jint count = sizeof(gMethods) / sizeof(gMethods[0]);
if (env->RegisterNatives(clazz, gMethods, count) != JNI_OK) {
return result;
}
result = JNI_VERSION_1_6;
return result;
}
JNIEXPORT
和JNICALL
爲jni.h中的宏定義,
#define JNIEXPORT __attribute__ ((visibility ("default")))
,JNIEXPORT
用來導出動態庫的函數符號,JNICALL
暫時定義爲空。
dvmResolveNativeMethod延遲解析機制
如果JNI Lib中沒有JNI_OnLoad函數,即在執行System.loadLibrary時,
無法把此JNI Lib實現的函數在進程中的地址增加到ClassObject->directMethods。則直到需要調用的時候纔會解析這些javah風格(包名+類名+方法名)的函數。這種方法是Android Studio默認的native工程使用的方法。
示例:
比如Java裏面MainActivity類中有個方法聲明爲public native String stringFromJNI();
,則Native中對應的函數名字應該是jstring Java_com_example_demo_MainActivity_stringFromJNI(){}
。
雖然這種方法是Android Studio默認Native工程使用的方法,但是因爲種種原因在實際Native開發中很少使用。
示例
Android Studio 3新建一個Native工程,默認有如下示例函數。
MainActivity.java
public class MainActivity extends AppCompatActivity {
// 加載動態庫
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());
}
// Native方法
public native String stringFromJNI();
}
native-lib.cpp
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv *env, jobject) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
參考:
- https://blog.csdn.net/fireroll/article/details/50102009
簽名
JNI簽名是使用一些縮寫符號來代表參數類型,這些符號由Java語言規定的。
數據類型的簽名
Java基本數據類型的簽名如下表所示:
簽名 | Java類型 | JNI類型 |
---|---|---|
Z | boolean | jboolean |
C | char | jcahr |
B | byte | jbyte |
S | short | jshort |
I | int | jint |
J | long | jlong |
F | float | jfloat |
D | double | jdouble |
[B | byte[ ] | jbyteArray |
[I | int[ ] | jintArray |
… | … | … |
Java複雜類型簽名格式是:“L”+“全限定類名”+“;” 。例如String
類型簽名爲Ljava/lang/String;
,對應的JNI類型爲jstring
。其餘的複雜數據類型對應的JNI類型都是jobject
,jstring本質上也是jobejct類型,因爲使用頻率高,所以又單獨定義方便使用。
函數參數簽名
函數的參數簽名由參數和返回值組成,參數用一對小括號包起來,即使參數爲空也要使用空括號,括號後面是返回值類型,如果沒有返回值則用字母V表示。
如:(I)V
表示參數爲int,無返回值。()I
表示參數爲空,返回值爲int。([IZ)I
表示參數爲int[]和boolean,返回值爲int。
參考:
劉超. 深入理解Android 5.0系統[M]. 人民郵電出版社, 2015.