Linux下 JNI的运用

学习 Android 其间触及对 JNI 的运用;JNI的运用关于 Android 来说又是十分的重要和关键。那么到底 Java 到底是怎么调用 C/C++ 的,

Android 源码中的 JNI,到底是如何使用的?

下面是十分简单的计算器源码,只是用来熟悉JNI的基本语法,其间我自己碰到过的一个问题

便是LoadLibrary()调用之后,程序直接溃散,最开始以为是模拟器是x86的形式,而编译的so文件是arm的形式,可是将模拟器改成arm之后还是溃散,最终无奈在自己手机上测验也是如此,一打开就直接溃散,在网上能找到的各种办法都试了,最终发现是so命名的问题

咱们经常会写如下的代码输出日志:

Log.d(TAG,”Debug Log”);

咱们就以Log体系为例来学习JNI。

咱们先看一下Log类的内容,在android源码的\frameworks\base\core\java\android\Log.java文件中

/**
   * Send a {@link #DEBUG} log message.
   * @param tag Used to identify the source of a log message.  It usually identifies
   *     the class or activity where the log call occurs.
   * @param msg The message you would like logged.
   */
  public static int d(String tag, String msg) {
    return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
​
​
  /** @hide */ public static final int LOG_ID_MAIN = 0;
  /** @hide */ public static final int LOG_ID_RADIO = 1;
  /** @hide */ public static final int LOG_ID_EVENTS = 2;
  /** @hide */ public static final int LOG_ID_SYSTEM = 3;
​
  /** @hide */ public static native int println_native(int bufID,
      int priority, String tag, String msg);

能够看到所有的Log的办法都调用了native 的println_native办法,在android源码中的\frameworks\base\core\jni\android_until_Log.cpp文件中完成:

/*
 * In class android.util.Log:
 *  public static native int println_native(int buffer, int priority, String tag, String msg)
 */
/*
*JNI办法增加了JNIEnv和jobject两参数,其他的参数和返回值只是将Java层参数映**射成JNI的数据类型,然后经过调用本地库和JNIEnv提供的JNI函数处理数据,最终返给java层
*/
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
    jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
  const char* tag = NULL;
  const char* msg = NULL;
​
  if (msgObj == NULL) {  //异常处理
    jclass npeClazz;
​
    npeClazz = env->FindClass("java/lang/NullPointerException");
    assert(npeClazz != NULL);
    //抛出异常
    env->ThrowNew(npeClazz, "println needs a message");
    return -1;
   }
​
  if (bufID < 0 || bufID >= LOG_ID_MAX) {
    jclass npeClazz;
​
    npeClazz = env->FindClass("java/lang/NullPointerException");
    assert(npeClazz != NULL);
​
    env->ThrowNew(npeClazz, "bad bufID");
    return -1;
   }
​
  if (tagObj != NULL)
    tag = env->GetStringUTFChars(tagObj, NULL);
  msg = env->GetStringUTFChars(msgObj, NULL);
    //向内核写入日志
  int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
​
  if (tag != NULL)
    env->ReleaseStringUTFChars(tagObj, tag);
  env->ReleaseStringUTFChars(msgObj, msg);
​
  return res;
}

至此,JNI层现已完成了在java层声明的Native层办法,可是这两个又是怎么联系到一同的呢?咱们再看android_util_Log.cpp的源码

/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] = {
  /* name, signature, funcPtr */
   { "isLoggable",   "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
   {"println_native","(IILjava/lang/String;Ljava/lang/String;)I",(void*)android_util_Log_println_native },
};

在\dalvik\libnativehelper\include\nativehelper\Jni.h文件中有JNINativeMethod 的界说:

typedef struct {
  const char* name;    //java层声明的native函数的函数名
  const char* signature;  //Java函数的签名
  void*    fnPtr;    //函数指针,指向JNI层的完成办法
} JNINativeMethod;

咱们能够看到printIn_native的对应联系:

{"println_native","(IILjava/lang/String;Ljava/lang/String;)I",(void*)android_util_Log_println_native }

Java层声明的函数名是print_native

Java层声明的native函数的签名为(IILjava/lang/String;Ljava/lang/String;)I

JNI办法完成办法的指针为(void*)android_util_Log_println_native

咱们知道了java层和JNI层的映射联系,可是怎么把这种联系告知Dalvik虚拟机呢?,咱们继续看android_util_Log.cpp的源码

int register_android_util_Log(JNIEnv* env)
{
  jclass clazz = env->FindClass("android/util/Log");
​
  if (clazz == NULL) {
    LOGE("Can't find android/util/Log");
    return -1;
   }
  
  levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
  levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
  levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
  levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
  levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
  levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
        
  return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
}
​
}; // namespace android

这个函数的最终调用了AndroidRuntime::registerNativeMethods函数

能够在\frameworks\base\core\jni\AndroidRuntime.cpp 中找到registerNativeMethods的完成

