JNI 动态注册和静态注册的详解

3 篇文章 1 订阅

对于未知的技术我们心里总是比较忐忑,但是当你攻下这个技术领域的时候又是一种满满的收获,坚持探索----------Bill  2019.04.08 

  本博文采用问答的方式讲解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

百度网盘:链接:https://pan.baidu.com/s/19Fbvp7HvukwiKuXyv-cOjQ 
提取码:ohce 

 

动态注册代码连接:https://download.csdn.net/download/bill_xiao/11097670

百度网盘:链接:https://pan.baidu.com/s/1XakbRZZAslHLp9rm28bkmw 
提取码:20ur 

 

 

 

 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值