1.3 Native方法注册


Native方法注册分为两种:

  • 静态注册

多用于NDK开发

  • 动态注册

多用于Framework层开发

下面我们用实际的例子对两种注册做区分及了解。

1.静态注册

我们在Android Studio一个项目中

右键本项目->new->module->Java Library

LibraryName = media

Java class name = MediaRecorder.java,创建成功后,打开MediaRecorder.class,并学着MediaRecorder编写:

接着我们对MediaRecorder.java进行编译和生成JNI方法,我们进入 media/src/main/java中,在 terminal命令行中输入:

javac com/rikkatheworld/media/MediaRecorder.java

javah -jni com.rikkatheworld.media.MediaRecorder

最后会产生了一个 MediaReorder.class和一个 com_rikkatheworld_media_MediaRecorder.h头文件。

上面可能这里可能会有点坑,可以看我 Android音视频开发入门(5)使用LAME编码一个PCM文件第一节怎么去搭建一个JNI项目,这里我就不多赘述。

我们来看下这个被编译出来的头文件的内容:

// com_rikkatheworld_media_MediaRecorder.h

#include <jni.h>

#ifndef _Included_com_rikkatheworld_media_MediaRecorder

#define _Included_com_rikkatheworld_media_MediaRecorder

#ifdef __cplusplus

extern “C” {

#endif

// 1

JNIEXPORT void JNICALL Java_com_rikkatheworld_media_MediaRecorder_native_1init

(JNIEnv *, jclass);

JNIEXPORT void JNICALL Java_com_rikkatheworld_media_MediaRecorder_start

(JNIEnv *, jobject);

#ifdef __cplusplus

}

#endif

#endif

被编译出来的头文件就是我们Java层在JNI层中对应的类。

注释1中的方法:以Java开头,说明这个方法在Java中调用JNI方法的,后面的名称是以 包名+类名+方法名组成格式,还发现 native_1init()中多了个“1”,这是因为Java声明的native_init()中有 “_”下划线,转换成到native语言中,就变成了 “_1”。

其中 JNIEnv是Native世界中 Java环境的代表,通过 JNIEnv *这个指针,就可以在Native层中访问Java层的代码并进行操作了,它只在创建它的线程中有效,不能跨线程传递。

jclass是JNI型数据,对应Java的 java.lang.Class的实例,是所有对象的父类。

上面这两个数据类型是JNI层常用且关键的数据,后面会对其做介绍。

我们在 Java层中调用 native_init()后,就会从 JNI中寻找 Java_com_rikkatheworld_media_MediaRecorder_native_1init()的函数,如果没有,就会报错,如果找到就会为这两个方法建立一个关联,它就会保存一个JNI的函数指针,即一个指针指向了这个函数,等下一次再调用这个方法时,这个指针所指向的函数就能立马被调用。这就是静态注册。

静态注册总结:

根据方法名,将Java方法和JNI函数建立联系,它的缺点是:

  • JNI层的函数名非常长,它是 Java+包名+类名+方法名

  • 声明Native方法的类需要用 javah生成头文件

  • 第一次调用时,由于JNI层的指针还未指向对应函数,所以它要一个一个找直到找到,所以每第一次调用新的Native方法时,效率并不会太高

2.动态注册

静态注册是Java的Native通过方法指针来与JNI进行关联的,如果Java的Native方法一开始就知道它在JNI中对应的函数指针,就可以避免上述的缺点,这就是动态注册。

JNI中有一种结构来记录Java的Native方法和JNI方法的关联联系,它就是 JNINativeMethod,它在jni.h中的源码是这样的:

// jni.h

typedef struct {

const char* name; //Java方法中的名字

const char* signature; //Java方法的简明信息

void* fnPtr; //JNI中对应的方法指针

} JNINativeMethod;

我们应用开发中使用的 MediaRecorder就是采用动态注册的方式,我们来看看其JNI层是怎么来使用这个 JNINativeMethod的:

