1. 程式人生 > >JNI 動態註冊和靜態註冊的詳解

JNI 動態註冊和靜態註冊的詳解

   1. 什麼是JNI? 
       JNI的英文縮寫是 java nativie interface  ,按照字面解釋就是java 本地介面。什麼樣的接口才叫nativie interface  ,用c/c++寫程式碼。所以JNI是用c++語言編寫的介面供java呼叫。

     2.為什麼JNI用C++寫得程式碼可以供java使用,兩個是完全不同的語言,他們是如何轉換的
  我們用java中寫native方法:

public  native void native_init(); //關鍵字native
使用javah 可以生成類似這樣的標頭檔案

#include <jni.h>
/* Header for class aai_along_and_jni_test2_JNI_test1 */

#ifndef _Included_aai_along_and_jni_test2_JNI_test1
#define _Included_aai_along_and_jni_test2_JNI_test1
#ifdef __cplusplus
extern "C" {
#endif
裡面有個#include <jni.h> ,#include 就是c/c++引用標頭檔案的方式。所以答案也就在這個jni.h裡面。對jni有點了解的應該知道jni的幾個資料型別,c++的資料型別跟JNI的資料型別對應關係都定義在jni.h裡面,如下。

typedef uint8_t         jboolean;       /* unsigned 8 bits */
typedef int8_t          jbyte;          /* signed 8 bits */
typedef uint16_t        jchar;          /* unsigned 16 bits */
typedef int16_t         jshort;         /* signed 16 bits */
typedef int32_t         jint;           /* signed 32 bits */
typedef int64_t         jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */

typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;
typedef jarray          jbooleanArray;
typedef jarray          jbyteArray;
typedef jarray          jcharArray;
typedef jarray          jshortArray;
typedef jarray          jintArray;
typedef jarray          jlongArray;
typedef jarray          jfloatArray;
typedef jarray          jdoubleArray;
typedef jobject         jthrowable;
typedef jobject         jweak;
jni.h檔案配合jvm,這樣就完成了c++層的資料與java層的資料轉換過程,因此native可以傳遞值到java ,java 也可以傳值到native實現資料的相互通訊關係。其實從中也是可以看出來JNI只是做了一個轉換的工作:資料轉換的過程。

 

3.靜態註冊
  1)載入jni庫並且載入nativie方法,本文的JNI庫名為:jni-test

public class JNIStaticTest{
    public  String name;
    public Context testContext;
    private final String TAG="JNIStaticTest-java";
   //載入的jni庫,庫名:libjni-test.so ,載入的時候省略lib和.so
    static {
        System.loadLibrary("jni-test");
    }
    public JNIStaticTest(){
        native_init();
    }
    public JNIStaticTest(Context context){
        Log.d(TAG,"初始化");
        testContext=context;
        native_init();
    }
    public void setName(String name1){
        Log.i(TAG,"setName:"+name1);
        name =name1;
    }
    public String getName(){
        Log.i(TAG,"getName:"+name);
        return name;
    }
    public String transimFromJNI(String from,ExternClass inner){
        Log.i(TAG,"from="+from+",ExternClass name="+inner.className);
        String returnString="Java have get information";
        return returnString;
    }
    //以下內地方法實現在native層,這裡只是作為函式的呼叫介面。
    public native String stringFromJNI();
    public  native void native_init();
    public native void transmitToJNI(String from, ExternClass inner);
}
   可能你會問,系統是怎麼識別到我的lib庫呢?系統會在三個資料夾去搜索system/lib ,vend/lib ,your app package/lib。/data/app/aai.along.and.jni/lib/arm/libjni-test.so  這是在我的app安裝目錄下找到的。如果這三個目錄都沒有找到lib那麼就會報UnsatisfiedLinkError異常。在libcore\ojluni\src\main\java\java\lang\Runtime.java 程式碼裡面有這樣的描述。

 public void loadLibrary(String libname, ClassLoader classLoader) {
        checkTargetSdkVersionForLoad("java.lang.Runtime#loadLibrary(String, ClassLoader)");
        java.lang.System.logE("java.lang.Runtime#loadLibrary(String, ClassLoader)" +
                              " is private and will be removed in a future Android release");
        loadLibrary0(classLoader, libname);
    }

    synchronized void loadLibrary0(ClassLoader loader, String libname) {
        if (libname.indexOf((int)File.separatorChar) != -1) {
            throw new UnsatisfiedLinkError(
    "Directory separator should not appear in library name: " + libname);
        }
        String libraryName = libname;
        if (loader != null) {
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
                // It's not necessarily true that the ClassLoader used
                // System.mapLibraryName, but the default setup does, and it's
                // misleading to say we didn't find "libMyLibrary.so" when we
                // actually searched for "liblibMyLibrary.so.so".
                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                                               System.mapLibraryName(libraryName) + "\"");
            }
            String error = doLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }

        String filename = System.mapLibraryName(libraryName);
        List<String> candidates = new ArrayList<String>();
        String lastError = null;
        for (String directory : getLibPaths()) {
            String candidate = directory + filename;
            candidates.add(candidate);

            if (IoUtils.canOpenReadOnly(candidate)) {
                String error = doLoad(candidate, loader);
                if (error == null) {
                    return; // We successfully loaded the library. Job done.
                }
                lastError = error;
            }
        }

        if (lastError != null) {
            throw new UnsatisfiedLinkError(lastError);
        }
        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    }

    private volatile String[] mLibPaths = null;

    private String[] getLibPaths() {
        if (mLibPaths == null) {
            synchronized(this) {
                if (mLibPaths == null) {
                    mLibPaths = initLibPaths();
                }
            }
        }
        return mLibPaths;
    }
