Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
一.JNI概述
4 B, _% J" ~) p2 Z* w+ q( ?JNI即java native interface,通俗的说,JNI是一种技术,通过这种技术可以做到以下两点:
. X; m, b& H0 p7 q% I; m1.java程序中的函数可以调用C/C++ 编写的函数
& U& |$ x& S) T! ]: L' d2.C/C++ 程序可以通过它调用java层的函数
" \# N6 \% H1 o* }" YJNI主要是完成java和C/C++ 代码和交互,但是java为什么要调用C/C++ 程序哪,这是不是破坏了java的平台无关特性,其实java需要调用C/C++ 程序需要是出于以下几点:  d5 r: q3 D. a; A; t+ |, ?
1.早在java语言诞生前,很多程序都是由C/C++ 语言编写的,他们遍布在软件世界的各个角落。虽然java出世后备受追捧,但是并不能完全替代C/C++ ,很多功能还需要调用它们,重复制造轮子会影响用户的体验,而JNI技术则解决了这个问题。- D! v+ U+ C7 h) n" C
2.C/C++ 程序的运行效率是高于java的(虽然后期推出了JIT技术),在很多在运行速度上要求比较高、调用比较频繁的场合就可能选用C/C++ 语言。
7 ~1 y" i% e$ J* F3.有时需要和平台代码通信而又没有对应的Java API的时候,也需要使用C/C++ 代码编写好相应的代码供Java层使用。2 ]% B( s6 y. D/ E8 J7 }
在Android平台上,JNI就是一座将Java世界和Native世界的天堑变成通途的桥,如下图所示,它展示了Android平台上JNI所处的位置。
8 j5 L/ R4 ^- ^/ W7 [
- Y: \* |, o) v$ ^% j. {) x4 f3 o; z0 Q0 R
0 w4 l7 |& v3 v. e! I. s0 [# O- o
二.SWIG概述
+ v$ ^7 C0 `3 a2 V0 VSWIG是一个软件开发工具,它将用C和C ++编写的程序与各种高级编程语言连接起来。SWIG与不同类型的目标语言一起使用。SWIG可以作为测试和原型化C / C ++软件的工具。SWIG通常用于解析C / C ++接口并生成各种语言调用C / C ++代码所需的“粘合代码”。转化代码时是非常方便实用的,而且用起来也不是太复杂,最主要的是写一次代码可以为多种语言调用。工具是开源的,学习手册请查看 http://www.swig.org/
3 w7 O$ r0 P0 P( t+ a; I三.示例
. A8 A0 N( k; R7 L3 }2 }俗话说,百闻不如一见,我们先实现一个JNI调用的例子,然后再解析其原理。以下操作是在ubuntu环境下进行的。
+ x$ [2 K, W2 A8 }# w* V& \1.搭建编译环境
# B$ |* t: i; I0 Z9 A8 z; Z! l9 `: S4 gAndroid studio,android 原生开发工具包(NDK),CMake,LLDB 调试工具。可以使用SDK 管理器安装这些组件:' k; f; p( A' ~8 m1 C. ], h
1.1在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。+ P! g+ {& g# @! ~0 q. g, K# d
1.2点击SDK Tools 标签。
  c+ C. U+ Y- Y! O9 [6 J1 u1.3选中LLDB、CMake 和NDK 旁的复选框。  L. s" g/ q. h; t% w& Q
1.4点击Apply,然后在弹出式对话框中点击OK。
9 @% S. U* V8 X1.5安装完成后,点击Finish,然后点击OK。+ |3 I/ m6 F& a/ P
2.编写C代码
4 E4 B4 y+ {$ L; r
% Z# y# a: ]7 |  o# o7 _5 s. g8 ]3.编写SWIG的转化文件
# E& I& {3 @; @: K6 r( }" g$ i3 |1 G  j# G7 {4 v% p% K
4.生成JNI调用所需的文件
" w! ~, f; r, u使用命名swig -java -package com.example.jni add.i,会生成三个文件' M  p# Q9 U" Z' E9 K! C% A5 D, D9 P0 `
add_wrap.c:用于和java 对接的JNI函数
/ I+ D$ x7 W) x  K7 C; z- qaddJNI.java:用于和JNI 对接的java 本地接口6 x) e; b/ H! {5 }1 s
add.java:java 本地调用的接口,他调用addJNI.java 中的内容
7 C: W3 H4 F4 P2 Jcat add_wrap.c(因为生成的文件比较大,所以需要在)
2 O- y3 R) g4 I5 R7 m, y, Z3 b: X5 s3 Z8 x) M$ E
cat addJNI.java3 N9 [2 M7 z1 j1 B; G
" H0 l, u) j/ W! {8 ~: u
cat add.java
$ @' V0 h/ `+ s) E5 E5 N5 g& T* Q
5.生成C库
  ?) Z4 i9 g4 U  b( }2 V注意两点:
9 G2 ]3 w& E& _$ S3 H5.1 将刚生成都add_wrap.c文件添加到库,如果没有源码可以将add_wrap.c编译为独立的动态库文件  X! V- j0 N' x. x& d& }+ |; }$ a7 f
5.2 添加jni.h文件(安装NDK后就会存在)到可查找的头文件目录中,add_wrap.c使用了jni.h文件
& p; X( _, y# F+ n$ a# V5 p& @' tcat CMakeLists.txt
5 I2 I5 }: d; u! y, |+ X( f% V  j& p4 f
5 g+ K6 e/ [/ d0 x) H执行脚本:1 R) c) K: T" C- \
  e) V; r0 b% K# D  q% V