// android_media_MediaRecorder.cpp

static const JNINativeMethod gMethods[] = {

{“start”, “()V”, (void *)android_media_MediaRecorder_start},

{“stop”, “()V”, (void *)android_media_MediaRecorder_stop},

{“pause”, “()V”, (void *)android_media_MediaRecorder_pause},

{“resume”, “()V”, (void *)android_media_MediaRecorder_resume},

{“native_reset”, “()V”, (void *)android_media_MediaRecorder_native_reset},

{“release”, “()V”, (void *)android_media_MediaRecorder_release},

{“native_init”, “()V”, (void *)android_media_MediaRecorder_native_init}…

};

在这个JNI文件中,定义了一个 JNINativeMethod数组,里面声明了这些关联的方法

比如最后一行的:

"native_init":在Java中的方法

"()v" :Java层的签名

"android_media_MediaRecorder_native_init" :为对应JNI层的函数。

这里只声明一个 JNINativeMethod数组并不就完全的注册了这些函数,这个类还要去编写一个注册函数才有用,这个类中定义了一个函数来完成这个任务,它就是 register_android_media_MediaRecorder()

// android_media_MediaRecorder.cpp

// This function only registers the native methods, and is called from

// JNI_OnLoad in android_media_MediaPlayer.cpp

int register_android_media_MediaRecorder(JNIEnv *env)

{

return AndroidRuntime::registerNativeMethods(env,

“android/media/MediaRecorder”, gMethods, NELEM(gMethods));

}

从英文注释中,可以看出这个方法用来注册native方法,并且它只在 JNI_OnLoad()中被调用,这个方法我们听名字就知道,肯定是在 System.loadLibrary()会调用其里面的这个方法。因为在多媒体框架中都要进行 JNINativeMethod数组的注册,因此,注册函数就统一放在 JNI_OnLoad()中。

也就是说 JNI_OnLoad()是在 android_mediaMediaPlayer.cpp中的,我们来看看这个方法:

// android_media_MediaPlayer.cpp

jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)

{

JNIEnv* env = NULL;

jint result = -1;

if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {

ALOGE(“ERROR: GetEnv failed\n”);

goto bail;

}

assert(env != NULL);

// 1

if (register_android_media_MediaRecorder(env) < 0) {

ALOGE(“ERROR: MediaRecorder native registration failed\n”);

goto bail;

}

result = JNI_VERSION_1_4;

bail:

return result;

}

JNI_OnLoad()中调用了整个多媒体框架的注册 JNINativeMethod()数组的函数。

除了上面说到的 MediaRecorder,还有我们熟知的 MediaPlayer、MediaScanner等框架,都放在这里。

在注释1中就是调用了 register_android_media_MediaRecorder()

我们回到 register_android_media_MediaRecorder()中,它就是执行了 AndroidRuntime::registerNativeMethod()这个方法,我们看看这个方法:

// AndroidRuntime.cpp

/static/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,

const char* className, const JNINativeMethod* gMethods, int numMethods)

{

return jniRegisterNativeMethods(env, className, gMethods, numMethods);

}

它又返回了 jniRegisterNativeMethods(),它被定义在 JNIHelp.cpp中:

// JNIHelp.cpp

extern “C” int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,

const JNINativeMethod* gMethods, int numMethods)

{

// 1

if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {

char* tmp;

const char* msg;

if (asprintf(&tmp, “RegisterNatives failed for ‘%s’; aborting…”, className) == -1) {

msg = “RegisterNatives failed; aborting…”;

} else {

msg = tmp;

}

e->FatalError(msg);

}

return 0;

}

注释1可以看出,最终通过调用JNIEnv.RegisterNatives()来完成JNI的注册。后面就不讲了,因为会扯到JNIEnv,等了解了JNIEnv,再去看这些代码也不迟。

动态注册总结:

通过声明一个 JNINativeMethod数组,来把对应的函数名称放到这个数组中,通过JNIEnv的的注册,来实现函数的一一对应,这样做相比于静态注册:

  • 直观明了

想要注册的方法全都放在一个数组里,统一管理并且一目了然

  • 效率高

指针在一开始就指向了对应的函数,这样加快了效率。

2.数据类型的转换

==========================================================================

我们依旧拿上面 MediaRecorder为例,看下 android_media_MediaRecorder_start()

// adnroid_media_MediaRecorder.cpp

static void

android_media_MediaPlayer_start(JNIEnv *env, jobject thiz)

{

}

android_media_MediaRecorder_start() 的第二个参数 jobject,我们不是很清楚它是做什么的。

通过JNI,我们在Java层的数据经过了转换,变成了我们看不懂的数据。

我们需要了解JNI中的 jintjstringjclass这些数据类型是啥,我们就要搞清楚它们是这么来的。Java数据类型分成基本类型和引用类型,JNI也分成了这两个部分。

etails/104891791)2. 1 JNI中的基本数据类型


基本数据类型的转换如下表所示,可以看到除了 void,其他数据类型只在前面加一个“j”,第三列是签名格式。之前在 JNINativeMethod中出现过,它就是一个类型的简写:

| Java | Native | Signature |

| — | — | — |

| byte | jbyte | B |

| char | cchar | C |

| double | jdouble | D |

| float | jfloat | F |

| int | jint | I |

| short | jshort | S |

| long | jlong | J |

| boolean | jboolean | Z |

| void | void | V |

2. 2 JNI中的引用数据类型