想要更詳細的瞭解載入過程,可以參考這篇博文https://my.oschina.net/wolfcs/blog/129696

2) java層的我們已經寫完,現在開始轉到native層。native 層的方法命名是有一定的規則的,簡單點就是:包名+類名+方法名  由於jni中的點有特殊用處,所以點用_替代 。我java層:包名:aai.along.and.jni   類名:JNIStaticTest  方法名:native_init() 使用javah  生產的nativie方法名是:JNIEXPORT void JNICALL Java_aai_along_and_jni_JNIStaticTest_native_1init (JNIEnv *, jobject);   不對啊,跟剛才說的規則不同,多了JNIEXPORT  JNICALL 而且native_1init  比native_init 多了一個1.JNIEXPORT  JNICALL 這兩個都是jni的標誌性,告訴系統這個是jni方法,沒什麼特殊含義。而native_1init  裡面多了一個1確實需要特別注意的,只有當你的方法名、類名、包名中有_後面都需要加1.這是jni的語言規則。很多文章都沒講這個特殊性,是個大坑貨。

3)android studio 如果生產native的標頭檔案呢?

    1.選擇setings

2.選擇External Tools

  3.選擇+ 建立新的工具。

 . 

4填寫新的工具引數

5.程式碼中使用,在使用這個工具前,先要保證有生成對應的.class檔案,執行make project

需要注意點是:引數的填寫。

program :$JDKPath$\bin\javah.exe          
argument:-d $ModuleFileDir$/src/main/jni -classpath $ModuleSdkPath$\platforms\android-28\android.jar;$ModuleSdkPath$\extras/android/m2repository/com/android/support/appcompat-v7/25.3.1/appcompat-v7-25.3.1-sources.jar; $FileClass$             
working directory :$ModuleFileDir$\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\    

引數說明: $JDKPath$\bin\javah.exe   javah的路徑 。-d $ModuleFileDir$/src/main/jni  生成的標頭檔案目錄, -classpath $ModuleSdkPath$\platforms\android-28\android.jar;$ModuleSdkPath$\extras/android/m2repository/com/android/support/appcompat-v7/25.3.1/appcompat-v7-25.3.1-sources.jar;  需要引用其他jar包路徑。$FileClass$ 需要生成的java檔案    $ModuleFileDir$\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\  此處是表示你生成的class檔案所在的目錄。

這樣我們生成的.h檔案如下

#include <jni.h>
#include <android/log.h>
/* Header for class aai_along_and_jni_JNIStaticTest */

