Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
一.JNI概述
' r* k4 C& {. D- \  O" d" D! PJNI即java native interface,通俗的说,JNI是一种技术,通过这种技术可以做到以下两点:
  o& j! W, ~" G0 I& ^1 h1.java程序中的函数可以调用C/C++ 编写的函数/ F4 i& G/ ~: }* N9 V
2.C/C++ 程序可以通过它调用java层的函数; O3 l! b* `( R  ?
JNI主要是完成java和C/C++ 代码和交互,但是java为什么要调用C/C++ 程序哪,这是不是破坏了java的平台无关特性,其实java需要调用C/C++ 程序需要是出于以下几点:
+ i9 C  W* o$ o# \) C1.早在java语言诞生前,很多程序都是由C/C++ 语言编写的,他们遍布在软件世界的各个角落。虽然java出世后备受追捧,但是并不能完全替代C/C++ ,很多功能还需要调用它们,重复制造轮子会影响用户的体验,而JNI技术则解决了这个问题。
6 N, I& y0 z" Q' G( [5 H; L) D' p2.C/C++ 程序的运行效率是高于java的(虽然后期推出了JIT技术),在很多在运行速度上要求比较高、调用比较频繁的场合就可能选用C/C++ 语言。
" v9 X& \% I3 U) c! m; J! l! \& b3.有时需要和平台代码通信而又没有对应的Java API的时候,也需要使用C/C++ 代码编写好相应的代码供Java层使用。
  s8 p1 C3 v* `) y在Android平台上,JNI就是一座将Java世界和Native世界的天堑变成通途的桥,如下图所示,它展示了Android平台上JNI所处的位置。
