1. 程式人生 > >Android之JNI動態註冊native方法和JNI資料簡單使用

Android之JNI動態註冊native方法和JNI資料簡單使用

1、介紹JNI註冊方式

JVM 查詢 native 方法有兩種方式:     1)、按照 JNI 規範的命名規則(靜態註冊)    2) 、呼叫 JNI 提供的 RegisterNatives 函式,將本地函式註冊到 JVM 中(動態註冊)

2、動態註冊的步驟

先看有幾個檔案

1、MainActivity.java

package com.example.chenyu.test;
 
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
 
public class MainActivity extends AppCompatActivity {
 
    public static final String TAG = "TestJni";
    public TextView mTv;
    public JniClient mJniClient;
 
    static {
        System.loadLibrary("FirstJni");
    }
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTv = (TextView) findViewById(R.id.text);
        mJniClient = new JniClient();
        String result = mJniClient.getStr();
        int sum = mJniClient.addInt(2, 3);
        Log.d(TAG, " mTv.setText before");
        mTv.setText("string is" + result + " and 2 + 3 is " + sum);
        Log.d(TAG, " mTv.setText after");
    }
}

2、JniClient.java檔案

    package com.example.chenyu.test;
     
    public class JniClient {
    	public	JniClient() {
    	}
    	public native String getStr();
    	public native int addInt(int a, int b);
    }

3、JniClient.c檔案(新建一個資料夾jni,然後把這個檔案放在jni資料夾裡面)

//
// Created by chenyu on 5/7/17.
//
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>
 
#define JNIREG_CLASS "com/example/chenyu/test/JniClient"//指定要註冊的類
 
jstring get_strstr(JNIEnv* env, jobject thiz)
{
    return (*env)->NewStringUTF(env, "I am chenyu, 動態註冊JNI");
}
 
jint add_int(JNIEnv* env, jobject jobj, jint num1, jint num2){
    return num1 + num2;
}
 
/**
* 方法對應表
*/
static JNINativeMethod gMethods[] = {
        {"getStr", "()Ljava/lang/String;", (void*)get_str},
        {"addInt", "(II)I", (void*)add_int},
};
 
/*
* 為某一個類註冊本地方法
*/
static int registerNativeMethods(JNIEnv* env
        , const char* className
        , JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}
/*
* 為所有類註冊本地方法
*/
static int registerNatives(JNIEnv* env) {
    return registerNativeMethods(env, JNIREG_CLASS, gMethods,
                                 sizeof(gMethods) / sizeof(gMethods[0]));
}
/*
* System.loadLibrary("lib")時呼叫
* 如果成功返回JNI版本, 失敗返回-1
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    if (!registerNatives(env)) {//註冊
        return -1;
    }
    //成功
    result = JNI_VERSION_1_4;
    return result;
}

我們先說明JniClient.c檔案裡面內容

1)、JNIEXPORT和JNICALL含義

我們先看jni.h檔案,裡面包含了標頭檔案 jni_md.h檔案

 

我們再來看jni_md.h檔案

所以JNIEXPORT 和 JNICALL 是一個空定義

    JNIEXPORT jint JNICALL Java_com_example_firstjni_JniClient_AddInt  
      (JNIEnv *, jclass, jint, jint); 

第一個引數:JNIEnv* 是定義任意 native 函式的第一個引數(包括呼叫 JNI 的 RegisterNatives 函式註冊的函式),指向 JVM 函式表的指標,函式表中的每一個入口指向一個 JNI 函式,每個函式用於訪問 JVM 中特定的資料結構。 第二個引數:呼叫 Java 中 native 方法的例項或 Class 物件,如果這個 native 方法是例項方法,則該引數是 jobject,如果是靜態方法,則是 jclass。 第三個引數:Java 對應 JNI 中的資料型別,Java 中 int 型別對應 JNI 的 jint 型別。(後面會詳細介紹 JAVA 與 JNI 資料型別的對映關係)

函式返回值型別:夾在 JNIEXPORT 和 JNICALL 巨集中間的 jint,表示函式的返回值型別,對應 Java 的int 型別

我們用RegisterNatives動態獲取本地方法

我們先看JNINativeMethod 結構體的官方定義

    typedef struct {  
      const char* name;  
      const char* signature;  
      void* fnPtr;  
    } JNINativeMethod;

第一個變數name是Java中函式的名字。 第二個變數signature,用字串是描述了Java中函式的引數和返回值 第三個變數fnPtr是函式指標,指向native函式。前面都要接 (void *)

所以JniClient.c檔案裡面有下面的程式碼

    /**
    * 方法對應表
    */
    static JNINativeMethod gMethods[] = {
            {"getStr", "()Ljava/lang/String;", (void*)get_str},
            {"addInt", "(II)I", (void*)add_int},
    };

