Android JNI 函數註冊的兩種方式(靜態註冊/動態註冊)

JNI/NDK

在Android開發中,因爲種種緣由咱們須要調用C/C++代碼, 這個時候就要用到Android開發者都據說過的JNI(Java Native Interface)了, 在調用JNI相關方法以前, 要對java中native關鍵字定義的方法進行註冊, 註冊方式有兩種: 靜態註冊和動態註冊, 二者優缺點以下:java

  • 靜態註冊
    優勢: 理解和使用方式簡單, 屬於傻瓜式操做, 使用相關工具按流程操做就行, 出錯率低
    缺點: 當須要更改類名,包名或者方法時, 須要按照以前方法從新生成頭文件, 靈活性不高
  • 動態註冊
    優勢: 靈活性高, 更改類名,包名或方法時, 只需對更改模塊進行少許修改, 效率高
    缺點: 對新手來講稍微有點難理解, 同時會因爲搞錯簽名, 方法, 致使註冊失敗

靜態註冊

此註冊方法是初學者常常用到的, 比較常見, 這裏簡單說下流程,
1.編寫一個java類,在裏面加載對應的so庫而且經過native關鍵字定義須要調用的函數android

package com.example.wenzhe.myjni;
/**
 * Created by wenzhe on 16-1-27.
 */
public class JniTest {
public native int getRandomNum();
public native String getNativeString();

static {
    System.loadLibrary("HelloJni");
    }
}

2.在命令行下輸入 javac JniTest.java 生成JniTest.class文件
而後在src目錄下經過 javah com.example.wenzhe.myjni.JniTest 生成 com_example_wenzhe_myjni_JniTest.h 頭文件
3.將頭文件拷貝到jni目錄下(eclipse在src同級目錄創建文件夾,Android studio 在java同級目錄創建文件夾)
4.編寫C/C++源代碼 並把剛拷貝的頭文件包含進去 ,複製頭文件中函數的定義部分,並實現其中的你想要的功能ios

而後編寫Android.mk Application.mk(Application.mk主要用來定義適應的平臺,x86 arm等)數組

Android.mk以下:架構

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := HelloJni
LOCAL_SRC_FILES := HelloJni.cpp

include $(BUILD_SHARED_LIBRARY)

Application.mk以下:dom

#支持標準C++特性
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
#支持的CPU架構
APP_ABI := armeabi-v7a
#Android 版本
APP_PLATFORM := android-22

include $(BUILD_SHARED_LIBRARY)

其中LOCAL_MODULE定義的名字就是生成的so庫名字,so庫前面都會有個lib前綴,上面生產的so應該爲 libHelloJni.soeclipse

5.在命令行中進入jni目錄,輸入ndk-build 便可生產對應so庫,會自動放在libs文件夾下 至此就能夠運行程序了函數

動態註冊

動態註冊基本思想是在JNI_Onload()函數中經過JNI中提供的RegisterNatives()方法來將C/C++方法和java方法對應起來(註冊), 咱們在調用 System.loadLibrary的時候,會在C/C++文件中回調一個名爲 JNI_OnLoad ()的函數,在這個函數中通常是作一些初始化相關操做, 咱們能夠在這個方法裏面註冊函數, 註冊總體流程以下:工具

  1. 編寫Java端的相關native方法
  2. 編寫C/C++代碼, 實現JNI_Onload()方法
  3. 將Java 方法和 C/C++方法經過簽名信息一一對應起來
  4. 經過JavaVM獲取JNIEnv, JNIEnv主要用於獲取Java類和調用一些JNI提供的方法
  5. 使用類名和對應起來的方法做爲參數, 調用JNI提供的函數RegisterNatives()註冊方法

示例代碼以下:ui

// jni頭文件 
#include <jni.h>
 
#include <cassert>
#include <cstdlib>
#include <iostream>
using namespace std;
 
 
//native 方法實現
jint get_random_num(){
    return rand();
}
/*須要註冊的函數列表,放在JNINativeMethod 類型的數組中,
之後若是須要增長函數,只需在這裏添加就好了
參數:
1.java中用native關鍵字聲明的函數名
2.簽名(傳進來參數類型和返回值類型的說明) 
3.C/C++中對應函數的函數名(地址)
*/
static JNINativeMethod getMethods[] = {
        {"getRandomNum","()I",(void*)get_random_num},
};
//此函數經過調用RegisterNatives方法來註冊咱們的函數
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){
    jclass clazz;
    //找到聲明native方法的類
    clazz = env->FindClass(className);
    if(clazz == NULL){
        return JNI_FALSE;
    }
   //註冊函數 參數:java類 所要註冊的函數數組 註冊函數的個數
    if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){
        return JNI_FALSE;
    }
    return JNI_TRUE;
}
 
static int registerNatives(JNIEnv* env){
    //指定類的路徑,經過FindClass 方法來找到對應的類
    const char* className  = "com/example/wenzhe/myjni/JniTest";
    return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0]));
}
//回調函數
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
    JNIEnv* env = NULL;
   //獲取JNIEnv
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    //註冊函數 registerNatives ->registerNativeMethods ->env->RegisterNatives
    if(!registerNatives(env)){
        return -1;
    }
    //返回jni 的版本 
    return JNI_VERSION_1_6;
}

上面的代碼就能實現動態註冊JNI了 之後要增長函數只需在java文件中聲明native方法,在C/C++文件中實現,
並在getMethods數組添加一個元素並指明對應關係,經過ndk-build 生成so庫就能夠運行了
其中JNI版本能夠在jni.h頭文件中去查看支持哪些版本,通常定義在文件最後幾行

JNI 簽名

動態註冊中 JNINativeMethod 結構體中第二個參數需注意
括號內表明傳入參數的簽名符號,爲空能夠不寫,括號外表明返回參數的簽名符號,爲空填寫 V,對應關係入下表

簽名符號 C/C++ 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
[Z jbooleanArray boolean[]
[I jintArray int[]
[J jlongArray long[]
[D jdoubleArray double[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
L完整包名加類名; jobject class

舉個例子:

傳入的java參數有兩個 分別是 int 和 long[] 函數返回值爲 String 即函數的定義爲:String getString(int a ,long[] b)
簽名就應該是 :"(I[J)Ljava/lang/String;"(不要漏掉英文分號)
若是有內部類 則用 $ 來分隔 如:Landroid/os/FileUtils$FileStatus;

總結

當熟悉動態註冊後, 動態註冊無疑是註冊函數的更好方式, 惟一要注意的是註冊函數時, 須要額外當心, 別把類名,函數名和簽名寫錯了, 否則loadLibraries時會致使應用Crash, 關於JNI部分知識, 我還會寫一篇關於如何高效傳遞數據以及JNI開發過程當中的一些坑的總結.

做者:smewise 連接:https://www.jianshu.com/p/1d6ec5068d05 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

相關文章
相關標籤/搜索