#ifndef _Included_aai_along_and_jni_JNIStaticTest
#define _Included_aai_along_and_jni_JNIStaticTest
#ifdef __cplusplus
extern "C" {
#endif
#define LOG_TAG "JNIStaticTest-jni"
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
/*
 * Class:     aai_along_and_jni_JNIStaticTest
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_aai_along_and_jni_JNIStaticTest_stringFromJNI
  (JNIEnv *, jobject);
/*
 * Class:     aai_along_and_jni_JNIStaticTest
 * Method:    native_init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_aai_along_and_jni_JNIStaticTest_native_1init
  (JNIEnv *, jobject);

/*
 * Class:     aai_along_and_jni_JNIStaticTest
 * Method:    transmitToJNI
 * Signature: (Ljava/lang/String;Laai/along/and/jni/ExternClass;)V
 */
JNIEXPORT void JNICALL Java_aai_along_and_jni_JNIStaticTest_transmitToJNI
  (JNIEnv *, jobject, jstring, jobject);

#ifdef __cplusplus
}
#endif
#endif
 其中如下的程式碼增加是我為了除錯列印log用的。因為native的程式碼屬於c++,上層的logcat就無法抓取到對應的log,除錯非常不方面,加了如下的log我們可以比較清楚的看到native 與java層的聯動。

#include <android/log.h>
#define LOG_TAG "JNIStaticTest-jni"
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
  4)java層呼叫native層,實現剛才的.h檔案的方法即可。參考如下程式碼。

#include <string>
#include <iostream>
#include <stdio.h>
//#include "jniUtils.h"
#include "aai_along_and_jni_JNIStaticTest.h"
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>

//extern "C" JNIEXPORT jstring JNICALL
//extern "C" JNIEXPORT  JNICALL
//extern "C" JNIEXPORT  JNICALL
int file_size2(const  char* filename);
using namespace std;
static const char* const kClassJniTest =
        "aai/along/and/jni/JNIStaticTest";
static const char* const kClassJniExtern =
    "aai/along/and/jni/ExternClass";
    //儲存java層的fieldID 和methodID 以方便後續使用
 struct fields_t {
            jfieldID    context;
            jfieldID    name;
            jclass       clazz;
             jobject       obj;//obj的儲存一定要使用NewGlobalRef的方法
            jmethodID setNameMethodID;
            jmethodID getNameMethodID;
            jmethodID transimFromJNIMethodID;
        }fields;
  struct fields_t *Pfield;


jstring JNICALL  Java_aai_along_and_jni_JNIStaticTest_stringFromJNI(
        JNIEnv* env,
        jobject  obj ) {
         LOGI("JAVA 呼叫 JNI stringFromJNI");
    string hello = "Hello from JNI";
    return env->NewStringUTF(hello.c_str());
}
void JNICALL Java_aai_along_and_jni_JNIStaticTest_native_1init
  (JNIEnv *env, jobject object){
  //printf("Java_aai_along_and_jni_JNIStaticTest_native_1init");
   LOGI("JAVA 呼叫 JNI  native_1init");
   //初始化 方法 field id
   Pfield=&fields;
   jclass clazz = env->FindClass(kClassJniTest);//關聯native 和java層 獲取java層的class
   Pfield->clazz=clazz;
       if (Pfield->clazz == NULL) {
        LOGE("can not find class");
           return;
       }
//獲取java層的方法id 並且儲存起來
Pfield->name = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
if (Pfield->name == NULL) {
            LOGE("can not find name ID");
            return;
           }

Pfield->context = env->GetFieldID(clazz, "testContext", "Landroid/content/Context;");
       if (Pfield->context == NULL) {
              LOGE("can not find context ID");
              return;
             }

Pfield->setNameMethodID = env->GetMethodID(
                                  clazz,
                                  "setName",
                                  "(Ljava/lang/String;)V");
if (Pfield->setNameMethodID == NULL) {
            LOGE("can not find setNameMethodID");
            return;
           }

Pfield->getNameMethodID = env->GetMethodID(
                                        clazz,
                                        "getName",
                                        "()Ljava/lang/String;");

   if (Pfield->getNameMethodID == NULL) {
               LOGE("can not find getNameMethodID");
               return;
              }

 Pfield->transimFromJNIMethodID = env->GetMethodID(
                                          clazz,
                                          "transimFromJNI",
                                          "(Ljava/lang/String;Laai/along/and/jni/ExternClass;)Ljava/lang/String;");


  if (Pfield->transimFromJNIMethodID == NULL) {
              LOGE("can not find transimFromJNIMethodID");
              return;
             }


  }


  void JNICALL Java_aai_along_and_jni_JNIStaticTest_transmitToJNI
    (JNIEnv * env, jobject thizz,jstring information, jobject obj){
     const char *transmitString = env->GetStringUTFChars(information, NULL);
     LOGI("JAVA 呼叫 JNI transmitToJNI transmitString=%s",transmitString);
     Pfield->obj=env->NewGlobalRef(thizz);//想持久儲存thizz的物件,一定要使用NewGlobalRef 後續也要手動刪除
     //初始化  field id 獲取obj 的name
      jclass clazz=env->FindClass(kClassJniExtern);
      jfieldID  fieldNameID=env->GetFieldID(clazz, "className", "Ljava/lang/String;");
      if (fieldNameID == NULL) {
                    LOGE("can not find className ");
                    return;
                   }

      jobject objName;
      objName=env->GetObjectField(obj,fieldNameID);
      jstring jstringName=(jstring)(objName);
      const char *charName = env->GetStringUTFChars(jstringName, NULL);
      LOGI("JNI 呼叫 JAVA  ExternClass className=%s",charName);


      //呼叫java 層的方法。CallVoidMethod CallObjectMethod
      string message = "JNI 呼叫 JAVA ";
      jstring jMessage=env->NewStringUTF(message.c_str());
//通過CallVoidMethod方法,傳入之前獲取的MethodID 呼叫java層方法
       env->CallVoidMethod(Pfield->obj, Pfield->setNameMethodID, jMessage);

      jobject callName= env->CallObjectMethod(Pfield->obj, Pfield->getNameMethodID);
        const char *callNameChar = env->GetStringUTFChars((jstring)callName, NULL);
         LOGI("JNI 呼叫 JAVA  getName=%s",callNameChar);
       jobject calltransim= env->CallObjectMethod(Pfield->obj, Pfield->transimFromJNIMethodID, jMessage,obj);

       const char *calltransimChar = env->GetStringUTFChars((jstring)calltransim, NULL);
        LOGI("JNI 呼叫 JAVA transimFromJNI calltransimChar=%s",calltransimChar);

        //垃圾回收
         env->DeleteLocalRef(callName);
         env->DeleteLocalRef(calltransim);
          env->DeleteLocalRef(objName);
         env->DeleteGlobalRef(Pfield->obj);


    }
  5)native 如何呼叫java層呢?

  1.java層與native層的關聯;