- m$ u+ |  ^" z8 B4 I& [! S, `" ?& C* T( I
$ u% z. z# l; O- H8 B( v
! f' K% M1 P( p& g
二.SWIG概述
" m( f) Y1 X7 K1 `SWIG是一个软件开发工具,它将用C和C ++编写的程序与各种高级编程语言连接起来。SWIG与不同类型的目标语言一起使用。SWIG可以作为测试和原型化C / C ++软件的工具。SWIG通常用于解析C / C ++接口并生成各种语言调用C / C ++代码所需的“粘合代码”。转化代码时是非常方便实用的,而且用起来也不是太复杂,最主要的是写一次代码可以为多种语言调用。工具是开源的,学习手册请查看 http://www.swig.org/
2 r& A6 J; `2 F0 J三.示例
4 r6 E$ o5 j) K' u' {9 ?  U2 p俗话说,百闻不如一见,我们先实现一个JNI调用的例子,然后再解析其原理。以下操作是在ubuntu环境下进行的。5 A. `0 q1 ^- \) w! h
1.搭建编译环境, v2 O; \& o7 C4 A. a
Android studio,android 原生开发工具包(NDK),CMake,LLDB 调试工具。可以使用SDK 管理器安装这些组件:( C/ g1 R: Y! {
1.1在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。
. x3 [4 W0 x$ p/ e1.2点击SDK Tools 标签。
2 f' R8 X" K' l) h5 W/ F1.3选中LLDB、CMake 和NDK 旁的复选框。  l# D! v- E( {  ]
1.4点击Apply,然后在弹出式对话框中点击OK。
+ U- \$ O" \4 `8 f6 e1.5安装完成后,点击Finish,然后点击OK。
6 ?) a" |5 T/ x6 H+ }* U' \2.编写C代码( F/ A% o! c% X1 K! @

' w+ p+ W7 v: ]! j3.编写SWIG的转化文件4 D7 W. h& K) c  w9 t6 }' u* c, ^" D

. j' W6 H* M5 D. p4.生成JNI调用所需的文件/ a  o0 x. R- v, q/ s5 q4 ~1 }' c
使用命名swig -java -package com.example.jni add.i,会生成三个文件3 X; u( W0 y6 O5 l0 \8 Z
add_wrap.c:用于和java 对接的JNI函数. U& L" D0 }1 q$ H7 N! L  U. q
addJNI.java:用于和JNI 对接的java 本地接口
8 g3 g6 L! w, \1 |" y& `add.java:java 本地调用的接口,他调用addJNI.java 中的内容
4 M( s: Z& V- M0 C9 X# H4 rcat add_wrap.c(因为生成的文件比较大,所以需要在)  q) A) U5 `  G# [/ y* a- y
( h' ?" t- c; }! n' {5 h4 k
cat addJNI.java5 f" K7 g5 Y% y! ^# Q* s3 z# Q6 r
) b% H' X' E) Q( [
cat add.java1 a* W, X5 f  h1 L" K$ @

! @" F4 ~4 j. b5.生成C库3 n' Q- v- O1 A9 g# n" g  Q: V
注意两点:, k& N$ f% _' v) ?
5.1 将刚生成都add_wrap.c文件添加到库,如果没有源码可以将add_wrap.c编译为独立的动态库文件
, S" }& Y4 b  g5.2 添加jni.h文件(安装NDK后就会存在)到可查找的头文件目录中,add_wrap.c使用了jni.h文件
5 R# z$ x! Y- W& _! jcat CMakeLists.txt, _/ b8 B2 P3 X: d
: ?3 C+ U' c& Y% _( |; r
执行脚本:8 n7 g* Z) K9 _0 e

5 N' \- D- b* X# {生成的目录如下
+ Y0 s' a) I$ ]( F) p3 o! ]: ]build' D- ]$ `- p0 H3 @4 o
├── arm64-v8a
8 n; r+ K# Y% }1 F! \6 H/ a1 C│ └── lib% g$ Y& Z. q9 o4 n: B/ g3 `- q
│ └── libadd.so
/ i0 T& e$ q+ k, E├── armeabi-v7a
* I6 @. Q7 p& h  `│ └── lib4 ^1 D; |4 p! _4 N3 i3 E4 O6 O
│ └── libadd.so
+ t2 f" @8 |# L* W0 A2 h' P- R+ u  Y├── x86
( A% ^, D* s* H% v" P( ]│ └── lib
$ R3 \3 }& Q  `3 f1 j" r, F; \│ └── libadd.so
3 H; n4 b9 m( B/ r1 U, H└── x86_64
, j/ _; g' p. x└── lib/ V9 e5 Q: x! I! W# K8 P
└── libadd.so8 M2 E. j% R( X( R9 B4 a" K
将so文件移到lib同级目录下,删除lib文件(和Android Studio配置一致)8 f( E. }& H! @9 P
6.Android studio测试运行9 X! l3 \. p4 C5 ^6 J; M. g
6.1 Android studio创建新的项目,在向导的Configure your new project部分,选中Include C++ Support 复选框。
# J& s7 v' c! I5 Z3 J: E# u6.2 将生成的so 文件复制到app\src\main\jniLibs 目录下(四种库和文件夹一起复制)
& ~+ a% B" _  v8 p6.3 将SWIG生成的add.java 和addJNI.java 文件复制 app\src\main\java\com\example\jni 目录下(与导出时package 参数一致)' J0 ^2 C; W5 H& j
6.4 测试程序如下,测试成功
, l" m& W) E" F/ v6 z; x( `8 C& v
- L% ^% O) S) a7 k9 I四.加载过程
5 ~, O/ r1 n& Q6 H2 i2 \) D大家都知道,如果我们希望使用C/C++函数,将其加载到内存是基础,那到底什么时候加载,怎么加载的哪?, K% }- O; Y0 d! c3 |. i
其实只要在调用前,可以在任何时候加载本地库,但是我们一般的做法是在java类static语句中加载,加载的方式是通过System.loadLibrary方法。需要注意的是System.loadLibrary的参数是动态库的名字,如上示例,在linux下为libadd.so,而在window下为add.dll。其实JNI对java程序员还是比较照顾的,他们只需要加载库文件,其他的都不需要做。native修饰的函数也已经被JNI自动生成,如果在*JNI.java中添加加载库的static函数,那么java程序员连加载库都免了,是不是感觉很方便。
; X( G' [; Q; R7 c7 R" k五.注册过程) B4 D, _# _6 S0 ]
读到这里,相信大家应该还是有一个很大的疑问,C库我已经将其加载,但是它又是怎么和java函数联系起来的哪,为什么我调用java函数而它会自动调用C库的函数,他们之间是怎么联系起来的哪?5 L$ k& C4 V7 _/ E& A+ H. g4 v
正常的java方法是在加载的过程中会被放到方法区,而native方法则没有被定义。这就必须存在一个注册的过程,即将C库中的函数(上例中add_wrap.c中的函数)与Java中Native方法(上例中addJIN.java中方法)建立对应。注册过程分为两种:9 c) w% ~3 G7 v4 `# W( O
1.静态注册, t+ ~" s# A2 K( `
当java层调用add方法时,如上例所示,他会从C库中查找Java_com_example_jni_addJNI_add方法,如果没有则报错,如果找到就将两个函数建立一个关联,以后调用add方法时就会直接调用Java_com_example_jni_addJNI_add函数。从这里可以看出,静态方法是通过函数名来建立java函数和C函数之间的对应的。所以它要求对应的C函数(add_wrap.c)必须遵循特定的格式。上例就是通过静态的方式进行注册。
3 _; r( v1 e1 `  k" H: u. k! G; o静态注册是有缺点的:
2 a; x" M! V5 u) {(1)需要编译所有生命了native方法的java类,每个class文件都需要对应一个**JNI.java的文件。
( y: C4 X4 ^/ e; y  V(2)生成的JNI层函数名特别长,书写起来不方便! C$ X/ D: H* x* {5 `4 f
(3)初次调用native函数时需要根据函数名搜索对应的JNI层函数来建立连接,影响运行效率
8 D" M* Y4 Q# o& Q: Q) E3 ]! ?0 j2.动态注册  T* \4 s! p6 [+ U- G  w% x
从静态注册中可以发现java函数和JNI函数是有一个数据结构来保存关联的。这个数据结构大致如下:
" v/ P1 o% m- ]- k" g2 t% H
  E" v4 B' s$ z) m3 A. _$ h有一个指向这种数据结构的二级指针来保存所有的本地方法。所以我们就可以考虑在类加载的时候直接将本地方法和JNI函数建立连接,这种方式就是动态注册。, v" L* f8 K0 f! \2 Y
而在java层我们只用了System.loadLibray方法,所以因该在这个方法中进行动态注册。顺藤摸瓜我们可以找到JNI_Onload函数,在执行System.loadLibrary方法时。
7 U) A. F9 p5 ?. t2 _JNI_Onload函数主要操作有两部分:6 E$ T8 Z* [% P
1.通过JNIEnv的FindClass函数找到java层注册JNI的方法3 }9 f) M& Y( U, T9 H4 ], W+ X
2.调用该函数
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

朋友一起走 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    16