基本概念
JNI的定义
JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是c&c++)。从Javal.1开始, JNI标准成为Java平台的一部分,它允许Java代码和其他语言写的代码进行交互。
为什么要使用JNI?
效率上c/c++语言效率更高
代码移植,复用已经存在的c代码
java反编译比c语言容易
学习JNI必须要掌握的知识点
JAVA语言
c/c++ 语言
NDK(native development kits)
夸平台概览
JNI的副作用
一旦使用JNI, JAVA程序就丧失了JAVA平台的两个优点
程序不再跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分
程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。
Android中的五层架构
1.application:应用层
2.application framework:应用框架层
3.libraries和dalvik :函数库和虚拟机层
4.HAL(硬件抽象层)
5.linux kernel
JNI与NDK的关系
JNI与NDK的区别
JNI (Java native interface):在Google开发者指南中给出的定义如下: "JNI是Java和C++组件用以互相沟通的接口。
NDK(Native Development Kit):在Google开发者指南中给出的定义如下"Android NDK是一套允许您使用原生代码语言(例如C和C++)实现部分应用的工具集。
JN是一种技术,NDK是一个工具
静态库与动态库
什么是库?
库是写好的现有的,成熟的,可以复用的代码集合。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a. .lib)和动态库(.so. .dll) 。
二者的不同点在于代码被载入的时刻不同。
静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。
共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。
动态库为什么又叫做共享库?
不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
库文件的命名
静态库:libxxx.a
动态库:libxxxx.so.major.minor
*xxxx是该lib的名称, major是主版本号, minor是副版本号
查看可执行程序的依赖
linux下:
ldd命令可以查看一个可执行程序依赖的共享库。
例如#ldd/bin/Inlibc.so.6
=> /lib/libc.so.6 (0× 40021000)/lib/ld-linux.so.2
=>/lib/d-linux.so.2 (0×40000000)
可以看到In命令依赖于libc库和ld-linux库。
mac下:
使用otool命令可以查看一个可执行程序依赖的共享库。
例如. #otool -L hello
=> /libmyhello1.so (compatibility version 0.0.0, current version 0.0.0)
=> /usr/lib/libSystem.B.dylib
hello.h
void hello(const char* str);
hello.c
#include
void hello(const char* name){
printf ("hello %s",name);
}
main.c
#include"hello.h"
int main(){
hello ("san zhang");
return()
}
静态库生成使用
st hello.h //新建hello.h文件
st hello.c //新建hello.c文件
st main.c //新建main.c文件
gcc -c hello.c
ls
helio.c hello.h hello.o main. c
ar -cr libstatic.a hello.o
ls
libstatic. a main.chello.h hello.ohelio.c
gcc -o hello main.c -L.-lstatic
ls
hello
hello
hello world.i love this world%
动态库生成使用
st hello.h //新建hello.h文件
st hello.c //新建hello.c文件
st main.c //新建main.c文件
gcc -c hello.c
ls
helio.c hello.h hello.o main. c
ar -cr libstatic.a hello.o
ls
libstatic. a main.chello.h hello.ohelio.c
gcc -o hello main.c -L.-lstatic
ls
hello
hello
hello world.i love this world%
rm -rf hello hello.o libstatic.a //删除静态库生成的文件
gcc -c hello.c
gcc -shared -o libdynamic.so hello.o //生成静态库
gcc -o hellol main.c -L. -ldynamic //生成可以执行文件hello1
hello1 //执行
otool -L hello1
环境的搭建
NDK下载
下载途径
google官网下载链接:https://developer.android.google.cn/ndk/index.html
NDK目录的介绍
目录结构梳理
docs帮助文档
build/tools批处理文件
platforms存放jni开发所需要用到的一些头文件和动态链接库
prebuilt预编译用到的工具
sample使用jni的案例
ndk-build编译打包C代码的指令
toolchains不同平台的编译器链接器目录以及一些和编译连接相关的工具,gcc,ld等工具都在这个目录 sources NDK的部分源码
JNI Hello Word :https://blog.csdn.net/salute_li/article/details/52469214
2018 11 月自己编写hello word:https://mp.csdn.net/postedit/84073518
GitHub:https://github.com/smallletters/sample-code
日志的输出
如:#define LOGI(..)_ _ android_log_print(ANDROID_LOG_INFO,TAG,_VA_ARGS__)
编写native文件的另外一种方式--直接编写
一般格式:
JNIEXPORT返回类型JNICALL Java_包名_类名-方法名(参数) {
}
JNI基础
字符串的操作
数组的访问
void (*GetIntArrayRegion)(JNIEnv*, jintArray,jsize a, jsize b,jint* buf)
将jintArray数组中从a-b中的数据复制到buf中
jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*):
将jintarray转换成jnitarray.如果isCopy不是NULL, *isCopy在复制完成后即被设为JNI_TRUE;如果未复制,则设为JNII_FALSE
jarray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement):
构造新的数组,它将保存类elementClass中的对象。所有元素初始值均设为initialElement
对象字段的操作
. jclass GetObjectClass (JNIEnv *env, jobject obj):
通过对象获取这个类。该函数比较简单,唯一注意的是对象不能为NULL,否则获取的class肯定返回也为NULL。
. jfieldID GetFieldID (JNIEnv *env, jclass clazz, const char *name, const char *sig):
返回Java类(非静态)域的属性ID。
jobject GetObjectField(jobject obj,jfieldID fieldID):
根据fieldld获取相应的field
void SetObjectField(jobject obj, jfieldID fieldID, jobject value):
设置对象成员变量的值
静态字段的操作
静态字段的访问
jfieldID GetStaticFieldID (JNIEnv *env,jclass clazz, const char*name, const char *sig): 获取类的静态域ID方法
静态字段的设置
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value):
设置static field的值
访问静态字段与对象字段的比较
访问静态字段和对象实例字段的不同点:
访问静态字段使用GetStaticFieldID,而访问对象的实例字段使用GetFieldID,但是,这两个方法都有相同的返回值类型: jfieldID.
构造函数的调用
void GetCharArrayRegion (JNIEnv *env, ArrayType array, jsize start,jsize.• len, NativeType *buf): 将基本类型数组某一区域复制到缓冲区中的一组函数
void SetCharArrayRegion (JNIEnv *env, ArrayType array, jsize start, jsize.len, NativeType *buf):
将基本类型数组的某一区域从缓冲区中复制回来的一组函数
jobject NewObject (JNIEnv *env, jclass clazz, jmethodID methodD, ..):
构造新Java对象
使用时缓存
字段ID和方法ID可以在字段的值被访问或者方法被回调的时候缓存起来。
缓存字段ID
缓存方法ID
.C文件 缓存方法ID C++代码
JNIEXPORT void JNICALL 包名—类名—方法名
(JNIEnv *env, jobject obj){
jclass clsobj =(*env)-GetObjectclass (env, obj);
static jmethodID mid NULL;
if (mid == NULL) {
mid = (*env)->GetMethodID(env, cLsobj, "sayHello", "()V");
}
(*env)->CallVoidMethod (env, obj, mid);
静态初始化的过程中缓存
我们在使用时缓存字段和方法的ID,每次本地方法被调用时都要检查ID是否已经被缓存。许多情况下,在字段ID和方法ID被使用前就初始化是很方便的。
异常的处理
jthrowable ExceptionOccurred (JNIEnv *env):
确定某个异常是否正被抛出。
void ExceptionDescribe (JNIEnv *env):
将异常及堆栈的回溯输出到标准输出
void ExceptionClear (JNIEnv *env):
清除当前抛出的任何异常。如果当前无异常,则不产生任何效果。
jint ThrowNew (JNIEnv *env, jclass clazz, const char *message):
利用指定类的消息(由message指定)构造异常对象并抛出该异常
JNI的性能测试