一年之计在于春
金三银四已经要到来,2019的新的开始,作为一个开发人员,你是否面上了自己理想的公司,薪资达到心中理想的高度?

注:原文地址

简介

最近项目中经常使用到JNI,在这里记录总结一下。

面试:如果不准备充分的面试,完全是浪费时间,更是对自己的不负责。

1. JNI 概念

JNI是什么?

        JNI(Java Native
Interface)译为JAVA本地接口调用,它允许Java代码和其他语言(主要是C与C++)写的代码进行交互,简单的说,一种在Java虚拟机控制下执行代码的标准机制。

今天给大家分享下我整理的Java架构面试专题及答案,其中大部分都是大企业面试常问的面试题,可以对照这查漏补缺,当然了,这里所列的肯定不可能覆盖全部方式,不过也希望能对即将找工作的朋友起到一些帮助!

1.1 概念

JNI 全称 Java Native Interface,Java 本地化接口,可以通过 JNI
调用系统提供的 API。操作系统,无论是 Linux,Windows 还是 Mac
OS,或者一些汇编语言写的底层硬件驱动都是 C/C++ 写的。Java和C/C++不同
,它不会直接编译成平台机器码,而是编译成虚拟机可以运行的Java字节码的.class文件
,通过JIT技术即时编译成本地机器码,所以有效率就比不上C/C++代码,JNI技术就解决了这一痛点,JNI
可以说是 C 语言和 Java
语言交流的适配器、中间件
,下面我们来看看JNI调用示意图:来自[JNI开发系列①JNI概念及开发流程

  • 简书]()

图片 1

JNI 调用示意图

JNI技术通过JVM调用到各个平台的API,虽然JNI可以调用C/C++,但是JNI调用还是比C/C++编写的原生应用还是要慢一点,不过对高性能计算来说,这点算不得什么,享受它的便利,也要承担它的弊端。

NDK是什么?

        Android NDK(Android Native Development Kit
)是一套android本地开发工具集合,允许你用像C与C++语言那样实现应用程序的一部分。

Java相关的基础,数据结构与算法,性能调优、设计模式、NDK技术,人工智能,音视频开发以及混合开发等。在这由于文字很多,我总结了Android面试所涉及到的常问范围及常问面试题,以及系统的进阶视频资料,免费分享给大家,省去网上搜集的麻烦。文末有领取!

1.2 JNI 与 NDK 区别

  • JNI:JNI是一套编程接口,用来实现Java代码与本地的C/C++代码进行交互;
  • NDK:
    NDK是Google开发的一套开发和编译工具集,可以生成动态链接库,主要用于Android的JNI开发;

为什么要使用NDK开发?

        1、安全性,java是半解释型语言,很容易被反汇编后拿到源代码文件,我们可以在重要的交互功能使用C或C++语言代替。

        2、效率,C或C++语言比起java来说效率要高出很多。

一、Java篇

2. JNI 作用

  • 扩展:JNI扩展了JVM能力,驱动开发,例如开发一个wifi驱动,可以将手机设置为无限路由;
  • 高效:
    本地代码效率高,游戏渲染,音频视频处理等方面使用JNI调用本地代码,C语言可以灵活操作内存;
  • 复用: 在文件压缩算法 7zip开源代码库,机器视觉
    OpenCV开放算法库等方面可以复用C平台上的代码,不必在开发一套完整的Java体系,避免重复发明轮子;
  • 特殊: 产品的核心技术一般也采用JNI开发,不易破解;

JNI在Android中作用:
JNI可以调用本地代码库(即C/C++代码),并通过 Dalvik
虚拟机与应用层和应用框架层进行交互,Android中JNI代码主要位于应用层和应用框架层;

  • 应用层: 该层是由JNI开发,主要使用标准JNI编程模型;
  • 应用框架层:
    使用的是Android中自定义的一套JNI编程模型,该自定义的JNI编程模型弥补了标准JNI编程模型的不足;

JNI和NDK的区别?

        从工具上说,NDK其实多了一个把.so和.apk打包的工具,而JNI开发并没有打包,只是把.so文件放到文件系统的特定位置上面。

        从编译库说,NDK开发C或C++只能能使用NDK自带的有限的头文件,而使用JNI则可以使用文件系统中带的头文件。

        从编写方式说,它们一样。

1.多线程并发;

补充知识点:

Java语言执行流程:

  • 编译字节码:Java编译器编译 .java源文件,获得.class 字节码文件;
  • 装载类库:使用类装载器装载平台上的Java类库,并进行字节码验证;
  • Java虚拟机:将字节码加入到JVM中,Java解释器和即时编译器同时处理字节码文件,将处理后的结果放入运行时系统;
  • 调用JVM所在平台类库:JVM处理字节码后,转换成相应平台的操作,调用本平台底层类库进行相关处理;

图片 2

Java 语言执行流程

Java一次编译到处执行:
JVM在不同的操作系统都有实现,Java可以一次编译到处运行,字节码文件一旦编译好了,可以放在任何平台的虚拟机上运行;

JNI 相关元素

1、JNI组织结构

        JNI函数表的组成就像C++的虚函数表,虚拟机可以运行多张函数表。JNI接口指针仅在当前线程中起作用,指针不能从一个线程进入另一个线程,但可以在不同的线程中调用本地方法。

2、原始数据

图片 3

3、Java类型和JNI类型对应表

图片 4

Java类型和JNI类型对应表

4、JNI域描述符

图片 5

引用类型则为 L + 该类型类描述符 + 。

数组,其为 : [ + 其类型的域描述符 + 。

多维数组则是 n个[ +该类型的域描述符 , N代表的是几维数组。

String类型的域描述符为 Ljava/lang/String;[ + 其类型的域描述符 + ;int[
] 其描述符为[Ifloat[ ] 其描述符为[FString[ ]
其描述符为[Ljava/lang/String;Object[
]类型的域描述符为[Ljava/lang/Object;int [ ][ ]
其描述符为[[Ifloat[ ][ ] 其描述符为[[F

将参数类型的域描述符按照申明顺序放入一对括号中后跟返回值类型的域描述符,规则如下:
(参数的域描述符的叠加)返回类型描述符。对于,没有返回值的,用V(表示void型)表示。

举例如下:

Java层方法 JNI函数签名String test ( ) Ljava/lang/String;int f (int i,
Object object) (ILjava/lang/Object;)Ivoid set (byte[ ] bytes) ([B)V

  • sleep 和 wait 区别
  • join 的用法
  • 线程同步:synchronized 关键字等
  • 线程通信
  • 线程池
  • 手写死锁

3. 查看 jni.h 文件源码方法

jni.h 头文件就是为了让 C/C++ 类型和 Java
原始类型相匹配的头文件定义。

可以通过点击 Android项目的含有#include <jni.h>的头文件或 C/C++
文件跳转到 jni.h 头文件查看;
如果没有这样的文件的话,可以在 Android Studio 上新建一个类,随便写一个
native 方法,然后点击红色的方法,AS 会自动生成一个对应的 C
语言文件jnitest.c,就可以找到 jni.h 文件了

图片 6

图片 7

或者,通过 javah
命令javah cn.cfanr.testjni.JniTest,就可以生成对应头文件cn_cfanr_testjni_JniTest.h

图片 8

javah 生成的

JNIEnv与JavaVM 概念

1、JNIEnv 概念 : 是一个线程相关的结构体, 该结构体代表了 Java
在本线程的运行环境 ;

2、JNIEnv 与 JavaVM :

     注意区分这两个概念;

(1)JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个;

(2)JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI
中可能有很多个 JNIEnv;

3、JNIEnv 作用 :

(1)调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可以使用 JNIEnv 调用
Java 中的代码;

(2)操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用
JNIEnv 来操作这个 Java 对象;

4、JNIEnv 体系结构

线程相关 : JNIEnv 是线程相关的, 即 在 每个线程中 都有一个 JNIEnv 指针,
每个JNIEnv 都是线程专有的, 其它线程不能使用本线程中的 JNIEnv, 线程 A
不能调用 线程 B 的 JNIEnv;

5、JNIEnv 不能跨线程 :

(1) 当前线程有效 : JNIEnv 只在当前线程有效, JNIEnv 不能在
线程之间进行传递, 在同一个线程中, 多次调用 JNI层方法, 传入的 JNIEnv
是相同的;

(2) 本地方法匹配多JNIEnv : 在 Java 层定义的本地方法,
可以在不同的线程调用, 因此 可以接受不同的 JNIEnv;

JNIEnv 结构 : 由上面的代码可以得出, JNIEnv 是一个指针,
指向一个线程相关的结构, 线程相关结构指向 JNI 函数指针 数组,
这个数组中存放了大量的 JNI 函数指针, 这些指针指向了具体的 JNI 函数;

注意:JNIEnv只在当前线程中有效。本地方法不能将JNIEnv从一个线程传递到另一个线程中。相同的
Java
线程中对本地方法多次调用时,传递给该本地方法的JNIEnv是相同的。但是,一个本地方法可被不同的
Java 线程所调用,因此可以接受不同的 JNIEnv。

更多经验持续更新…

2.Java 中的引用方式,及各自的使用场景3.HashMap
的源码4.GC是什么?如何工作的?回收算法有哪些5.Error 和 Exception
区别?6.反射和注解了解吗?项目中有使用过吗?7.网络相关:

4. JNI 数据类型映射

由头文件代码可以看到,jni.h有很多类型预编译的定义,并且区分了 C 和
C++的不同环境。

#ifdef HAVE_INTTYPES_H
# include <inttypes.h>      /* C99 */
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 */
#else
typedef unsigned char   jboolean;       /* unsigned 8 bits */
typedef signed char     jbyte;          /* signed 8 bits */
typedef unsigned short  jchar;          /* unsigned 16 bits */
typedef short           jshort;         /* signed 16 bits */
typedef int             jint;           /* signed 32 bits */
typedef long long       jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
#endif

/* "cardinal indices and sizes" */
typedef jint            jsize;

#ifdef __cplusplus
/*
 * Reference types, in C++
 */
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
//……

typedef _jobject*       jobject;
typedef _jclass*        jclass;
typedef _jstring*       jstring;
typedef _jarray*        jarray;
typedef _jobjectArray*  jobjectArray;
typedef _jbooleanArray* jbooleanArray;
//……

#else /* not __cplusplus */

/*
 * Reference types, in C.
 */
typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;
typedef jarray          jbooleanArray;
//……

#endif

当是C++环境时,jobject, jclass, jstring, jarray
等都是继承自_jobject类,而在 C
语言环境是,则它的本质都是空类型指针typedef void* jobject;

  • http 状态码
  • http 与 https 的区别?https 是如何工作的?

4.1 基本数据类型

下图是Java基本数据类型和本地类型的映射关系,这些基本数据类型都是可以直接在
Native 层直接使用的

图片 9

基本数据类型映射

8.Java 中 LRUCache 是如何实现的?为什么要用 LinkedHashmap?9.设计模式:

4.2 引用数据类型

另外,还有引用数据类型和本地类型的映射关系:

图片 10

引用数据类型映射

需要注意的是,

  • 1)引用类型不能直接在 Native 层使用,需要根据 JNI
    函数进行类型的转化后,才能使用;
  • 2)多维数组(含二维数组)都是引用类型,需要使用 jobjectArray
    类型存取其值;

例如,二维整型数组就是指向一位数组的数组,其声明使用方式如下:

      //获得一维数组的类引用,即jintArray类型  
    jclass intArrayClass = env->FindClass("[I");   
    //构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为length,类型为 jsize
    jobjectArray obejctIntArray  =  env->NewObjectArray(length ,intArrayClass , NULL); 
  • 手写单例,volitate 关键字的原理
  • 手写生产者消费者模式
  • 项目中都使用过哪些设计模式?
  • 编码常遵循的设计原则:单一职责、开闭原则、里氏替换等

4.3 方法和变量 ID

同样不能直接在 Native 层使用。当 Native 层需要调用 Java
的某个方法时,需要通过 JNI 函数获取它的 ID,根据 ID 调用 JNI
函数获取该方法;变量的获取也是类似。ID 的结构体如下:

struct _jfieldID;                       /* opaque structure */
typedef struct _jfieldID* jfieldID;     /* field IDs */

struct _jmethodID;                      /* opaque structure */
typedef struct _jmethodID* jmethodID;   /* method IDs */

10.ArrayList 和 LinkedList 区别?

5. JNI 描述符

二、Android

5.1域描述符

1)基本类型描述符

下面是基本的数据类型的描述符,除了 boolean 和 long 类型分别是 Z 和 J
外,其他的描述符对应的都是Java类型名的大写首字母。另外,void 的描述符为
V

图片 11

基本类型描述符

2)引用类型描述符

一般引用类型描述符的规则如下,注意不要丢掉“;”

L + 类描述符 + ;

如,String 类型的域描述符为:

Ljava/lang/String;

数组的域描述符特殊一点,如下,其中有多少级数组就有多少个“[”,数组的类型为类时,则有分号,为基本类型时没有分号

[ + 其类型的域描述符

例如:

int[]    描述符为 [I
double[] 描述符为 [D
String[] 描述符为 [Ljava/lang/String;
Object[] 描述符为 [Ljava/lang/Object;
int[][]  描述符为 [[I
double[][] 描述符为 [[D

对应在 jni.h 获取 Java 的字段的 native 函数如下,name为 Java
的字段名字,sig 为域描述符

//C
jfieldID    (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
jobject     (*GetObjectField)(JNIEnv*, jobject, jfieldID);
//C++
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    { return functions->GetFieldID(this, clazz, name, sig); }
jobject GetObjectField(jobject obj, jfieldID fieldID)
    { return functions->GetObjectField(this, obj, fieldID); }

具体使用,后面会讲到

1.源码相关:

5.2 类描述符

类描述符是类的完整名称:包名+类名,java 中包名用 . 分割,jni 中改为用 /
分割
如,Java 中 java.lang.String 类的描述符为 java/lang/String
native 层获取 Java 的类对象,需要通过 FindClass() 函数获取, jni.h
的函数定义如下:

//C
jclass  (*FindClass)(JNIEnv*, const char*);
//C++
jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }

字符串参数就是类的引用类型描述符,如 Java 对象
cn.cfanr.jni.JniTest,对应字符串为Lcn/cfanr/jni/JniTest; 如下:

jclass jclazz = env->FindClass("Lcn/cfanr/jni/JniTest;");

详细用法的例子,后面会讲到。

  • Activity 启动过程
  • 事件分发源码,以及由此衍生的事件拦截如何实现
  • 消息机制:Handler
    源码(结合Looper、MessageQueue),以及取不到消息时会怎样?
  • View.post 为什么可以拿到宽高?

5.3 方法描述符

方法描述符需要将所有参数类型的域描述符按照声明顺序放入括号,然后再加上返回值类型的域描述符,其中没有参数时,不需要括号,如下规则:

(参数……)返回类型

例如:

  Java 层方法   ——>  JNI 函数签名
String getString()  ——>  Ljava/lang/String;
int sum(int a, int b)  ——>  (II)I
void main(String[] args) ——> ([Ljava/lang/String;)V

另外,对应在 jni.h 获取 Java 方法的 native 函数如下,其中 jclass
是获取到的类对象,name 是 Java 对应的方法名字,sig
就是上面说的方法描述符

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

不过在实际编程中,如果使用 javah 工具来生成对应的 native
代码,就不需要手动编写对应的类型转换了。

2.自定义 View;

6. JNIEnv 分析

JNIEnv 是 jni.h
文件最重要的部分,它的本质是指向函数表指针的指针(JavaVM也是),函数表里面定义了很多
JNI 函数,同时它也是区分 C 和
C++环境的(由上面介绍描述符时也可以看到),在 C 语言环境中,JNIEnv
strut JNINativeInterface*的指针别名。

struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;

#if defined(__cplusplus)  
typedef _JNIEnv JNIEnv;   //C++中的 JNIEnv 类型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;  //C语言的 JNIEnv 类型
typedef const struct JNIInvokeInterface* JavaVM;
#endif
  • 流程:onMeasure, onLayout, onDraw
  • onMeasure 中的 MeasureSpec 是如何计算的?

6.1 JNIEnv 特点

  • JNIEnv 是一个指针,指向一组 JNI 函数,通过这些函数可以实现 Java 层和
    JNI 层的交互,就是说通过 JNIEnv 调用 JNI 函数可以访问 Java
    虚拟机,操作 Java 对象;
  • 所有本地函数都会接收 JNIEnv 作为第一个参数;(不过 C++ 的JNI
    函数已经对 JNIEnv 参数进行了封装,不用写在函数参数上)
  • 用作线程局部存储,不能在线程间共享一个 JNIEnv 变量,也就是说
    JNIEnv 只在创建它的线程有效,不能跨线程传递;相同的 Java
    线程调用本地方法,所使用的 JNIEnv 是相同的,一个 native
    方法不能被不同的 Java 线程调用;

发表评论

电子邮件地址不会被公开。 必填项已用*标注