数组的JNI层数据都以Array结尾,签名都以“[”开头,有些数据类型以“;”结尾:

| Java | Native | Signature |

| — | — | — |

| 所有对象Object | jobject | L+classname +; |

| Class | jclass | Ljava/lang/Class; |

| Throwable | jthrowable | Ljava/lang/Throwable; |

| String | jstring | Ljava/lang/String |

| Object[] | jobject[]Array | [L+classname +; |

| byte[] | jbyteArray | [B |

| char[] | jcharArray | [C |

| double[] | jdoubleArray | [D |

他们也有继承关系,比如所有的类型都继承 jobject,所有的数组都继承自 jarray

再来看一个MediaRecorder的例子:

// MediaRecorder.java

private native void _setOutputFile(FileDescriptor fd) throws IllegalStateException, IOException;

// android_media_MediaRecorder.cpp

android_media_MediaRecorder_setNextOutputFileFD(JNIEnv *env, jobject thiz, jobject fileDescriptor)

{

}

可以看到 FileDescriptor就转成了 jobject

3.方法签名

=======================================================================

方法签名的意义是什么:便于注册后找到对应的函数

因为Java中有重载机制,即同返回类型+同类名,如果使用动态注册,在JNINativeMethod数组中,只放上两个名字(JNI层和JAVA层函数名),到时候如果有重载函数,JNI指针是无法区分的。

这个时候就出现了方法签名, 它会带上参数,比如 (jlong)V,就是输入的是一个long型,返回的是一个void。

这样即使重载,也能通过参数来区分。这就是方法签名的意义。

Java中用 javap来自动生成签名,这里就不再演示了。

4.JNIEnv

=========================================================================

JNIEnv是只有JNI才有的,它是Java环境的代表,通过 JNIEnv *指针,就可以在Native层中访问Java层的代码。

它是线程私有的,so每个线程都有自己的 JNIEnv *,它的作用有以下两点:

  1. 调用Java的方法

  2. 操作Java中的变量和对象

先来看下JNIEnv的定义,它在jni.h中被声明:

// jni.h

#if defined(__cplusplus)

//1 c++中JNIEnv类型

typedef _JNIEnv JNIEnv;

typedef _JavaVM JavaVM;

#else

//2 C中JNIEnv类型

typedef const struct JNINativeInterface* JNIEnv;

typedef const struct JNIInvokeInterface* JavaVM;

#endif

首先先判断当前是什么语言,_cplusplus就是C++,如果当前语言是C++,JNIEnv就是 _JNIEnv类型,如果是C,就是 JNIInvokeInterface*这个类型。

这里的定义中我们也看到了JVM,JavaVM就是JVM在Native层的代表。通过 JavaVM.AttachCurrentThread()可以获取这个线程的JNIEnv,这样就可以在不同的线程调用Java方法了。

我们来看下 _JNIEnv这个C++中定义的结构体:

// jni.h

struct _JNIEnv {

#if defined(__cplusplus)

jclass FindClass(const char* name)

{ return functions->FindClass(this, name); }

jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)

{ return functions->GetMethodID(this, clazz, name, sig); }

jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)

{ return functions->GetFieldID(this, clazz, name, sig); }

}

这里定义了很多的函数,之前讲过的动态注册就在这里面。

这里列出了三个常用的方法:

  1. FindClass

用来找到Java中指定名称的类

  1. GetMethodID

用来得到Java中的方法

  1. GetFieldID

用来得到Java中的成员变量。

这些函数都调用了 JNINativeInterface的方法,而JNINativeInterface对每个函数都保存了一个指针,这样就能够定位到虚拟机中的JNI函数表,从而实现了JNI层在虚拟机中的函数调用了。这样JNI就可以访问Java层的代码了。

4.1 jfieldID和jmethodID


再来看看 native_init()中的方法:

static void

android_media_MediaRecorder_native_init(JNIEnv *env)

{

jclass clazz;

clazz = env->FindClass(“android/media/MediaRecorder”);

if (clazz == NULL) {

return;

}

fields.context = env->GetFieldID(clazz, “mNativeContext”, “J”);

if (fields.context == NULL) {

return;

}

fields.surface = env->GetFieldID(clazz, “mSurface”, “Landroid/view/Surface;”);

if (fields.surface == NULL) {

作者2013年从java开发,转做Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

参与过不少面试,也当面试官 面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我整理了一份阿里P7级别的最系统的Android开发主流技术,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你想深入系统学习Android开发,成为一名合格的高级工程师,可以收藏一下这些Android进阶技术选型

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

Java语言与原理;
大厂,小厂。Android面试先看你熟不熟悉Java语言

高级UI与自定义view;
自定义view,Android开发的基本功。

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

NDK开发;
未来的方向,高薪必会。

前沿技术;
组件化,热升级,热修复,框架设计

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多,CodeChina上可见;

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!
成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

[外链图片转存中…(img-4mOsEW4T-1647530465013)]

Java语言与原理;
大厂,小厂。Android面试先看你熟不熟悉Java语言

[外链图片转存中…(img-b3g339Ex-1647530465014)]

高级UI与自定义view;
自定义view,Android开发的基本功。

[外链图片转存中…(img-UEOSZcv0-1647530465014)]

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

[外链图片转存中…(img-RiTvUIc7-1647530465015)]

NDK开发;
未来的方向,高薪必会。

[外链图片转存中…(img-PZMZO8fa-1647530465015)]

前沿技术;
组件化,热升级,热修复,框架设计

[外链图片转存中…(img-Bekv6rUf-1647530465015)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多,CodeChina上可见;

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!

JNI原理学习,美团Android面试题相关推荐

  1. Android面试题大全(中高级)

    目录 Android Android主流框架 JAVA部分 设计模式 网络 其他 Android 1.synchronized和lock的区别 答: 详解synchronized与Lock的区别与使用 ...

  2. Android JNI的学习经历

    Android JNI的学习经历 理解JNI Java测MediaScanner的分析 静态注册: 静态方法是根据函数名字建立java 和jni之间的关联 动态注册的原理:java native 函数 ...

  3. Android JNI原理分析

    引言:分析Android源码6.0的过程,一定离不开Java与C/C++代码直接的来回跳转,那么就很有必要掌握JNI,这是链接Java层和Native层的桥梁,本文涉及相关源码: frameworks ...

  4. Android 6.0 JNI原理分析 和 Linux系统调用(syscall)原理

    JNI原理 引言:分析Android源码6.0的过程,一定离不开Java与C/C++代码直接的来回跳转,那么就很有必要掌握JNI,这是链接Java层和Native层的桥梁,本文涉及相关源码: fram ...

  5. Android系统的JNI原理分析(二)- 数据类型转换和方法签名

    声明 前阶段在项目中使用了Android的JNI技术,在此文中做些技术知识总结. 本文参考了一些书籍的若干章节,比如<Android进阶解密-第9章-JNI原理>.<深入理解Andr ...

  6. Android架构——ViewModel原理学习总结

    本文是楼主学习ViewModel 源码的一些总结,感觉ViewModel的源码是Android 三大架构中 最容易理解的一个了.本文ViewModel基于版本androidx.lifecycle:li ...

  7. 安卓开发淘宝抢购界面!史上最全的Android面试题集锦,附带学习经验

    前言 这是"拔剑金九银十"的第二篇文章,本文主要针对3年以上的Android开发者进阶面试中高级开发工程师而整理. 希望可以对你们有所帮助.不多废话,进入正题. 目录: Java中 ...

  8. Android面试送分题:最新Android面试题整理,详细的Android学习指南

    前言 之前老是看着搞Java的朋友炫耀他的核心知识点笔记,真的,我内心毫无波澜,只有一点点酸 其实Android开发也有很多知识点,我一直以来就想要一份Android核心知识点笔记来帮助自己查漏补缺, ...

  9. JNI学习笔记:JNI原理

    一.JNI数据类型 在上节中出现了jstring类型,这里说明一下,JNI有自己的原始数据类型和数据引用类型. 二.JNI原理 Java语言的执行环境是Java虚拟机(JVM),JVM其实是主机环境中 ...

最新文章

  1. php 插入表,php 向数据库表中插入数据
  2. 1亿组图文对,填补中文开源多模态数据集空白!还附带基础模型,来自华为诺亚方舟实验室...
  3. (转载)MyEclipse github
  4. Css fade()函数降低颜色变量透明度
  5. BZOJ-1951 古代猪文 (组合数取模Lucas+中国剩余定理+拓展欧几里得+快速幂)...
  6. 计算机维修实训报告模板,[计算机维修实验报告模板.doc
  7. [JavaScript] Cookie,localStorage,sessionStorage概述
  8. FinSpy 发布 Mac 和 Linux OS 版本攻击埃及组织机构
  9. CCF NOI1071 Pell数列
  10. cmd使用SBT构建scala项目
  11. [算法竞赛入门]WERTYU
  12. 一窥Memory测试算法及自我修复机制
  13. 如何在win10桌面便签日历中显示法定节假日安排?
  14. 微信/支付宝网页扫码授权
  15. iOS界面--Tom猫的实现
  16. Springboot中Feign的使用方法
  17. 中国星际争霸历史回顾(重写版)
  18. ELF文件格式, ELF文件是什么,里面包含什么内容
  19. CLEARink 和天马联合宣布签署谅解备忘录
  20. 5000月薪与50000月薪的Linux运维的区别

热门文章

  1. ios系统更新提示没有连接到服务器上,ios 更新时不再连接到无线网
  2. ESM(ECMAScript Moudle),esbuild是什么
  3. 推荐广告之-MLR学习
  4. 基于php家装设计平台交易管理系统
  5. Error from server error dialing backend remote error tls internal error
  6. Shell 十六进制转换成二进制的方案
  7. 快速搭建 物联网电力检测,web SCADA 工控组态
  8. Linux--Redis6.0 安装与使用简介
  9. Win10启动64位IE浏览器
  10. 多肽合成:二肽Cbz-Pro-Ala,14030-00-3