什么是JNI
百度百科:https://baike.baidu.com/item/JNI/9412164
當(dāng)然,里面也有關(guān)于JNI的實(shí)現(xiàn),懂的看官就不必往下看啦,大致上差不多。
開發(fā)環(huán)境
- Mac電腦
- IntelliJ IDEA(寫java代碼)
- Clion(寫C/C++代碼)
- 配置好java運(yùn)行環(huán)境
開始寫代碼
第一步,在IDEA里創(chuàng)建一個(gè)java工程,創(chuàng)建一個(gè)java類
Register.java
public class Register {
public native void staticReg();
public static void main(String[] args) {
Register register = new Register();
register.staticReg();
}
}
說明:public native void staticReg();native修飾的方法就是C方法,相當(dāng)于這里定義一個(gè)接口,讓C代碼去實(shí)現(xiàn)。
為了讓C代碼可以知道這個(gè)接口,我們需要進(jìn)行以下兩步操作:
1. 使用javac Register.java命令,把java類編譯成.class文件
注意:需要在Register.java文件的目錄下執(zhí)行該命令。
生成的.class文件內(nèi)容如下:
Register.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Register {
public Register() {
}
public native void staticReg();
public static void main(String[] var0) {
Register var1 = new Register();
var1.staticReg();
}
看起來(lái)跟java代碼差不多
2. 使用javah Register命令,根據(jù)剛才生成的.class文件,生成一個(gè).h頭文件
注意:javah 命令后面的Register是不帶后綴的
Register.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Register */
#ifndef _Included_Register
#define _Included_Register
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Register
* Method: staticReg
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT void JNICALL Java_Register_staticReg
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
可以看到,我們從最開始的java文件,生成了最終的.h頭文件。了解C語(yǔ)言的同學(xué)大概已經(jīng)猜到了,這個(gè)頭文件里定義了一個(gè)java文件里public native void staticReg()對(duì)應(yīng)的方法Java_Register_staticReg (JNIEnv *, jobject)。
既然已經(jīng)有了C的頭文件,我們是不是在我們的C的library項(xiàng)目里引用這個(gè)頭文件,并實(shí)現(xiàn)其中的方法,就可以生成一個(gè)動(dòng)態(tài)庫(kù)了呢?
說干就干 ~
第二步,在Clion里創(chuàng)建一個(gè)C++ Library工程register

創(chuàng)建完成之后的目錄結(jié)構(gòu)是這樣子的

可以看到,創(chuàng)建的時(shí)候會(huì)默認(rèn)生成一個(gè)
library.h和一個(gè)library.cpp文件。這里我們是要自己引入.h頭文件、再實(shí)現(xiàn)這個(gè)頭文件的方法,所以可以刪掉這個(gè)這兩個(gè)文件。
現(xiàn)在我們找到上面生成的.h頭文件,復(fù)制粘貼到libaray目錄下,
然后修改CMakeLists.txt文件

切換到Register.h文件,發(fā)現(xiàn)有報(bào)錯(cuò)

那么這個(gè)依賴在哪里呢?我們需要找到依賴的.h文件,復(fù)制到libaray項(xiàng)目下面。
這個(gè)jni.h在jdk里面/Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home/include/jni.h
復(fù)制完之后,看到還報(bào)錯(cuò),需要把尖括號(hào)改成雙引號(hào)


這里需要再引入一個(gè)頭文件jni_md.h(jni.h里引用了這個(gè)),目錄/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/jni_md.h(看官可自行搜索)
把jni_md.h,jni.h都復(fù)制粘貼之后,可以看到Register.h文件就已經(jīng)不報(bào)錯(cuò)了。
然后創(chuàng)建一個(gè)Register.cpp文件,并引用Register.h頭文件,實(shí)現(xiàn)里面的方法
Register.cpp
//
// Created by wh on 2019/9/27.
//
#include "Register.h" //引用頭文件
/*
* Class: Register
* Method: staticReg
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT void JNICALL Java_Register_staticReg
(JNIEnv *env, jobject obj) {
printf("this is my first jni program"); //實(shí)現(xiàn)頭文件中定義的方法,打印一個(gè)字符串
}
到這里,代碼基本上寫完了。
現(xiàn)在我們把Libaray工程build一下,可以看到,生成了一個(gè)libregister.dylib文件:

這個(gè)文件就是生成的動(dòng)態(tài)庫(kù)了。
引用動(dòng)態(tài)庫(kù)
我們復(fù)制libregister.dylib的絕對(duì)路徑,切回到j(luò)ava工程,修改Register.java類
public class Register {
//靜態(tài)加載庫(kù)文件
static {
System.load("/Users/wh/CLionProjects/register/cmake-build-debug/libregister.dylib");
}
public native void staticReg();
public static void main(String[] args) {
Register register = new Register();
register.staticReg();
}
}
運(yùn)行一下main函數(shù):

問題
Android開發(fā)平時(shí)用到的動(dòng)態(tài)庫(kù)不是
.so結(jié)尾的嗎,.dylib是什么鬼?什么是靜態(tài)注冊(cè)?什么是動(dòng)態(tài)注冊(cè)?
C/C++中如何調(diào)用java代碼?
jni在編寫和使用的過程中,有什么需要特別注意的地方嗎?
待續(xù)...
- 本人是個(gè)初學(xué)者,本篇文章記錄了自已編寫代碼的過程以及思路,可能有些模棱兩可的情況,歡迎批評(píng)指正。
- 上面只說的都只是JNI的靜態(tài)注冊(cè),計(jì)劃后續(xù)更新動(dòng)態(tài)注冊(cè)代碼編寫過程。