Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
一.JNI概述+ B; ^$ p" c% f0 X/ B
JNI即java native interface,通俗的说,JNI是一种技术,通过这种技术可以做到以下两点:
1 Y! A8 Y6 Z" l1.java程序中的函数可以调用C/C++ 编写的函数
% N- d. i! v/ a$ L$ Q2.C/C++ 程序可以通过它调用java层的函数6 d4 w' y% e8 M2 j
JNI主要是完成java和C/C++ 代码和交互,但是java为什么要调用C/C++ 程序哪,这是不是破坏了java的平台无关特性,其实java需要调用C/C++ 程序需要是出于以下几点:
" V7 [/ M8 T# ^8 L) l  M- s1.早在java语言诞生前,很多程序都是由C/C++ 语言编写的,他们遍布在软件世界的各个角落。虽然java出世后备受追捧,但是并不能完全替代C/C++ ,很多功能还需要调用它们,重复制造轮子会影响用户的体验,而JNI技术则解决了这个问题。' j5 [: j5 g4 y
2.C/C++ 程序的运行效率是高于java的(虽然后期推出了JIT技术),在很多在运行速度上要求比较高、调用比较频繁的场合就可能选用C/C++ 语言。) n1 M7 O5 N3 }2 z8 d& f, ^) F6 j
3.有时需要和平台代码通信而又没有对应的Java API的时候,也需要使用C/C++ 代码编写好相应的代码供Java层使用。
3 _! h% F7 l1 ?" s/ S1 Y2 h2 k在Android平台上,JNI就是一座将Java世界和Native世界的天堑变成通途的桥,如下图所示,它展示了Android平台上JNI所处的位置。" F( ~# }% E' R4 o$ ^

) r" W8 Z4 ]4 I. r' w- Y7 P, }$ }- }
6 x% p' _/ ]# Y+ B. R
二.SWIG概述
/ }1 |: }# O* gSWIG是一个软件开发工具,它将用C和C ++编写的程序与各种高级编程语言连接起来。SWIG与不同类型的目标语言一起使用。SWIG可以作为测试和原型化C / C ++软件的工具。SWIG通常用于解析C / C ++接口并生成各种语言调用C / C ++代码所需的“粘合代码”。转化代码时是非常方便实用的,而且用起来也不是太复杂,最主要的是写一次代码可以为多种语言调用。工具是开源的,学习手册请查看 http://www.swig.org/
0 p3 E0 S1 \% D& L8 ]三.示例* N  d  c: y8 f# n4 \8 m
俗话说,百闻不如一见,我们先实现一个JNI调用的例子,然后再解析其原理。以下操作是在ubuntu环境下进行的。
, U8 L; Z  u. P7 _7 |! J' z1.搭建编译环境$ p/ `* M2 O( K" u" C
Android studio,android 原生开发工具包(NDK),CMake,LLDB 调试工具。可以使用SDK 管理器安装这些组件:
6 \0 A3 r, D8 ^1.1在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。( }& M9 @. @1 Z; _9 m8 m
1.2点击SDK Tools 标签。
2 D1 u, N  i: p1.3选中LLDB、CMake 和NDK 旁的复选框。
: D  ~6 {3 m; L6 A1.4点击Apply,然后在弹出式对话框中点击OK。
4 g# J1 K2 N& e$ K1.5安装完成后,点击Finish,然后点击OK。; ^! G4 b9 y. H- a& m! ?- D, @; C, D8 ]
2.编写C代码
0 }3 n* `" A5 i7 ~7 E* J2 L0 X, }, K2 F% p. R0 p8 O
3.编写SWIG的转化文件# W7 [* \- _, h3 K* l
6 G7 o/ P' e, S6 d8 v0 F, K
4.生成JNI调用所需的文件
# {& R  ~9 n0 A3 @0 P# v8 {使用命名swig -java -package com.example.jni add.i,会生成三个文件& P  @5 }) k4 N& i; {
add_wrap.c:用于和java 对接的JNI函数& u0 O% v0 ?/ E+ m) U
addJNI.java:用于和JNI 对接的java 本地接口
9 N# z7 L. A8 T# Qadd.java:java 本地调用的接口,他调用addJNI.java 中的内容
; m! B5 d( I  p9 icat add_wrap.c(因为生成的文件比较大,所以需要在), p# w% q* g* Q- q

6 K5 h5 s5 v8 o0 N! E; Tcat addJNI.java. y& O- b) n0 m. E8 R/ H5 I* z
! U, h0 n+ E8 Y# r
cat add.java
0 p" N. d0 T1 m% u$ c8 z9 ?9 A& b- d4 I9 Q
5.生成C库
1 T# x' c# r, \- P4 A0 d& A- ?' c注意两点:
2 Z7 f* d% ^! b7 U8 U) K+ p( y, X5.1 将刚生成都add_wrap.c文件添加到库,如果没有源码可以将add_wrap.c编译为独立的动态库文件
& n; V) v& c/ R: ?5 w, ?5.2 添加jni.h文件(安装NDK后就会存在)到可查找的头文件目录中,add_wrap.c使用了jni.h文件, x2 k( n# p/ e, e# a  h) X  v
cat CMakeLists.txt: w" d8 X4 w+ W4 [1 M' P$ q
2 e$ |! N7 b/ M1 R  S9 ~
执行脚本:
- ?4 W! U1 p: F# d1 ^
3 W' b: S% C  ~0 C$ g7 Q" |! r  H生成的目录如下
) e, H' ?$ o- U, D/ {build
  Z( {$ n( n  i, X8 c/ @├── arm64-v8a: O( Q9 a5 h5 W/ l6 S# o
│ └── lib
* q3 T' M. j* }$ X( V4 r│ └── libadd.so
% X# v& U" V2 g6 n; m├── armeabi-v7a
- ]& v* Q( L- i( k4 k; u1 i│ └── lib2 U/ m* U: Q; u/ ]$ i' l$ o0 q
│ └── libadd.so# f: Q7 l9 h  [
├── x86
& _/ M% g$ K$ x+ [0 U1 ?& d│ └── lib
1 g! i% i" r5 k/ L│ └── libadd.so0 s% K  Q& T0 f  `
└── x86_64: z, L$ I3 [. h* n" O) t! T9 F
└── lib% Q/ z- n+ }4 x7 Y, P
└── libadd.so
4 B& a1 C3 z: R2 ^将so文件移到lib同级目录下,删除lib文件(和Android Studio配置一致)
4 i  K7 D. ~1 F6.Android studio测试运行4 c' k4 ?, b4 {/ ~
6.1 Android studio创建新的项目,在向导的Configure your new project部分,选中Include C++ Support 复选框。, `5 ], B& b4 ]0 D1 R
6.2 将生成的so 文件复制到app\src\main\jniLibs 目录下(四种库和文件夹一起复制)
  c9 L. W  q7 B' x6.3 将SWIG生成的add.java 和addJNI.java 文件复制 app\src\main\java\com\example\jni 目录下(与导出时package 参数一致)4 ]2 |. ^& p# N
6.4 测试程序如下,测试成功
5 r1 N2 R; f! @' S
" C4 J5 j9 b3 p4 T, m四.加载过程
1 h; J  q$ V7 Q; X: O大家都知道,如果我们希望使用C/C++函数,将其加载到内存是基础,那到底什么时候加载,怎么加载的哪?
- e& H7 ^6 g9 j" ?# D其实只要在调用前,可以在任何时候加载本地库,但是我们一般的做法是在java类static语句中加载,加载的方式是通过System.loadLibrary方法。需要注意的是System.loadLibrary的参数是动态库的名字,如上示例,在linux下为libadd.so,而在window下为add.dll。其实JNI对java程序员还是比较照顾的,他们只需要加载库文件,其他的都不需要做。native修饰的函数也已经被JNI自动生成,如果在*JNI.java中添加加载库的static函数,那么java程序员连加载库都免了,是不是感觉很方便。  G! p' ?" S: N9 ]) W
五.注册过程" N, N( c- a  E/ I0 f. L
读到这里,相信大家应该还是有一个很大的疑问,C库我已经将其加载,但是它又是怎么和java函数联系起来的哪,为什么我调用java函数而它会自动调用C库的函数,他们之间是怎么联系起来的哪?
; ^8 u7 {$ ?. j! @7 @+ F- h3 Z正常的java方法是在加载的过程中会被放到方法区,而native方法则没有被定义。这就必须存在一个注册的过程,即将C库中的函数(上例中add_wrap.c中的函数)与Java中Native方法(上例中addJIN.java中方法)建立对应。注册过程分为两种:
0 b+ E/ \2 }% ]. j! I1.静态注册
: B. k/ v% U2 R8 R% n: \1 K% o当java层调用add方法时,如上例所示,他会从C库中查找Java_com_example_jni_addJNI_add方法,如果没有则报错,如果找到就将两个函数建立一个关联,以后调用add方法时就会直接调用Java_com_example_jni_addJNI_add函数。从这里可以看出,静态方法是通过函数名来建立java函数和C函数之间的对应的。所以它要求对应的C函数(add_wrap.c)必须遵循特定的格式。上例就是通过静态的方式进行注册。
3 j. `1 t4 e+ Q4 A# D, F# b静态注册是有缺点的:6 l7 Y9 _. [/ T# g% R% q
(1)需要编译所有生命了native方法的java类,每个class文件都需要对应一个**JNI.java的文件。
0 Q8 K/ [7 H+ R. h(2)生成的JNI层函数名特别长,书写起来不方便8 I- d2 h2 J: F- y) D* f
(3)初次调用native函数时需要根据函数名搜索对应的JNI层函数来建立连接,影响运行效率$ E8 k% V  S5 r
2.动态注册
$ k; t8 ?$ m2 Y/ a8 I7 B9 @- M从静态注册中可以发现java函数和JNI函数是有一个数据结构来保存关联的。这个数据结构大致如下:4 @# f5 m" Z$ c: ]$ P

3 l( f; k. U% `' {+ N7 e) l有一个指向这种数据结构的二级指针来保存所有的本地方法。所以我们就可以考虑在类加载的时候直接将本地方法和JNI函数建立连接,这种方式就是动态注册。
  I* \. y$ l* A5 \) Z% a, r而在java层我们只用了System.loadLibray方法,所以因该在这个方法中进行动态注册。顺藤摸瓜我们可以找到JNI_Onload函数,在执行System.loadLibrary方法时。
6 v( G$ c3 q5 p, C, s4 Y5 p' g; DJNI_Onload函数主要操作有两部分:
' N3 X# U* b3 R3 m1.通过JNIEnv的FindClass函数找到java层注册JNI的方法$ H5 g8 i/ E, G. _
2.调用该函数
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

朋友一起走 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    16