jclass clazz = env->FindClass(kClassJniTest);
2.獲取java層的方法ID

Pfield->setNameMethodID = env->GetMethodID(
                                  clazz,
                                  "setName",
                                  "(Ljava/lang/String;)V");
if (Pfield->setNameMethodID == NULL) {
            LOGE("can not find setNameMethodID");
            return;
           }
3.向CallVoidMethod 方法,傳入之前獲取的setNameMethodID ,以及需要傳遞的引數值,jMessage

string message = "JNI 呼叫 JAVA ";
jstring jMessage=env->NewStringUTF(message.c_str());
 env->CallVoidMethod(Pfield->obj, Pfield->setNameMethodID, jMessage);
具體的方法傳遞引數,可以參考jni.h檔案。

在第二步中Pfield->setNameMethodID = env->GetMethodID( clazz, "setName", "(Ljava/lang/String;)V");  各個引數的含義是什麼呢?

jni.h 中的函式原型是這樣的: 

jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetMethodID(this, clazz, name, sig); }

jclass clazz  很好理解,就是與native關聯的java層的類。

const char* name  就是需要呼叫的方法名

const char* sig  sig又是什麼鬼?之前沒聽過,一臉懵逼。這個是jni特有的稱呼。它的語法規則是:"(引數型別)返回型別"

引數型別和返回型別的規則需要按jni的方式。

由於我們java層的方法是setName(String name1)  string型別,依據規則任何java類的全名:Ljava/lang/String;   ;分號不要忘記

 

這樣我們的java層與native層就能聯動了,程式碼上傳在最後面。靜態註冊的方法基本已經講解完。靜態註冊的方式有很大的弊端就是編寫方法名稱太長,太不美觀了。所以我覺得還是動態註冊最好,最方便。

 

4.動態註冊
  java層的程式碼跟靜態方式相同,就不囉嗦了,參考靜態註冊的程式碼。直接講解native層 .cpp檔案。

  

#include <jni.h>
#include <string>
#include <iostream>
#include <stdio.h>
#include <android/log.h>
//#include "JNIHelp.h"
#include <stdlib.h>
//#include "android_runtime/AndroidRuntime.h"


#define LOG_TAG "JNISharedTest-jni"
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))

using namespace std;
static const char* const kClassJniTest =
        "aai/along/and/jni/JNISharedTest";
