Android系统面经(2/20)深入解析JNI

牛客高级系列专栏:

安卓(安卓系统开发也要掌握)


嵌入式


  • 本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人从嵌入式Linux转Android系统开发过程中对常见安卓系统开发面试题的理解;
  • 1份外卖价格助您提高安卓面试准备效率,为您面试保驾护航!!

正文开始⬇

JNI开发是安卓开发必备技能,实用性100分,不过校招或者基础的社招面试时,面试官一般会问有没有用过JNI,用过就加分,讲述一下曾经使用的场景,没用过就下一个问题。所以对于基础面试级别的同学,知道简单的概念和使用方法就行了。本文只分析JNI的基础知识点,更多的语法细节的介绍不属于本文范围。面试官可能会问:

  1. 阐述你对JNI的理解⭐⭐⭐⭐⭐
  2. 使用JNI有什么优缺点⭐⭐⭐⭐⭐
  3. 什么是JNI?具体说说如何实现Java与C++的互调⭐⭐⭐⭐⭐
  4. 什么是NDK?为什么要使用NDK?⭐⭐
  5. JNI开发的一般步骤是?⭐⭐⭐⭐
  6. NI函数的注册方法都有什么?⭐⭐⭐⭐
  7. 谈谈你对JNI静态注册和动态注册的区别。⭐⭐

目录

  • 1 概述
    • 1.1 JNI的优缺点
      • 1.1.1 JNI的优点
      • 1.1.2 JNI缺点
    • 1.2 Java调用C/C++的步骤:
    • 1.3 C/C++调用Java的步骤:
  • 2 JNI开发
    • 2.1 JNI开发的一般步骤
    • 2.2 JNI与NDK
    • 2.3 JNI的注册方法
      • 2.3.1 静态方法
      • 2.3.2 动态方法
  • 3 JNI 基础
    • 3.1 Java 基础数据类型:
    • 3.2 java 引用数据类型:
    • 3.3 在java中,数组中的数据类型和JNI的数组类型对应定义:
    • 3.4 本地程序调用基本顺序
  • 4 Java & Native 程序之间参数传递
    • 4.1 基本数据类型传递
    • 4.2 字符串传递
      • 4.2.1 JNI本地String函数
      • 4.2.2 UTF-8 strißngs & C-strings
    • 4.3 基本数据类型数组传递
    • 4.4 访问对象的变量和函数回调
    • 4.5 创建对象和对象数组
    • 4.6 本地和全局引用
  • 参考

1 概述

面试题:阐述你对JNI的理解⭐⭐⭐⭐⭐ JNI 是Java Native Interface的缩写,表示"Java本地调用"。通过JNI技术可以实现: ●Java调用C程序 ●C程序调用Java代码 我们的android源码中有很多代码都是Jni的实现的。例如MediaScanner的实现。就是通过jni的技术让我们在java层扫描到媒体相关的资源的

面试题:使用JNI有什么优缺点?⭐⭐⭐⭐⭐

1.1 JNI的优缺点

1.1.1 JNI的优点

  • 首先,Java语言提供的类库无法满足要求,且在数学运算,实时渲染的游戏上,音视频处理等方面上与C/C++相比效率稍低。
  • 然后,Java语言无法直接操作硬件,C/C++代码不仅能操作硬件而且还能发挥硬件最佳性能。
  • 接着,使用Java调用本地的C/C++代码所写的库,省去了重复开发的麻烦,并且可以利用很多开源的库提高程序效率。

通过对上面的几点的总结,可以归纳出一句话就是规避Java语言的弱点,然后利用C/C++的优点。 实际Android中的驱动都是C/C++开发的,通过JNI,Java可以调用C/C++实现的驱动,从而拓展Java虚拟机的能力。另外,在高效率的数学运算、游戏的实时渲染、音视频的编解码等方面,一般都是用C/C++开发的。

1.1.2 JNI缺点

任何事情都是由两面性的,所以JNI也不例外,所以在决定使用 JNI之前,我想各位一定要了解JNI那些缺点,如果能接受和容纳那就可以放心大胆的使用了。

  • 使用JNI细小的错误都能让这个JVM不稳定,并且这些错误很难再现和调试
  • 使用JNI的应用失去了JAVA本身提供的不同平台的可移植性。
  • JNI 框架不提供自动的垃圾回收机制,所以这部分代码要考虑内存的释放

面试题:具体说说如何实现Java与C++的互调⭐⭐⭐⭐⭐

1.2 Java调用C/C++的步骤:

  1. java声明native函数
  2. jni实现对应的c函数
  3. 编译生成so库
  4. java 加载so库,并调用native函数

1.3 C/C++调用Java的步骤:

a)从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象。 b)获取类的默认构造方法ID。 c)查找实例方法的ID。 d)创建该类的实例。 e)调用对象的实例方法。 示例:

JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod  
(JNIEnv *env, jclass cls)  
{  
    jclass clazz = NULL;  
    jobject jobj = NULL;  
    jmethodID mid_construct = NULL;  
    jmethodID mid_instance = NULL;  
    jstring str_arg = NULL;  
    // 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象  
    clazz = (*env)->FindClass(env, "com/study/jnilearn/ClassMethod");  
    if (clazz == NULL) {  
        printf("找不到'com.study.jnilearn.ClassMethod'这个类");  
        return;  
    }  

    // 2、获取类的默认构造方法ID  
    mid_construct = (*env)->GetMethodID(env,clazz, "<init>","()V");  
    if (mid_construct == NULL) {  
        printf("找不到默认的构造方法");  
        return;  
    }  

    // 3、查找实例方法的ID  
    mid_instance = (*env)->GetMethodID(env, clazz, "callInstanceMethod", "(Ljava/lang/String;I)V");  
    if (mid_instance == NULL) {  

        return;  
    }  

    // 4、创建该类的实例  
    jobj = (*env)->NewObject(env,clazz,mid_construct);  
    if (jobj == NULL) {  
        printf("在com.study.jnilearn.ClassMethod类中找不到callInstanceMethod方法");  
        return;  
    }  

    // 5、调用对象的实例方法  
    str_arg = (*env)->NewStringUTF(env,"我是实例方法");  
    (*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200);  

    // 删除局部引用  
    (*env)->DeleteLocalRef(env,clazz);  
    (*env)->DeleteLocalRef(env,jobj);  
    (*env)->DeleteLocalRef(env,str_arg);  
}  

