Android系统面经(2/20)深入解析JNI
牛客高级系列专栏:
安卓(安卓系统开发也要掌握)
- 想通关安卓面试,请看:《150道安卓高频面试题目录及答案链接》
- 想通关安卓系统面试,请看:《140道安卓系统Framework面试题目录及答案链接》
- 想进阶安卓开发,请看:《Android进阶知识体系解析_15大安卓进阶必备知识点》
- 想了解安卓APP完整开发流程,请看:《安卓APP完整开发流程》
- 想掌握安卓App性能优化,请看:《安卓性能优化讲解和实战专栏》
- 想掌握Gradle语法,制作Gradle插件,请看:《安卓Gradle语法解析和实践大全》
嵌入式
- 想通关嵌入式面试,请看: 《111道嵌入式面试题目录及答案链接》
- 想多掌握几个嵌入式项目,请看:《6个嵌入式项目交流分享(附源码)》
- 本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人从嵌入式Linux转Android系统开发过程中对常见安卓系统开发面试题的理解;
- 1份外卖价格助您提高安卓面试准备效率,为您面试保驾护航!!
正文开始⬇
JNI开发是安卓开发必备技能,实用性100分,不过校招或者基础的社招面试时,面试官一般会问有没有用过JNI,用过就加分,讲述一下曾经使用的场景,没用过就下一个问题。所以对于基础面试级别的同学,知道简单的概念和使用方法就行了。本文只分析JNI的基础知识点,更多的语法细节的介绍不属于本文范围。面试官可能会问:
- 阐述你对JNI的理解⭐⭐⭐⭐⭐
- 使用JNI有什么优缺点⭐⭐⭐⭐⭐
- 什么是JNI?具体说说如何实现Java与C++的互调⭐⭐⭐⭐⭐
- 什么是NDK?为什么要使用NDK?⭐⭐
- JNI开发的一般步骤是?⭐⭐⭐⭐
- NI函数的注册方法都有什么?⭐⭐⭐⭐
- 谈谈你对JNI静态注册和动态注册的区别。⭐⭐
目录
- 1 概述
- 1.1 JNI的优缺点
- 1.1.1 JNI的优点
- 1.1.2 JNI缺点
- 1.2 Java调用C/C++的步骤:
- 1.3 C/C++调用Java的步骤:
- 1.1 JNI的优缺点
- 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++的步骤:
- java声明native函数
- jni实现对应的c函数
- 编译生成so库
- 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开发的一般步骤是?⭐⭐⭐⭐
- 编写声明了native方法的Java类
- 将Java源代码编译成class字节码文件
- 用javah -jni命令生成.h头文件(javah是jdk自带的一个命令,-jni参数表示将class中用native声明的函数生成jni规则的函数)
- 用本地代码实现.h头文件中的函数
- 将本地代码编译成动态库(windows:.dll,linux/unix:.so,mac os x:*.jnilib)
- 拷贝动态库至 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?⭐⭐
- 代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。
- 可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。
- 提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。
- 便于移植。用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
- 准备工作,Android Studio 中安装好NDK、 CMAK、 LLDB 工具
- 使用Android Studio创建一个工程,在Java代码中声明一个native方法
- 在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();
}
- javac xxx.java 或者 build 生成class文件
- 将 .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的头文件 具体操作图如下: 最后的生成结果:
/* 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
- 最后就是实现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%内容,订阅专栏后可继续查看/也可单篇购买
2020年研究生毕业后,工作重心由嵌入式Linux转为安卓系统,Android发展已经很多年,网上面向中初级Android系统开发的面经还比较少,也不够集中,因此梳理出本专栏,本专栏收集了本人工作中持续积累的众多安卓系统知识,持续更新中。