一.JNI 開(kāi)發(fā)的一般流程
在 windows 系統(tǒng)上面我們經(jīng)常能看到很多類(lèi)似于 xxx.dll 的文件锈玉,在做 android 開(kāi)發(fā)的時(shí)候我們能看到很多 xxx.so 的文件。這些都是啥呢芹壕?其實(shí)就是用 c 和 c++ 實(shí)現(xiàn)生成的動(dòng)態(tài)庫(kù)窟绷,供 windows 和 android 系統(tǒng)來(lái)調(diào)用锯玛。
我們解壓 QQ 和支付寶的 apk 找到它的 libs 目錄下,會(huì)發(fā)現(xiàn)有大量的 .so 庫(kù)文件兼蜈,還有很多是動(dòng)態(tài)下載加載的我們看不到攘残,能看到的就已經(jīng)有好幾百個(gè)了。為什么要這么弄饭尝?直接就用 java 開(kāi)發(fā)不行嗎肯腕?其實(shí)好處有很多,比如安全钥平,高效实撒,跨平臺(tái)等等。今天我們就來(lái)看下 JNI 開(kāi)發(fā)的一般開(kāi)發(fā)流程涉瘾。
1.1 編寫(xiě) native 方法
public class NdkTest {
public static void main(String[] args) {
NdkTest ndkTest = new NdkTest();
System.out.println("簽名密鑰:"+ndkTest.getSignaturePassword());
}
// 獲取簽名密鑰
public native String getSignaturePassword();
static{
// 加載某個(gè)路徑下的動(dòng)態(tài)庫(kù)
System.load("C:/Users/hcDarren/Desktop/android/NDK/NDK_Day12/x64/Debug/NDK_Day12.dll");
}
}
1.2 生成 xxx.h 頭文件
javah -d ../jni -jni com.darren.ndk12.NdkTest
1.3 VS 編寫(xiě)實(shí)現(xiàn)方法生成 dll 動(dòng)態(tài)庫(kù)
// 引入頭文件
#include "com_darren_ndk12_NdkTest.h"
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkTest_getSignaturePassword
(JNIEnv *env, jobject jobj){
return (*env)->NewStringUTF(env,"940223");
}
二.詳解 .h 頭文件和實(shí)現(xiàn)文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h" // 引入頭 jni.h 文件
/* Header for class com_darren_ndk12_NdkTest */
// 打個(gè)標(biāo)記知态,防止反復(fù)引入 copy 內(nèi)容
#ifndef _Included_com_darren_ndk12_NdkTest
#define _Included_com_darren_ndk12_NdkTest
#ifdef __cplusplus
// 如果是 c++ 則統(tǒng)一用 C 的編譯方式
// 會(huì)指示編譯器這部分代碼按C語(yǔ)言的進(jìn)行編譯,而不是C++的立叛。
// C語(yǔ)言并不支持函數(shù)重載负敏,因此編譯C語(yǔ)言代碼的函數(shù)時(shí)不會(huì)帶上函數(shù)的參數(shù)類(lèi)型,一般只包括函數(shù)名秘蛇。
extern "C" {
#endif
/*
* Class: com_darren_ndk12_NdkTest
* Method: getSignaturePassword
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkTest_getSignaturePassword
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
// 引入頭文件
#include "com_darren_ndk12_NdkTest.h"
// JNIEXPORT:在Jni編程中所有本地語(yǔ)言實(shí)現(xiàn)Jni接口的一個(gè)標(biāo)志
// jstring:對(duì)應(yīng) java 中的數(shù)據(jù)類(lèi)型 String
// JNICALL:也是一個(gè)標(biāo)記可以去掉其做,編譯運(yùn)行也不會(huì)有問(wèn)題
// JNIEnv:c 與 java 相互調(diào)用的橋梁顶考,它提供了很多函數(shù)方法
// jobj:java 傳遞下來(lái)的對(duì)象,即上面的 NdkTest
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkTest_getSignaturePassword
(JNIEnv *env, jobject jobj){
// 通過(guò) JNIEnv 把 c 字符串轉(zhuǎn)為 jstring
return (*env)->NewStringUTF(env,"940223");
}
三.JNIEnv 的實(shí)現(xiàn)原理
JNI 的基礎(chǔ)學(xué)習(xí)我們主要搞清 JNIEnv 就可以了妖泄,只要熟悉它里面的函數(shù)方法驹沿。但是學(xué)習(xí)的時(shí)候肯定不光要搞清它的方法函數(shù),還需要搞清它的實(shí)現(xiàn)原理蹈胡。如果有留意我之前寫(xiě)的一些文章你會(huì)發(fā)現(xiàn) c 和 c++ 有所不同渊季,c++ 是這樣的。
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkTest_getSignaturePassword
(JNIEnv *env, jobject jobj){
return env -> NewStringUTF("940223");
}
為什么會(huì)有這樣的不同罚渐?c++ 的實(shí)現(xiàn)方式我們后面的文章再去詳細(xì)講解却汉,我們先來(lái)模擬一下 c 的實(shí)現(xiàn)方式:
// 引入頭文件
#include <stdio.h>
// 定義結(jié)構(gòu)體指針別名
typedef const struct JNINativeInterface *JNIEnv;
// 定義一個(gè)結(jié)構(gòu)體
struct JNINativeInterface{
// 定義的不是函數(shù),函數(shù)指針
char*(*NewStringUTF)(JNIEnv*, char*);
};
// 只是做一個(gè)模擬
char* Java_com_darren_ndk12_NdkTest_getSignaturePassword(JNIEnv *env){
return (*env)->NewStringUTF(env, "940223");
}
// NewStringUTF 方法的實(shí)現(xiàn)
char* NewStringUTF(JNIEnv* jniEnv, char* str){
// 一連串的實(shí)現(xiàn) char* -> jstring
return str;
}
void main(){
// 中間還會(huì)有很多的流程荷并,我們能看到的就是調(diào)用 Java_com_darren_ndk12_NdkTest_getSignaturePassword
// 模擬 JNIEnv 創(chuàng)建過(guò)程
struct JNINativeInterface nativeInterface;
nativeInterface.NewStringUTF = NewStringUTF;
JNIEnv jniEnv = &nativeInterface;// 一級(jí)指針
// Java_com_darren_ndk12_NdkTest_getSignaturePassword 參數(shù)需要的是 JNIEnv*
JNIEnv* env = &jniEnv;// 雖然只有一個(gè) * 合砂,但是其實(shí)他是一個(gè)二級(jí)指針
char* singnature = Java_com_darren_ndk12_NdkTest_getSignaturePassword(env);
printf("singnature = %s", singnature);
// 然后將 jstring 返回給 java
getchar();
}
視頻鏈接:https://pan.baidu.com/s/1vyxCSn0SWo3-YnoD7Rzryw
視頻密碼:uqmc