static const char* const kClassJniExtern =
    "aai/along/and/jni/ExternClass";
    //儲存java層的fieldID 和methodID 以方便後續使用
 struct fields_t {
            jfieldID    context;
            jfieldID    name;
            jclass       clazz;
             jobject       obj;//obj的儲存一定要使用NewGlobalRef的方法
            jmethodID setNameMethodID;
            jmethodID getNameMethodID;
            jmethodID transimFromJNIMethodID;
        }fields;
  struct fields_t *Pfield;

  jstring JNISharedTest_stringFromJNI(JNIEnv* env, jobject  obj){
           LOGI("JAVA 呼叫 JNI stringFromJNI");
              string hello = "Hello from JNI";
              return env->NewStringUTF(hello.c_str());
}

  void JNISharedTest_transmitToJNI(JNIEnv * env, jobject thizz,jstring information, jobject obj){
 const char *transmitString = env->GetStringUTFChars(information, NULL);
     LOGI("JAVA 呼叫 JNI transmitToJNI transmitString=%s",transmitString);
     Pfield->obj=env->NewGlobalRef(thizz);//想持久儲存thizz的物件,一定要使用NewGlobalRef 後續也要手動刪除
     //初始化  field id 獲取obj 的name
      jclass clazz=env->FindClass(kClassJniExtern);
      jfieldID  fieldNameID=env->GetFieldID(clazz, "className", "Ljava/lang/String;");
      if (fieldNameID == NULL) {
                    LOGE("can not find className ");
                    return;
                   }

      jobject objName;
      objName=env->GetObjectField(obj,fieldNameID);
      jstring jstringName=(jstring)(objName);
      const char *charName = env->GetStringUTFChars(jstringName, NULL);
      LOGI("JNI 呼叫 JAVA  ExternClass className=%s",charName);


      //呼叫java 層的方法。CallVoidMethod CallObjectMethod
      string message = "JNI 呼叫 JAVA ";
      jstring jMessage=env->NewStringUTF(message.c_str());
       env->CallVoidMethod(Pfield->obj, Pfield->setNameMethodID, jMessage);

      jobject callName= env->CallObjectMethod(Pfield->obj, Pfield->getNameMethodID);
        const char *callNameChar = env->GetStringUTFChars((jstring)callName, NULL);
         LOGI("JNI 呼叫 JAVA  getName=%s",callNameChar);
       jobject calltransim= env->CallObjectMethod(Pfield->obj, Pfield->transimFromJNIMethodID, jMessage,obj);

       const char *calltransimChar = env->GetStringUTFChars((jstring)calltransim, NULL);
        LOGI("JNI 呼叫 JAVA transimFromJNI calltransimChar=%s",calltransimChar);

        //垃圾回收
         env->DeleteLocalRef(callName);
         env->DeleteLocalRef(calltransim);
          env->DeleteLocalRef(objName);
         env->DeleteGlobalRef(Pfield->obj);
}

  void JNISharedTest_native_init(JNIEnv* env,jobject  thizz){
 LOGI("JAVA 呼叫 JNI  native_1init");
   //初始化 方法 field id
   Pfield=&fields;
   jclass clazz = env->FindClass(kClassJniTest);
   Pfield->clazz=clazz;
       if (Pfield->clazz == NULL) {
        LOGE("can not find class");
           return;
       }

Pfield->name = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
if (Pfield->name == NULL) {
            LOGE("can not find name ID");
            return;
           }

Pfield->context = env->GetFieldID(clazz, "testContext", "Landroid/content/Context;");
       if (Pfield->context == NULL) {
              LOGE("can not find context ID");
              return;
             }

Pfield->setNameMethodID = env->GetMethodID(
                                  clazz,
                                  "setName",
                                  "(Ljava/lang/String;)V");
if (Pfield->setNameMethodID == NULL) {
            LOGE("can not find setNameMethodID");
            return;
           }

Pfield->getNameMethodID = env->GetMethodID(
                                        clazz,
                                        "getName",
                                        "()Ljava/lang/String;");

   if (Pfield->getNameMethodID == NULL) {
               LOGE("can not find getNameMethodID");
               return;
              }

 Pfield->transimFromJNIMethodID = env->GetMethodID(
                                          clazz,
                                          "transimFromJNI",
                                          "(Ljava/lang/String;Laai/along/and/jni/ExternClass;)Ljava/lang/String;");


  if (Pfield->transimFromJNIMethodID == NULL) {
              LOGE("can not find transimFromJNIMethodID");
              return;
             }
}
static const JNINativeMethod gMethods[] = {
    {
        "native_init",
        "()V",
        (void *)JNISharedTest_native_init
    },

    {
        "transmitToJNI",
        "(Ljava/lang/String;Laai/along/and/jni/ExternClass;)V",
        (void *)JNISharedTest_transmitToJNI
    },

    {
        "stringFromJNI",
        "()Ljava/lang/String;",
        (void *)JNISharedTest_stringFromJNI
    },
};

