Android JNI使用和原理分析

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

  1. JNIEnv類實際代表了Java環境,通過這個JNIEnv* 指針,就可以對Java端的代碼進行操作。例如,創建Java類的對象,調用Java對象的方法,獲取Java對象的屬性等等,JNIEnv的指針會被JNI傳入到本地方法的實現兩數中來對Java端的代碼進行操作。

  2. jclass : 爲了能夠在c/c++中使用java類JNI.h頭文件中專門定義了jclass類型來表示java中的Class類

VM怎樣使Native Method跑起來:
我們知道,當一個類第一次被使用到時,這個類的字節碼會被加載到內存,並且只會回載一次。在這個被加載的字節碼的入口維持着一個該類所有方法描述符的list,這些方法描述符包含這樣一些信息:方法代碼存於何處,它有哪些參數,方法的描述符(public之類)等等。
如果一個方法描述符內有native,這個描述符塊將有一個指向該方法的實現的指針。這些實現在一些DLL文件內,但是它們會被操作系統加載到java程序的地址空間。當一個帶有本地方法的類被加載時,其相關的DLL並未被加載,因此指向方法實現的指針並不會被設置。當本地方法被調用之前,這些DLL纔會被加載,這是通過調用java.system.loadLibrary()實現的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章