1. 程式人生 > >NDK開發 從入門到放棄(二:動態註冊JNI、多JNI呼叫)

NDK開發 從入門到放棄(二:動態註冊JNI、多JNI呼叫)

一、前言

上一篇我們講了NDK開發的最簡單的一個入門流程,且寫了一個例項。例項中java的native方法與C/C++程式碼函式是通過Java_<包名>_<類名>_<方法名>這種方式對應的,稱為靜態註冊。在上一篇的例子中,我們是通過javah -jni命令來自動生成.h標頭檔案的,自動幫我們寫好了方法名,所以雖然函式名很長(手寫的話容易寫錯),但是自動生產然後copy到C++類中,倒也沒什麼麻煩的。但是若是我們需要呼叫之前就早已寫好的C++程式碼,方法名肯定就不滿足靜態註冊的規則了,就需要用到動態註冊JNI的方法了。這篇文章將介紹動態註冊JNI的方式以及同時多個JNI時的處理方式(這裡一個靜態註冊,一個動態註冊),NDK開發流程以及靜態註冊請檢視文章:

NDK開發 從入門到放棄(一、基本流程入門瞭解)

二、例項

同樣,我們先寫一個本地方法的Java類。

public class JNIDynamicUtils {
    /**
     * 呼叫C++程式碼的方法,返回對應的字串
     * @return
     */
    public static native String getHelloStringFromJNI();

    /**
     * 載入so庫或jni庫
     */
    static {
        System.loadLibrary("JNI_DYNAMIC_ANDROID_TEST"
); } }

在靜態註冊的時候,我們會對該類做javah -jni操作,生產對應的.h的標頭檔案,在cpp檔案中使用標頭檔案中自動給我們生成好的C++方法名,因為此處我們要使用動態註冊的方式,則不需要做這個操作了。我們直接在jni目錄下新建jnidynamicutils.cpp檔案,假設我們已經有了一個C++程式碼的native_hello函式,該函式返回一個字串(Java與C++之間的型別對應與字串操作將在下一節講解),我們現在希望當我們呼叫JNIDynamicUtilsgetHelloStringFromJNI方法時呼叫這個native_hello函式,則需要在JNI_OnLoad

方法內進行動態註冊繫結,相關注釋見程式碼。

#include <stdio.h>
#include <jni.h>
#include <stdlib.h>

//C++層 native函式
jstring native_hello(JNIEnv *env, jclass clz) {
    return env->NewStringUTF("Hello java, this is C++. ---jni");
}

/**
 * JNINativeMethod由三部分組成,可新增多組對應:
 * (1)Java中的函式名;
 * (2)函式簽名,格式為(輸入引數型別)返回值型別;
 *  ()Ljava/lang/String; ()表示無參,Ljava/lang/String;表示返回String,在物件類名(包括包名,‘/’間隔)前面加L,分號結尾
 * (3)native函式名
 */
static JNINativeMethod gMethods[] = { {
      "getHelloStringFromJNI", "()Ljava/lang/String;", (void *) native_hello } };

//System.loadLibrary過程中會自動呼叫JNI_OnLoad,在此進行動態註冊
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
    JNIEnv *env = NULL;
    jint result = JNI_FALSE;

    //獲取env指標
    if (jvm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
        return result;
    }
    if (env == NULL) {
        return result;
    }
    //獲取類引用,寫類的全路徑(包名+類名)。FindClass等JNI函式將在後面講解
    jclass clazz = env->FindClass("***/***/JNIDynamicUtils");
    if (clazz == NULL) {
        return result;
    }
    //註冊方法
    if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) < 0) {
        return result;
    }
    //成功
    result = JNI_VERSION_1_6;
    return result;
}

這裡給出另一靜態註冊的相關程式碼,但不再解釋(JNIStaticUtils.java 檔案與 jnistaticutils.cpp檔案,jnistaticutils.h檔案就不給出了,只是申明瞭一個函式名而已)。

public class JNIStaticUtils {
    /**
     * 呼叫C++程式碼的方法,返回對應的字串
     * @return
     */
    public static native String getStringFromJNI();

    /**
     * 載入so庫或jni庫
     */
    static {
        System.loadLibrary("JNI_STATIC_ANDROID_TEST");
    }
}
#include <stdio.h>
#include <jni.h>
#include <stdlib.h>
#include "jnistaticutils.h"

JNIEXPORT jstring JNICALL Java_<包名>_JNIStaticUtils_getStringFromJNI(JNIEnv *env, jclass clazz) {
    return env->NewStringUTF("this is string from jni.");
}

同上篇文章講過的,需要Android.mk和Application.mk檔案,Android.mk的程式碼如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := JNI_STATIC_ANDROID_TEST
LOCAL_SRC_FILES =: jnistaticutils.cpp
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := JNI_DYNAMIC_ANDROID_TEST
LOCAL_SRC_FILES =: jnidynamicutils.cpp
include $(BUILD_SHARED_LIBRARY)

然後我們在jni目錄下可以任意選擇一個檔案右鍵來執行ndk-build操作,在main/libs目錄下生成了libJNI_DYNAMIC_ANDROID_TEST.so檔案與libJNI_STATIC_ANDROID_TEST.so檔案,我們複製去jniLibs目錄下。
我們在Activity中分別呼叫測試(點選對應的按鈕,分別執行不同的操作,將C++返回的字串在介面上累加顯示出來,程式碼簡單,就不貼了):
這裡寫圖片描述