/*
 * Register native methods using JNI.
 */
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
  const char* className, const JNINativeMethod* gMethods, int numMethods)
{
  return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

他的内部完成只是调用了jniRegisterNativeMethods ()。

在\dalvik\libnativehelper\JNIHelp.c中jniRegisterNativeMethods函数的完成

/*
 * Register native JNI-callable methods.
 *
 * "className" looks like "java/lang/String".
 */
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
  const JNINativeMethod* gMethods, int numMethods)
{
  jclass clazz;
​
  LOGV("Registering %s natives\n", className);
  clazz = (*env)->FindClass(env, className);
  if (clazz == NULL) {
    LOGE("Native registration unable to find class '%s'\n", className);
    return -1;
   }
​
  int result = 0;
  if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
    LOGE("RegisterNatives failed for '%s'\n", className);
    result = -1;
   }
​
   (*env)->DeleteLocalRef(env, clazz);
  return result;
}

这儿是调用了JNIEnv的RegisterNatives函数,能够阅读函数的注释,注册一个类的Native办法。现已告知了虚拟机java层和native层的映射联系。

/*
 * Register one or more native functions in one class.
 *
 * This can be called multiple times on the same method, allowing the
 * caller to redefine the method implementation at will.
 */
static jint RegisterNatives(JNIEnv* env, jclass jclazz,
  const JNINativeMethod* methods, jint nMethods)
{
  JNI_ENTER();
​
  ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz);
  jint retval = JNI_OK;
  int i;
​
  if (gDvm.verboseJni) {
    LOGI("[Registering JNI native methods for class %s]\n",
      clazz->descriptor);
   }
​
  for (i = 0; i < nMethods; i++) {
    if (!dvmRegisterJNIMethod(clazz, methods[i].name,
        methods[i].signature, methods[i].fnPtr))
     {
      retval = JNI_ERR;
     }
   }
​
  JNI_EXIT();
  return retval;
}

其作用是向clazz参数指定的类注册本地办法,这样,虚拟机就能得到Java层和JNI层之间的对应联系,就能够完成java和native层代码的交互了。咱们注意到在Log体系的实例中,JNI层完成办法和注册办法中都运用了JNIEnv这个指针,经过它调用JNI函数,拜访Dalvik虚拟机,进而操作Java对象

咱们能够在\Dalvik\libnativehelper\include\nativehelper\jni.h中找到JNIEnv的界说:

struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)  //界说了C++
typedef _JNIEnv JNIEnv;  //C++中的JNIEnv的类型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

这儿只是用关键字typedef关键字做了类型界说,那么_JNIEnv和JNINativeInterface的界说

/*
 * C++ object wrapper.
 *
 * This is usually overlaid on a C struct whose first element is a
 * JNINativeInterface*.  We rely somewhat on compiler behavior.
 */
struct _JNIEnv {
  /* do not rename this; it does not seem to be entirely opaque */
  const struct JNINativeInterface* functions;
​
#if defined(__cplusplus)
​
  jint GetVersion()
   { return functions->GetVersion(this); }
​
  jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
    jsize bufLen)
   { return functions->DefineClass(this, name, loader, buf, bufLen); }
​
  jclass FindClass(const char* name)
   { return functions->FindClass(this, name); }
​
  jmethodID FromReflectedMethod(jobject method)
   { return functions->FromReflectedMethod(this, method); }
​
 ………..

_JNIEnv只是对const struct JNINativeInterface类型的封装,并间接调用const struct JNINativeInterface上界说的办法

/*
 * Table of interface function pointers.
 */
struct JNINativeInterface {
……
 jclass    (*FindClass)(JNIEnv*, const char*);
 jboolean   (*IsSameObject)(JNIEnv*, jobject, jobject);
……
};

这儿才真实触及JNI函数的调用,也只是一个接口

可是咱们能够得出如下定论:

C++中: JNIEnv便是struct _JNIEnv。JNIEnv *env 等价于 struct _JNIEnv env ,在调用JNI函数的时候,只需要env->FindClass(JNIEnv,const char ),就会间接调用JNINativeInterface结构体里面界说的函数指针,而无需首先对env解引用。

C中: JNIEnv便是const struct JNINativeInterface *。JNIEnv env 等价于const struct JNINativeInterface ** env,因而要得到JNINativeInterface结构体里面的函数指针就必须先对env解引用得到( env),得到const struct JNINativeInterface *,才是真实指向JNINativeInterface结构体的指针,然后再经过它调用具体的JNI函数,因而需要这样调用:

(env)->FindClass(JNIEnv,const char*)。

尾述

最终这儿放上一张大佬推荐的 音视频开发思想脑图,并依据脑图收拾了一份体系学习的资料笔记和配套视频音视频开发技能相关的知识点笔记中都有具体的解读,并且把每个技能点收拾成了 PDF 文档(知识头绪 + 诸多细节)有需要的小伙伴:能够在评论区下方留言或者私信发送 “脑图”“笔记” 就能够免费领取

音视频开发思想导图

Android 源码中的 JNI,到底是如何使用的?

好了,以上便是今天要共享的内容,我们觉得有用的话,能够点赞共享一下;如果文章中有什么问题欢迎我们指正;欢迎在评论区或后台讨论哈~