第一個引數就是我們寫的方法,第三個就是.h檔案裡面的方法,主要是第二個引數比較複雜.括號裡面表示引數的型別,括號後面表示返回值。 “()” 中的字元表示引數,後面的則代表返回值。例如”()V” 就表示void * Fun(); “(II)V” 表示 void Fun(int a, int b); “(II)I” 表示 int addInt(int a, int b); "()Ljava/lang/String;" 表示String getStr(); 這些字元與函式的引數型別的對映表如下: 字元 J型別 java型別

    V void void
    Z jboolean boolean
    I jint int
    J jlong long
    D jdouble double
    F jfloat float
    B jbyte byte
    C jchar char
    S jshort short

陣列則以”[“開始,用兩個字元表示

    [I jintArray int[]
    [F jfloatArray float[]
    [B jbyteArray byte[]
    [C jcharArray char[]
    [S jshortArray short[]
    [D jdoubleArray double[]
    [J jlongArray long[]
    [Z jbooleanArray boolean[]

如圖:

物件型別:以”L”開頭,以”;”結尾,中間是用”/” 隔開。如上表第1個 陣列型別:以”[“開始。如上表第2個(n維陣列的話,則是前面多少個”[“而已,如”[[[D”表示“double[][][]”) 如果Java函式的引數是class,則以”L”開頭,以”;”結尾中間是用”/” 隔開的包及類名。而其對應的C函式名的引數則為jobject. 一個例外是String類,其對應的類為jstring

Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject 

如果JAVA函式位於一個嵌入類,則用作為類名間的分隔符。例如“(Ljava/lang/String;Landroid/os/FileUtilsFileStatus;)Z”

重寫JNI_OnLoad()方法這樣就會當呼叫 System.loadLibrary(“XXXX”)方法的時候直接來呼叫JNI_OnLoad(),這樣就達到了動態註冊實現native方法的作用。  

/*
* System.loadLibrary("lib")時呼叫
* 如果成功返回JNI版本, 失敗返回-1
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    if (!registerNatives(env)) {//註冊
        return -1;
    }
    //成功
    result = JNI_VERSION_1_4;
    return result;
}

為類註冊本地方法

    /*
    * 為所有類註冊本地方法
    */
    static int registerNatives(JNIEnv* env) {
        return registerNativeMethods(env, JNIREG_CLASS, gMethods,sizeof(gMethods) / sizeof(gMethods[0]));
    }

我們執行程式碼的時候要記得在build.gradle檔案加上部分生成so檔案的程式碼

defaultConfig {
        applicationId "com.example.chenyu.test"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        ndk{
            moduleName "FirstJni"
            ldLibs "log", "z", "m"
            abiFilters "armeabi", "armeabi-v7a", "x86"
            //用於指定應用應該使用哪個標準庫,此處新增c++庫支援
            stl "stlport_static"        //  支援stl
            cFlags "-fexceptions"        // 支援exception
        }
        sourceSets.main{
            jniLibs.srcDirs = ['libs']
        }
 
 
    }

然後我們進入這個專案的jni目錄,然後執行命令

ndk-build

然後就會在libs資料夾下面的armeabi資料夾下面生成libFirstJni.so檔案

3、JNI資料型別及常用方法

基本型別和本地等效型別表:

介面函式表:

const struct JNINativeInterface ... = {
    NULL,
    NULL,
    NULL,
    NULL,
    GetVersion,
 
    DefineClass,
    FindClass,
    NULL,
    NULL,
    NULL,
    GetSuperclass,
    IsAssignableFrom,
    NULL,
 
    Throw,
    ThrowNew,
    ExceptionOccurred,
    ExceptionDescribe,
    ExceptionClear,
    FatalError,
    NULL,
    NULL,
 
    NewGlobalRef,
    DeleteGlobalRef,
    DeleteLocalRef,
    IsSameObject,
    NULL,
    NULL,
    AllocObject,
 
    NewObject,
    NewObjectV,
    NewObjectA,
    GetObjectClass,
 
    IsInstanceOf,
 
    GetMethodID,
 
    CallObjectMethod,
    CallObjectMethodV,
    CallObjectMethodA,
    CallBooleanMethod,
    CallBooleanMethodV,
    CallBooleanMethodA,
    CallByteMethod,
    CallByteMethodV,
    CallByteMethodA,
    CallCharMethod,
    CallCharMethodV,
    CallCharMethodA,
    CallShortMethod,
    CallShortMethodV,
    CallShortMethodA,
    CallIntMethod,
    CallIntMethodV,
    CallIntMethodA,
    CallLongMethod,
    CallLongMethodV,
    CallLongMethodA,
    CallFloatMethod,
    CallFloatMethodV,
    CallFloatMethodA,
    CallDoubleMethod,
    CallDoubleMethodV,
    CallDoubleMethodA,
    CallVoidMethod,
    CallVoidMethodV,
    CallVoidMethodA,
 
    CallNonvirtualObjectMethod,
    CallNonvirtualObjectMethodV,
    CallNonvirtualObjectMethodA,
    CallNonvirtualBooleanMethod,
    CallNonvirtualBooleanMethodV,
    CallNonvirtualBooleanMethodA,
    CallNonvirtualByteMethod,
    CallNonvirtualByteMethodV,
    CallNonvirtualByteMethodA,
    CallNonvirtualCharMethod,
    CallNonvirtualCharMethodV,
    CallNonvirtualCharMethodA,
    CallNonvirtualShortMethod,
    CallNonvirtualShortMethodV,
    CallNonvirtualShortMethodA,
    CallNonvirtualIntMethod,
    CallNonvirtualIntMethodV,
    CallNonvirtualIntMethodA,
    CallNonvirtualLongMethod,
    CallNonvirtualLongMethodV,
    CallNonvirtualLongMethodA,
    CallNonvirtualFloatMethod,
    CallNonvirtualFloatMethodV,
    CallNonvirtualFloatMethodA,
    CallNonvirtualDoubleMethod,
    CallNonvirtualDoubleMethodV,
    CallNonvirtualDoubleMethodA,
    CallNonvirtualVoidMethod,
    CallNonvirtualVoidMethodV,
    CallNonvirtualVoidMethodA,
 
    GetFieldID,
 
    GetObjectField,
    GetBooleanField,
    GetByteField,
    GetCharField,
    GetShortField,
    GetIntField,
    GetLongField,
    GetFloatField,
    GetDoubleField,
    SetObjectField,
    SetBooleanField,
    SetByteField,
    SetCharField,
    SetShortField,
    SetIntField,
    SetLongField,
    SetFloatField,
    SetDoubleField,
    GetStaticMethodID,
    CallStaticObjectMethod,
    CallStaticObjectMethodV,
    CallStaticObjectMethodA,
    CallStaticBooleanMethod,
    CallStaticBooleanMethodV,
    CallStaticBooleanMethodA,
    CallStaticByteMethod,
    CallStaticByteMethodV,
    CallStaticByteMethodA,
    CallStaticCharMethod,
    CallStaticCharMethodV,
    CallStaticCharMethodA,
    CallStaticShortMethod,
    CallStaticShortMethodV,
    CallStaticShortMethodA,
    CallStaticIntMethod,
    CallStaticIntMethodV,
    CallStaticIntMethodA,
    CallStaticLongMethod,
    CallStaticLongMethodV,
    CallStaticLongMethodA,
    CallStaticFloatMethod,
    CallStaticFloatMethodV,
    CallStaticFloatMethodA,
    CallStaticDoubleMethod,
    CallStaticDoubleMethodV,
    CallStaticDoubleMethodA,
    CallStaticVoidMethod,
    CallStaticVoidMethodV,
    CallStaticVoidMethodA,
    GetStaticFieldID,
    GetStaticObjectField,
    GetStaticBooleanField,
    GetStaticByteField,
    GetStaticCharField,
    GetStaticShortField,
    GetStaticIntField,
    GetStaticLongField,
    GetStaticFloatField,
    GetStaticDoubleField,
    SetStaticObjectField,
    SetStaticBooleanField,
    SetStaticByteField,
    SetStaticCharField,
    SetStaticShortField,
    SetStaticIntField,
    SetStaticLongField,
    SetStaticFloatField,
    SetStaticDoubleField,
    NewString,
    GetStringLength,
    GetStringChars,
    ReleaseStringChars,
    NewStringUTF,
    GetStringUTFLength,
    GetStringUTFChars,
    ReleaseStringUTFChars,
    GetArrayLength,
    NewObjectArray,
    GetObjectArrayElement,
    SetObjectArrayElement,
    NewBooleanArray,
    NewByteArray,
    NewCharArray,
    NewShortArray,
    NewIntArray,
    NewLongArray,
    NewFloatArray,
    NewDoubleArray,
    GetBooleanArrayElements,
    GetByteArrayElements,
    GetCharArrayElements,
    GetShortArrayElements,
    GetIntArrayElements,
    GetLongArrayElements,
    GetFloatArrayElements,
    GetDoubleArrayElements,
    ReleaseBooleanArrayElements,
    ReleaseByteArrayElements,
    ReleaseCharArrayElements,
    ReleaseShortArrayElements,
    ReleaseIntArrayElements,
    ReleaseLongArrayElements,
    ReleaseFloatArrayElements,
    ReleaseDoubleArrayElements,
    GetBooleanArrayRegion,
    GetByteArrayRegion,
    GetCharArrayRegion,
    GetShortArrayRegion,
    GetIntArrayRegion,
    GetLongArrayRegion,
    GetFloatArrayRegion,
    GetDoubleArrayRegion,
    SetBooleanArrayRegion,
    SetByteArrayRegion,
    SetCharArrayRegion,
    SetShortArrayRegion,
    SetIntArrayRegion,
    SetLongArrayRegion,
    SetFloatArrayRegion,
    SetDoubleArrayRegion,
    RegisterNatives,
    UnregisterNatives,
    MonitorEnter,
    MonitorExit,
    GetJavaVM,
};

JNI與C/C++資料型別的轉換(效率開發)字元陣列與jbyteArray

jbyteArray轉字元陣列

int byteSize = (int) env->GetArrayLength(jbyteArrayData);  //jbyteArrayData是jbyteArray型別的資料
unsigned char* data = new unsigned char[byteSize + 1];
env->GetByteArrayRegion(jbyteArrayData, 0, byteSize, reinterpret_cast<jbyte*>(data));
data[byteSize] = '\0';

字元陣列轉jbyteArray

    jbyte *jb =  (jbyte*) data;   //data是字元陣列型別
    jbyteArray jarray = env->NewByteArray(byteSize);   //byteSize是字元陣列大小
    env->SetByteArrayRegion(jarray, 0, byteSize, jb);

字元陣列與jstring     jstring轉字元陣列

char* JstringToChar(JNIEnv* env, jstring jstr) {
    if(jstr == NULL) {
        return NULL;
    }
    char* rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("utf-8");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes",
            "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char*) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}

字元陣列轉jstring

jstring StrtoJstring(JNIEnv* env, const char* pat)
{
    jclass strClass = env->FindClass("java/lang/String");
    jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
    jbyteArray bytes = env->NewByteArray(strlen(pat));
    env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
    jstring encoding = env->NewStringUTF("utf-8");
    return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}

最簡單的可以直接使用

jstring jstr = env->NewStringUTF(str);

jint與int的互轉都可以直接使用強轉,如:

jint i = (jint) 1024;