2 JNI开发

2.1 JNI开发的一般步骤

面试题:JNI开发的一般步骤是?⭐⭐⭐⭐

  1. 编写声明了native方法的Java类
  2. 将Java源代码编译成class字节码文件
  3. 用javah -jni命令生成.h头文件(javah是jdk自带的一个命令,-jni参数表示将class中用native声明的函数生成jni规则的函数)
  4. 用本地代码实现.h头文件中的函数
  5. 将本地代码编译成动态库(windows:.dll,linux/unix:.so,mac os x:*.jnilib)
  6. 拷贝动态库至 java.library.path 本地库搜索目录下,并运行Java程序使用AndroidStudio创建JNI工程

上面的步骤是不是看起来有点复杂,但是在实际开发过程中不用担心。在Android Studio里集成了NDK工具集,能够快速的帮你完成上述步骤。

2.2 JNI与NDK

面试题:什么是NDK?⭐⭐

NDK全称是Native Development Kit,NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。NDK集成了交叉编译器(交叉编译器需要UNIX或LINUX系统环境),并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。

面试题:为什么使用NDK?⭐⭐

  1. 代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。
  2. 可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。
  3. 提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。
  4. 便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。

2.3 JNI的注册方法

2.3.1 静态方法

  • 创建Java类,声明 native 方法
  • javah 生成头文件 .h文件的作用
  • 创建 C/C++ 文件,实现对应的native方法

如何连接 Java 层方法和 native 层方法的:

Java方法被调用时,JVM会生成对应的 native 方法名,例如 com.example.StrHelper.getStr() ,JVM会在JNI库中查找 Java_com_example_StrHelper_getStr 函数,如果找到了,就会保存一个该 JNI 函数的指针,直接调用该指针。如果没找到就会报错。

上代码: 以下源码可以通过gitlab获取:JniTest

  1. 准备工作,Android Studio 中安装好NDK、 CMAK、 LLDB 工具
  2. 使用Android Studio创建一个工程,在Java代码中声明一个native方法
  1. 在java中声明native方法
public class MainActivity extends AppCompatActivity {

    /**
     *使用静态代码块加载'native-lib'库,该库即为C/C++代码编译后的共享库,
     * 加载后才能让java层调用C/C++的代码。
     * 库名称由CMakeLists.txt文件中的add_library指定。通常此处名称是native-lib的话,
     * 那么编译成功的共享库名称为libnative-lib.so。
     */
    static {
        System.loadLibrary("native-lib");
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(sayHello());//本地方法调用和java函数调用毫无二致

    }

    /**
     * 本地'native-lib'库中实现了的本地方法,共享库会打包到本应用中
     * 区别于普通java函数,在函数申明中多了个native字段,以及没有函数体
     */
    public native String sayHello();
}
  1. javac xxx.java 或者 build 生成class文件

alt

  1. 将 .class 生成 .h文件,javah -cp <class文件所在包的绝对路径> -d <生成路径>-jni com.hanson.jnitest.MainActivity

1)点击"View->Tool Windows->Terminal",即在Studio中进行终端命令行工具.执行如下命令生成c语言头文件。

2)这里需要注意的是要进入 \app\src\main的目录下执行javah命令,为的是生成的 .h 文件同样是在\app\src\main路径下,可以在Studio的工程结构中直接看到。

操作命令:

  javah -encoding UTF-8 -classpath 
  D:\WorkSpace\AndroidStudio\JniTest2\app\src\main\java -d 
  D:\WorkSpace\AndroidStudio\JniTest2\app\src\main\jni -jni 
  com.hanson.jnitest.MainActivity

然后就可以看到在-d指定的目录下生成了jni的头文件 具体操作图如下: alt 最后的生成结果:

alt

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_hanson_jnitest_MainActivity */

#ifndef _Included_com_hanson_jnitest_MainActivity
#define _Included_com_hanson_jnitest_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_hanson_jnitest_MainActivity
 * Method:    sayHello
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_hanson_jnitest_MainActivity_sayHello
  (JNIEnv *env, jobject);

#ifdef __cplusplus
}
#endif
#endif
  1. 最后就是实现native方法了,创建一个C++文件,最好保持名字和.h文件一致
#include <jni.h>
#include "com_hanson_jnitest_MainActivity.h"
#include <string>
#include <android/log.h>
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "In C/C++:", __VA_ARGS__);
using name

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Android系统面试题全解析 文章被收录于专栏

2020年研究生毕业后,工作重心由嵌入式Linux转为安卓系统,Android发展已经很多年,网上面向中初级Android系统开发的面经还比较少,也不够集中,因此梳理出本专栏,本专栏收集了本人工作中持续积累的众多安卓系统知识,持续更新中。

全部评论
资料很实用,收藏了
点赞 回复 分享
发布于 2023-02-21 22:09 天津
JNI和NDK概念理解了
点赞 回复 分享
发布于 2023-02-21 22:21 江苏

相关推荐

评论
5
19
分享

创作者周榜

更多
牛客网
牛客企业服务