JNI原理分析
用法很比較簡單。1.編寫java文件,使用關鍵字native 2.編寫頭文件。3實現C++代碼。4編譯出SO 5.集成調用。
1.編寫JAVA
創建Java文件:
package com.zx.testjni;
public class JNITest {
static{
System.loadLibrary("testjni");
}
public static native int add (int a,int b);
public static native String sayHello (String name);
}
2.編譯頭文件
到此目錄中 按住shift 右擊鼠標 選擇 在此處打開窗口命令 m
輸入javah 生成頭文件
3.編寫C/C++文件
新建jni目錄
拷貝 頭文件,新建 Android.mk 和Application.mk文件 和c文件
Android.mk內容:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := testjni
LOCAL_SRC_FILES := com_zx_testjni_JNITest.c
include $(BUILD_SHARED_LIBRARY)
Application.mk內容
APP_PLATFORM := android-17
頭文件內容:
/* DO NOT EDIT THIS FILE - it is machine generated /
#include <jni.h>
/ Header for class com_zx_testjni_JNITest */
#ifndef _Included_com_zx_testjni_JNITest
#define _Included_com_zx_testjni_JNITest
#ifdef __cplusplus
extern “C” {
#endif
/*
- Class: com_zx_testjni_JNITest
- Method: add
- Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_zx_testjni_JNITest_add
(JNIEnv *, jclass, jint, jint);
/*
- Class: com_zx_testjni_JNITest
- Method: sayHello
- Signature: (Ljava/lang/String;)V
*/
JNIEXPORT jstring JNICALL Java_com_zx_testjni_JNITest_sayHello
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
C文件內容:
#include “com_zx_testjni_JNITest.h”
#include <stdio.h>
#include <string.h>
JNIEXPORT jint JNICALL Java_com_zx_testjni_JNITest_add
(JNIEnv *env, jclass object, jint a, jint b){
return a+b;
}
JNIEXPORT jstring JNICALL Java_com_zx_testjni_JNITest_sayHello
(JNIEnv *env, jclass object, jstring name){
// char str1[6] = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’};
// char str3[22];
// strcat( str3, str1);
// strcat( str3, name);
return (*env)->NewStringUTF(env,“hello i am jni”);
}
4編譯SO文件
找到jni文件的目錄,打開 cmd 輸入 ndk-build ,如果沒配置path 就如上圖 輸入全路徑。
′f5刷新一下項目,會看到生成的文件
5調用
結果:
主要其實是C文件的編寫是重點。 還是調用的原理是重點。
JNI是java native interface 。可以讓Java代碼調用到 native層方法。一般是C和C++。
生成頭文件
比如我們有個源文件在:E:\com\zx\TestNative.java
第一種:直接cd到E:\目錄 按住shift 點擊右鍵 打開CMD 然後使用:javah com.zx.TestNative,其中javah後面的是需要生成頭文件類的全路徑(包名+類名),在當前目錄就會生成com_zx_TestNative.h 頭文件。
第二種:任意目錄 打開CMD,使用如下命令:javah -classpath E:\ com.zx.TestNative 注意有空格。
其中-classpath後跟當前程序在磁盤上的位置,該位置只寫到package com.zx;前一級目錄就行了,後面是需要生成頭文件類的全路徑。就會在當前的目錄生成com_zx_TestNative.h頭文件。
頭文件詳解:
/* DO NOT EDIT THIS FILE - it is machine generated /
#include <jni.h>
/ Header for class com_zx_astudyandroidclient_TestNative */
#ifndef _Included_com_zx_astudyandroidclient_TestNative
#define _Included_com_zx_astudyandroidclient_TestNative
#ifdef __cplusplus
extern “C” {
#endif
/*
- Class: com_zx_astudyandroidclient_TestNative
- Method: add
- Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_zx_astudyandroidclient_TestNative_add
(JNIEnv *, jclass, jint, jint);
/*
- Class: com_zx_astudyandroidclient_TestNative
- Method: reduce
- Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_zx_astudyandroidclient_TestNative_reduce
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
編寫 C++代碼:
/* DO NOT EDIT THIS FILE - it is machine generated /
#include “com_zx_TestNative.h”
#include
// 必須的頭文件
#include
using namespace std;
/
- Class: com_zx_TestNative
- Method: add
- Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_zx_TestNative_add
(JNIEnv *env, jclass object, jint a, jint b);
{
cout << "Java_com_zx_TestNative_add : " << endl;
return a+b;
}
/*
- Class: com_zx_TestNative
- Method: reduce
- Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_zx_TestNative_reduce
(JNIEnv *env, jclass object, jint a, jint b);
{
cout << "Java_com_zx_TestNative_reduce : " << endl;
return a-b;
靜態配置的缺點:
1.需要編譯所有生命了native函數的Java類,每個所生成的class文件都得用javah生成一個頭文件。
2.javah生成的JNI層函數名特別長,書寫起來很不方便。
3.初次調用native函數時要根據函數名字搜索對應的JNI層函數來建立關聯關係,這樣會影響運行效率。
動態註冊:
1.java文件:
public class JNITest {
static{
Log.d("ZX", "loadLibrary===");
System.loadLibrary("testjni");
}
public static native int add (int a,int b);
public static native int reduce (int a,int b);
public static native String sayHello (String name);
}
C++文件:
#include “com_zx_testjni_JNITest.h”
JNIEXPORT jint JNICALL add
(JNIEnv *env, jclass object, jint a, jint b){
LOGE(“JNICALL add\n”);
return a+b;
}
JNIEXPORT jint JNICALL reduce
(JNIEnv *env, jclass object, jint a, jint b){
LOGE(“JNICALL reduce\n”);
return a-b;
}
JNIEXPORT jstring JNICALL sayHello
(JNIEnv *env, jclass object, jstring name){
LOGE(“JNICALL sayHello\n”);
return env->NewStringUTF(“I am for C++”);
}
static JNINativeMethod gMethods[] = {
{"add", "(II)I", (void *)add},
{"reduce", "(II)I", (void *)reduce},
{"sayHello", "(Ljava/lang/String;)Ljava/lang/String;", (void *)sayHello},
};
//此函數通過調用RegisterNatives方法來註冊我們的函數
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods,int methodsNum){
LOGE("registerNativeMethods %d",methodsNum);
jclass clazz;
//找到聲明native方法的類
clazz = env->FindClass(className);
if(clazz == NULL){
LOGE("find class err\n");
return JNI_FALSE;
}
LOGE("start RegisterNatives\n");
//註冊函數 參數:java類 所要註冊的函數數組 註冊函數的個數
int result=env->RegisterNatives(clazz,gMethods,methodsNum);
LOGI("RegisterNatives result %d",result);
if(result < 0){
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env){
LOGE("registerNatives-----");
const char* className = "com/zx/testjni/JNITest";
return registerNativeMethods(env,className,gMethods, sizeof(gMethods)/ sizeof(gMethods[0]));
}
//可以在該函數中進行動態註冊JNI
JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *reserved){
LOGE(“jni_load JNI_OnLoad\n”);
JNIEnv* env = NULL;//定義JNI Env
jint result = -1;
/*JavaVM::GetEnv 原型爲 jint (*GetEnv)(JavaVM*, void**, jint);
*/
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("GetEnv failed!");
return result;
}
LOGI("loading . . .");
LOGE("register--------");
/*開始註冊
* 傳入參數是JNI env
* 以registerNatives(env)爲例說明
*/
if(registerNatives(env) != JNI_TRUE) {
LOGE("can't load registerNatives");
goto end;
}
result = JNI_VERSION_1_4;
end:
return result;
}
一定要注意:這裏是有分號結尾的。
這裏就是把Java的方法和native方法對應起來。
這個方法名要寫對,否則找不到對應的 java 類中的方法。
由於增加了Android打印:所以需要再Android.mk文件增加引用
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -llog
LOCAL_MODULE := testjni
LOCAL_SRC_FILES := com_zx_testjni_JNITest.cpp
include $(BUILD_SHARED_LIBRARY)
原理分析
1首先分析頭文件:
/* DO NOT EDIT THIS FILE - it is machine generated /
#include <jni.h>
/ Header for class com_zx_testjni_JNITest */
#ifndef _Included_com_zx_testjni_JNITest
#define _Included_com_zx_testjni_JNITest
#ifdef __cplusplus
extern “C” {
#endif
/*
- Class: com_zx_testjni_JNITest
- Method: add
- Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_zx_testjni_JNITest_add
(JNIEnv *, jclass, jint, jint);
/*
- Class: com_zx_testjni_JNITest
- Method: sayHello
- Signature: (Ljava/lang/String;)V
*/
JNIEXPORT jstring JNICALL Java_com_zx_testjni_JNITest_sayHello
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
這裏的JNIEXPORT和JNICALL都是JNI的關鍵字,表示此函數是被JNI調用的。這兩個宏在不同的操作系統中,其定義是不一樣的。
#define JNIIMPORT
#define JNIEXPORT attribute ((visibility (“default”)))
#define JNICALL
-
JNIEnv類實際代表了Java環境,通過這個JNIEnv* 指針,就可以對Java端的代碼進行操作。例如,創建Java類的對象,調用Java對象的方法,獲取Java對象的屬性等等,JNIEnv的指針會被JNI傳入到本地方法的實現兩數中來對Java端的代碼進行操作。
-
jclass : 爲了能夠在c/c++中使用java類JNI.h頭文件中專門定義了jclass類型來表示java中的Class類
VM怎樣使Native Method跑起來:
我們知道,當一個類第一次被使用到時,這個類的字節碼會被加載到內存,並且只會回載一次。在這個被加載的字節碼的入口維持着一個該類所有方法描述符的list,這些方法描述符包含這樣一些信息:方法代碼存於何處,它有哪些參數,方法的描述符(public之類)等等。
如果一個方法描述符內有native,這個描述符塊將有一個指向該方法的實現的指針。這些實現在一些DLL文件內,但是它們會被操作系統加載到java程序的地址空間。當一個帶有本地方法的類被加載時,其相關的DLL並未被加載,因此指向方法實現的指針並不會被設置。當本地方法被調用之前,這些DLL纔會被加載,這是通過調用java.system.loadLibrary()實現的。