生成的目录如下
; ?0 S# H# F. |4 h. ~- e" q6 `build' O. J9 |) Q+ A; {* s, I
├── arm64-v8a
. _4 a1 R+ [! W, z│ └── lib
1 S6 P) G9 V. N0 b% H1 S│ └── libadd.so
. Y! v- ~+ ~+ n" E) @$ Z├── armeabi-v7a# ^6 o0 B" Y" v! E/ G
│ └── lib4 ]1 i; G/ Y9 K) N6 T0 I2 g- t
│ └── libadd.so: A: ^5 B0 c; s( n( s* m
├── x86# F" h  e9 C; L& D0 C
│ └── lib
* \0 W4 G7 Y1 \! C│ └── libadd.so5 t6 p6 v) H3 A. [! O
└── x86_64
7 C; D1 e/ U) C" }/ S) K└── lib" }* J2 S7 Z- z+ P5 o$ ]* A! O/ L2 R( \
└── libadd.so
  K4 G* X4 G7 ]将so文件移到lib同级目录下,删除lib文件(和Android Studio配置一致)& `! O0 u7 |% p2 f4 X, D( y
6.Android studio测试运行
) I) M6 e$ ^8 y+ }2 u/ Y6.1 Android studio创建新的项目,在向导的Configure your new project部分,选中Include C++ Support 复选框。
4 X/ z9 i# H% K: L) l  o' ^. k6.2 将生成的so 文件复制到app\src\main\jniLibs 目录下(四种库和文件夹一起复制)
2 d, v* L1 G& ]- h3 ?6.3 将SWIG生成的add.java 和addJNI.java 文件复制 app\src\main\java\com\example\jni 目录下(与导出时package 参数一致)# N( E( a- V, Z
6.4 测试程序如下,测试成功0 c# i7 B( s' e- _. N
' g/ Z4 {, y4 @7 c
四.加载过程
- `% x- I' G2 c% T3 [大家都知道,如果我们希望使用C/C++函数,将其加载到内存是基础,那到底什么时候加载,怎么加载的哪?& `' H; z% ^+ ?7 _. L0 M9 \
其实只要在调用前,可以在任何时候加载本地库,但是我们一般的做法是在java类static语句中加载,加载的方式是通过System.loadLibrary方法。需要注意的是System.loadLibrary的参数是动态库的名字,如上示例,在linux下为libadd.so,而在window下为add.dll。其实JNI对java程序员还是比较照顾的,他们只需要加载库文件,其他的都不需要做。native修饰的函数也已经被JNI自动生成,如果在*JNI.java中添加加载库的static函数,那么java程序员连加载库都免了,是不是感觉很方便。, w; f3 e# V" [+ C! Y6 m
五.注册过程) @( u$ x2 p' m5 `8 Z3 ^
读到这里,相信大家应该还是有一个很大的疑问,C库我已经将其加载,但是它又是怎么和java函数联系起来的哪,为什么我调用java函数而它会自动调用C库的函数,他们之间是怎么联系起来的哪?, H, U/ Q- Q: P% v+ y
正常的java方法是在加载的过程中会被放到方法区,而native方法则没有被定义。这就必须存在一个注册的过程,即将C库中的函数(上例中add_wrap.c中的函数)与Java中Native方法(上例中addJIN.java中方法)建立对应。注册过程分为两种:2 j+ n/ X- C. b. q( ?0 E5 t4 {
1.静态注册6 }* K9 S1 ]* m+ B
当java层调用add方法时,如上例所示,他会从C库中查找Java_com_example_jni_addJNI_add方法,如果没有则报错,如果找到就将两个函数建立一个关联,以后调用add方法时就会直接调用Java_com_example_jni_addJNI_add函数。从这里可以看出,静态方法是通过函数名来建立java函数和C函数之间的对应的。所以它要求对应的C函数(add_wrap.c)必须遵循特定的格式。上例就是通过静态的方式进行注册。
6 ?# b# D+ n+ Z% P, B; T. R静态注册是有缺点的:
$ g) r: A1 e0 Z# x$ G( \(1)需要编译所有生命了native方法的java类,每个class文件都需要对应一个**JNI.java的文件。& {% Z( w3 K2 }- |+ [- K* z
(2)生成的JNI层函数名特别长,书写起来不方便
0 H) ]7 ]7 o5 M4 {- w(3)初次调用native函数时需要根据函数名搜索对应的JNI层函数来建立连接,影响运行效率
0 b* A3 v1 @/ G/ I2.动态注册4 r& H+ m  m, C1 ?! N+ J8 c
从静态注册中可以发现java函数和JNI函数是有一个数据结构来保存关联的。这个数据结构大致如下:
& ~% E% m4 v- e- R" H# |
2 e9 m# w* ~6 f/ p# R& [有一个指向这种数据结构的二级指针来保存所有的本地方法。所以我们就可以考虑在类加载的时候直接将本地方法和JNI函数建立连接,这种方式就是动态注册。1 L, \; @) C" I3 e! _
而在java层我们只用了System.loadLibray方法,所以因该在这个方法中进行动态注册。顺藤摸瓜我们可以找到JNI_Onload函数,在执行System.loadLibrary方法时。1 s+ i) S7 U3 W0 D8 ~; a
JNI_Onload函数主要操作有两部分:
6 ~1 R/ q; z* A% `  W( @1.通过JNIEnv的FindClass函数找到java层注册JNI的方法+ B  b5 _1 [* {; o: s9 o
2.调用该函数
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

朋友一起走 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    16