static int registerNativeMethods(JNIEnv* env
        , const char* className
        , const JNINativeMethod* gMethod, int numMethods) {
    jclass clazz;
    clazz = env->FindClass(className);
    if (clazz == NULL) {
     LOGI(" JNI 註冊 失敗,沒發現此類");
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethod, numMethods) < 0) {
     LOGI(" JNI 註冊 失敗");
        return JNI_FALSE;
    }
  LOGI(" JNI 註冊 成功");
    return JNI_TRUE;
}
static int register_along_jni(JNIEnv *env)
{
LOGI(" JNI 註冊");
   /// return AndroidRuntime::registerNativeMethods(env,
     //           kClassJniTest, gMethods, NELEM(gMethods));
             return   registerNativeMethods(env, kClassJniTest, gMethods,
                                                 NELEM(gMethods));
}
 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    LOGI(" JNI 載入");
    JNIEnv* env = NULL;
    jint result = -1;
     if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
             LOGE("ERROR: JNI 版本錯誤");

           return JNI_ERR;
        }
 if (register_along_jni(env) == -1) {
         LOGE("ERROR:  JNI_OnLoad 載入失敗");
       return JNI_ERR;
    }
  result = JNI_VERSION_1_6;
  return result;
}
1)靜態方法是根據方法名稱來關聯native與java的。那麼動態註冊方式又是通過什麼方式關聯native層和java層呢?

通過

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */)函式,在java中執行
static {
    System.loadLibrary("jni-shared");

}系統會呼叫JNI_OnLoad方法,所以我們動態關聯java層,只需要重寫這個方法就可以。
這裡需要特別說明一下,如果是在linux 環境編譯的話,可以去掉JNIEXPORT  JNICALL 這兩個關鍵字。

2) 註冊native方法

if (env->RegisterNatives(clazz, gMethod, numMethods) < 0) {
 LOGI(" JNI 註冊 失敗");
    return JNI_FALSE;
}
方法原型是:

 jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
        jint nMethods)
    { return functions->RegisterNatives(this, clazz, methods, nMethods); }

jclass clazz   java的類,跟之前靜態註冊方法一樣。

                   jclass clazz = env->FindClass(kClassJniTest);   kClassJniTest:類名,static const char* const kClassJniTest = "aai/along/and/jni/JNISharedTest";

const JNINativeMethod* methods :

static const JNINativeMethod gMethods[] = {
    {
        "native_init",  //java 層的方法名
        "()V",         //簽名   簽名的規則跟靜態註冊方法相同。
        (void *)JNISharedTest_native_init  //對應的native 層的方法。方法名稱你可以隨便取。
    },

    {
        "transmitToJNI",
        "(Ljava/lang/String;Laai/along/and/jni/ExternClass;)V",
        (void *)JNISharedTest_transmitToJNI
    },

    {
        "stringFromJNI",
        "()Ljava/lang/String;",
        (void *)JNISharedTest_stringFromJNI
    },
};
     jint nMethods  :gMethods陣列有多少個JNINativeMethod 結構體,JNINativeMethod 結構體如下

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

為了比較方便的獲取gMethods中的JNINativeMethod個數,使用如下的方法

#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))  //  x用gMethods替換。

這裡需要特別說明一下:如果是在linux環境
#include "android_runtime/AndroidRuntime.h"
#include "JNIHelp.h"  
增加這兩個標頭檔案,可以使用如下方法註冊
AndroidRuntime::registerNativeMethods(env,kClassMediaScanner, gMethods, NELEM(gMethods));引數跟RegisterNatives方法一樣
至此動態註冊方法講解完畢,是不是覺得很簡單。只需要兩步就完成動態註冊,所以以後還是建議使用動態註冊。

 
 
靜態註冊程式碼連線:https://download.csdn.net/download/bill_xiao/11097666
--------------------- 
作者:Bill_xiao 
來源:CSDN 
原文:https://blog.csdn.net/Bill_xiao/article/details/89095020 
版權宣告:本文為博主原創文章,轉載請