Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
一.JNI概述. l, J! K% [! L% e: L1 ~
JNI即java native interface,通俗的说,JNI是一种技术,通过这种技术可以做到以下两点:" v4 }" f. \5 K% B" }  M
1.java程序中的函数可以调用C/C++ 编写的函数9 M  B5 a! H9 Z: J9 s' |
2.C/C++ 程序可以通过它调用java层的函数1 N% p8 A9 ?% ]' u
JNI主要是完成java和C/C++ 代码和交互,但是java为什么要调用C/C++ 程序哪,这是不是破坏了java的平台无关特性,其实java需要调用C/C++ 程序需要是出于以下几点:
# n. A* w" k7 t4 V1 F8 @5 P+ U1.早在java语言诞生前,很多程序都是由C/C++ 语言编写的,他们遍布在软件世界的各个角落。虽然java出世后备受追捧,但是并不能完全替代C/C++ ,很多功能还需要调用它们,重复制造轮子会影响用户的体验,而JNI技术则解决了这个问题。
5 ~5 {: Z6 o* R; |2.C/C++ 程序的运行效率是高于java的(虽然后期推出了JIT技术),在很多在运行速度上要求比较高、调用比较频繁的场合就可能选用C/C++ 语言。
9 w. m1 O9 E1 p3.有时需要和平台代码通信而又没有对应的Java API的时候,也需要使用C/C++ 代码编写好相应的代码供Java层使用。
6 @8 U" s/ i2 H在Android平台上,JNI就是一座将Java世界和Native世界的天堑变成通途的桥,如下图所示,它展示了Android平台上JNI所处的位置。
) @8 h9 \% _! ]/ k) W, }6 o; l
0 f& j! X' X' Z0 B. c/ t; I: @' L; w* X3 r; x
, r3 X* ]3 D) a" V7 \0 y: \2 t
二.SWIG概述9 G5 {* k9 ^) s+ V0 H* ~
SWIG是一个软件开发工具,它将用C和C ++编写的程序与各种高级编程语言连接起来。SWIG与不同类型的目标语言一起使用。SWIG可以作为测试和原型化C / C ++软件的工具。SWIG通常用于解析C / C ++接口并生成各种语言调用C / C ++代码所需的“粘合代码”。转化代码时是非常方便实用的,而且用起来也不是太复杂,最主要的是写一次代码可以为多种语言调用。工具是开源的,学习手册请查看 http://www.swig.org/
% X' \( G; S/ O$ f" i  B" Y. Y% s三.示例* H2 b, u. O) @7 U
俗话说,百闻不如一见,我们先实现一个JNI调用的例子,然后再解析其原理。以下操作是在ubuntu环境下进行的。
- Q. l; M1 ?: d2 i1.搭建编译环境
! Y8 ]: q+ I% d+ W! NAndroid studio,android 原生开发工具包(NDK),CMake,LLDB 调试工具。可以使用SDK 管理器安装这些组件:
6 G0 D& S& j. F' D) d8 m1 Y1.1在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。( L* N1 J8 ~9 v( T6 \+ v- N
1.2点击SDK Tools 标签。8 _+ N; g; N' o; H( M4 n
1.3选中LLDB、CMake 和NDK 旁的复选框。
/ [( s9 ]0 r1 y1 k* k; A4 S1.4点击Apply,然后在弹出式对话框中点击OK。
+ M! Y. ]4 @+ R5 T1.5安装完成后,点击Finish,然后点击OK。
6 [$ T" y4 y' g- z' C9 T2.编写C代码
0 _4 k; e  [0 w& v' R
& r8 H/ T2 a5 E, f) ~. b3.编写SWIG的转化文件
& t3 I5 @- f; S" B
" ?7 H! b6 q! A1 V. s8 X4.生成JNI调用所需的文件
% z7 a9 C' z% d; W. W) L8 L/ v2 l, ^使用命名swig -java -package com.example.jni add.i,会生成三个文件) S" M- A. D) _% Z* O
add_wrap.c:用于和java 对接的JNI函数! g3 l. I  N+ h* {6 x3 S
addJNI.java:用于和JNI 对接的java 本地接口
) v4 n' C: n/ A- m( Dadd.java:java 本地调用的接口,他调用addJNI.java 中的内容
6 Q8 `9 c; M: n4 S* [, p0 ccat add_wrap.c(因为生成的文件比较大,所以需要在)
/ s4 S8 w/ F; Q( w
8 T, o7 S& m* Q8 Ecat addJNI.java
4 A8 T3 h* `8 b/ c! [1 b8 V0 {; g* s' W* ^. j
cat add.java# H  B+ d0 y% [, y5 L& \% w& f

- c+ c3 @  T6 x& `/ H2 i+ r( H5.生成C库" d5 d7 u1 O2 d$ a/ |6 A) _0 F
注意两点:3 c# c7 P2 \6 F  l( ?4 X! ^# U
5.1 将刚生成都add_wrap.c文件添加到库,如果没有源码可以将add_wrap.c编译为独立的动态库文件
+ f3 y& `: Y- i" @5.2 添加jni.h文件(安装NDK后就会存在)到可查找的头文件目录中,add_wrap.c使用了jni.h文件( j5 `2 t2 b  ?; h( ^& k4 T
cat CMakeLists.txt
( M2 ], v9 _; I/ I  ]* w( `. w5 k9 R/ `# u
执行脚本:
0 o) \: g, y: u4 U( S, Z# c6 ^5 ^% i$ a. ]
生成的目录如下* W2 n/ p0 g2 U9 Q( z+ h
build
" }6 x3 I1 M; ^1 m  j* c0 u$ r├── arm64-v8a
. O. I: d2 @; P2 M& }: b8 a│ └── lib
* s7 {+ S( q" d* {/ i" k/ t5 r( c│ └── libadd.so" q- \/ V. \' c6 ^
├── armeabi-v7a) t2 ?8 w! |  y! L4 ~% [" B
│ └── lib
, Z1 |0 O+ B3 ?5 k│ └── libadd.so
4 S0 y  V, I% a! u9 O& i' ~4 J├── x869 q1 h3 B4 K" I8 L7 W
│ └── lib
$ ]5 M" u/ ^5 ^│ └── libadd.so% c' f$ H' M" j0 i
└── x86_64) t$ B9 j' b! x  I
└── lib
; M! {3 l6 O% j* N9 C/ m) @4 |└── libadd.so
, X2 l. L& a2 L# o* |3 I7 J2 {+ P将so文件移到lib同级目录下,删除lib文件(和Android Studio配置一致)* n# y# I1 [7 t) ~
6.Android studio测试运行% J7 @( q& a: B) M" [9 H$ l
6.1 Android studio创建新的项目,在向导的Configure your new project部分,选中Include C++ Support 复选框。) \1 H. y9 l" S' j* S" A3 H! U
6.2 将生成的so 文件复制到app\src\main\jniLibs 目录下(四种库和文件夹一起复制)
* w+ l3 l: U" Z: s6.3 将SWIG生成的add.java 和addJNI.java 文件复制 app\src\main\java\com\example\jni 目录下(与导出时package 参数一致)9 r& S- o% Y& ]9 l/ y
6.4 测试程序如下,测试成功
% |; w% u6 L0 L5 p" F
9 f( q  Q- W8 g+ U$ o/ {& o, k四.加载过程
( d! ], @0 e/ J6 [大家都知道,如果我们希望使用C/C++函数,将其加载到内存是基础,那到底什么时候加载,怎么加载的哪?
" m2 q/ G$ M/ @2 l" W. l其实只要在调用前,可以在任何时候加载本地库,但是我们一般的做法是在java类static语句中加载,加载的方式是通过System.loadLibrary方法。需要注意的是System.loadLibrary的参数是动态库的名字,如上示例,在linux下为libadd.so,而在window下为add.dll。其实JNI对java程序员还是比较照顾的,他们只需要加载库文件,其他的都不需要做。native修饰的函数也已经被JNI自动生成,如果在*JNI.java中添加加载库的static函数,那么java程序员连加载库都免了,是不是感觉很方便。; J. ?  b# Y$ R1 K: Z/ T
五.注册过程6 }( B! @+ W0 m2 ^! t" O
读到这里,相信大家应该还是有一个很大的疑问,C库我已经将其加载,但是它又是怎么和java函数联系起来的哪,为什么我调用java函数而它会自动调用C库的函数,他们之间是怎么联系起来的哪?
4 i, g' L. \4 Q$ i正常的java方法是在加载的过程中会被放到方法区,而native方法则没有被定义。这就必须存在一个注册的过程,即将C库中的函数(上例中add_wrap.c中的函数)与Java中Native方法(上例中addJIN.java中方法)建立对应。注册过程分为两种:
9 o5 M! Z; F4 W* m6 q( F1.静态注册
3 O( o, Q9 a0 r' A* ]当java层调用add方法时,如上例所示,他会从C库中查找Java_com_example_jni_addJNI_add方法,如果没有则报错,如果找到就将两个函数建立一个关联,以后调用add方法时就会直接调用Java_com_example_jni_addJNI_add函数。从这里可以看出,静态方法是通过函数名来建立java函数和C函数之间的对应的。所以它要求对应的C函数(add_wrap.c)必须遵循特定的格式。上例就是通过静态的方式进行注册。9 m$ M2 h% u! I/ }: a% Z; n1 C  K2 E, U
静态注册是有缺点的:' z) N$ v) u6 U0 ^! r; Z
(1)需要编译所有生命了native方法的java类,每个class文件都需要对应一个**JNI.java的文件。3 S) e5 r& |0 x% l+ i
(2)生成的JNI层函数名特别长,书写起来不方便
- o' z6 x8 T( Z( D5 Y% [(3)初次调用native函数时需要根据函数名搜索对应的JNI层函数来建立连接,影响运行效率
# `3 s, T/ \2 a2.动态注册1 K& g+ z/ \$ M* p' d
从静态注册中可以发现java函数和JNI函数是有一个数据结构来保存关联的。这个数据结构大致如下:$ l; g. u8 C, e6 t, Z6 P0 h

0 y# q) ?7 E& s+ E7 [有一个指向这种数据结构的二级指针来保存所有的本地方法。所以我们就可以考虑在类加载的时候直接将本地方法和JNI函数建立连接,这种方式就是动态注册。
( X* ?& i' S2 x5 `5 K$ g而在java层我们只用了System.loadLibray方法,所以因该在这个方法中进行动态注册。顺藤摸瓜我们可以找到JNI_Onload函数,在执行System.loadLibrary方法时。0 A: E( O. d6 O6 ?0 a, g
JNI_Onload函数主要操作有两部分:7 v, \0 j. E# p1 L1 _8 F
1.通过JNIEnv的FindClass函数找到java层注册JNI的方法
4 T0 m3 M( c7 U2.调用该函数
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

朋友一起走 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    16