Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
一.JNI概述/ h9 W6 w9 T" @8 a8 M
JNI即java native interface,通俗的说,JNI是一种技术,通过这种技术可以做到以下两点:* L9 ^! {" y- K- l' x% q/ k6 Y
1.java程序中的函数可以调用C/C++ 编写的函数
/ Q, |+ E: ?9 v2.C/C++ 程序可以通过它调用java层的函数. x; V9 o9 P& a  O" ~: r- [
JNI主要是完成java和C/C++ 代码和交互,但是java为什么要调用C/C++ 程序哪,这是不是破坏了java的平台无关特性,其实java需要调用C/C++ 程序需要是出于以下几点:
- O7 V: J7 m. \1 C; I1.早在java语言诞生前,很多程序都是由C/C++ 语言编写的,他们遍布在软件世界的各个角落。虽然java出世后备受追捧,但是并不能完全替代C/C++ ,很多功能还需要调用它们,重复制造轮子会影响用户的体验,而JNI技术则解决了这个问题。& E' S# P& m2 ~
2.C/C++ 程序的运行效率是高于java的(虽然后期推出了JIT技术),在很多在运行速度上要求比较高、调用比较频繁的场合就可能选用C/C++ 语言。' m1 u6 Y9 n4 X6 }+ j" _
3.有时需要和平台代码通信而又没有对应的Java API的时候,也需要使用C/C++ 代码编写好相应的代码供Java层使用。7 M9 j$ V: M' j; {  F1 G# s
在Android平台上,JNI就是一座将Java世界和Native世界的天堑变成通途的桥,如下图所示,它展示了Android平台上JNI所处的位置。
, b& a4 p: d% }+ j+ ]% W
0 H7 F+ E6 z5 X3 [1 ?  u( r$ ?% d7 n& c: G( E9 f

0 @) n9 S: A/ ?# j9 S二.SWIG概述# `: Q& r4 M7 C! [; A4 @& E
SWIG是一个软件开发工具,它将用C和C ++编写的程序与各种高级编程语言连接起来。SWIG与不同类型的目标语言一起使用。SWIG可以作为测试和原型化C / C ++软件的工具。SWIG通常用于解析C / C ++接口并生成各种语言调用C / C ++代码所需的“粘合代码”。转化代码时是非常方便实用的,而且用起来也不是太复杂,最主要的是写一次代码可以为多种语言调用。工具是开源的,学习手册请查看 http://www.swig.org/
! F: h: q" P) d* {& ]7 w3 [8 y三.示例
6 Q) ^& f, _: f* K. x8 W俗话说,百闻不如一见,我们先实现一个JNI调用的例子,然后再解析其原理。以下操作是在ubuntu环境下进行的。
' E' I# ]# B: T! R1 S1.搭建编译环境
1 g4 @& \" R. K6 [5 n' f1 R+ TAndroid studio,android 原生开发工具包(NDK),CMake,LLDB 调试工具。可以使用SDK 管理器安装这些组件:+ P) }; i; m, Q/ @5 F! \
1.1在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。0 ?$ f0 a8 m: \: c
1.2点击SDK Tools 标签。
0 Q2 ?: W/ |  @1 g- |+ ~" d) [1.3选中LLDB、CMake 和NDK 旁的复选框。
' d0 q, ?  r2 l: L" p0 p1.4点击Apply,然后在弹出式对话框中点击OK。
8 w3 y: ^2 n$ ]8 P) H# e2 P& o1.5安装完成后,点击Finish,然后点击OK。$ A& |' ~5 l6 ?, E$ m
2.编写C代码
' k* P! ]# w4 d
% @6 X4 G$ W& \% [; |2 Y7 g3.编写SWIG的转化文件
8 q& x2 e, L! u6 y$ x1 j" H+ ~7 D8 @; S& a6 r
4.生成JNI调用所需的文件: ^% Y' |  [) g* f/ B& k0 k
使用命名swig -java -package com.example.jni add.i,会生成三个文件
1 I) _7 m6 R6 U( [5 i4 W1 ^add_wrap.c:用于和java 对接的JNI函数
* c. S' }( v% L/ paddJNI.java:用于和JNI 对接的java 本地接口. Z/ W/ L8 M5 Y7 k/ ]* h& h
add.java:java 本地调用的接口,他调用addJNI.java 中的内容
- X0 k/ c% }3 ecat add_wrap.c(因为生成的文件比较大,所以需要在); g2 G/ k, l4 R# _2 Y" q* c; o# l

- J# m/ E, K5 Kcat addJNI.java
) j4 a# F& J$ s! T; R% B. `4 O) A+ j* B3 g. \7 w# \2 h! c2 \
cat add.java9 W, q# n# ]) ^) q( J: o/ S7 L

, b  |( U$ i( p# n/ R3 B! T5.生成C库
! j) H! r" Y. j' h) I! ~& h注意两点:. @1 R  v+ z6 i+ E7 u7 }/ a6 y
5.1 将刚生成都add_wrap.c文件添加到库,如果没有源码可以将add_wrap.c编译为独立的动态库文件2 V3 L6 S9 G3 C  E; }. e$ k
5.2 添加jni.h文件(安装NDK后就会存在)到可查找的头文件目录中,add_wrap.c使用了jni.h文件. K1 \# p! c* {7 U( y& j' d0 `
cat CMakeLists.txt
4 y" Z9 r1 m& j
) D% [$ z! k$ l- r$ T* k: s执行脚本:
9 [8 @9 f. B; A/ H- p$ M# c/ S: i  v: R
生成的目录如下
! B2 I9 I( \/ p- Ibuild: Q" k  M0 K% n3 H* h: E1 r: k+ m
├── arm64-v8a+ F! r% v7 h7 y" _* F
│ └── lib
& B6 E, \/ W5 w! n, ^$ f0 f│ └── libadd.so
0 \/ Y4 ^; B; m' H├── armeabi-v7a
. S8 Z* S  r; {! f$ ^│ └── lib  F; X# H9 i/ {1 S
│ └── libadd.so9 m! a* ~- V  f1 V* B7 w. [
├── x86
* Q* L+ v, z. k) X│ └── lib$ x% d! n/ V/ x) f
│ └── libadd.so
9 F9 X: K5 G. z$ i6 ?8 O" z5 m└── x86_645 D$ [, h+ B' b  j- p/ f
└── lib
/ M3 ?9 {' H* e& Y4 D+ `, e4 M└── libadd.so
- `% h' K( R& f1 G' U3 E; R将so文件移到lib同级目录下,删除lib文件(和Android Studio配置一致)
* Q/ }) u, U+ l  ~) e, z6 `) u0 X! P6.Android studio测试运行
# s5 T  ?3 H0 Z  R6 s; |9 q! k6.1 Android studio创建新的项目,在向导的Configure your new project部分,选中Include C++ Support 复选框。
8 H& j2 T- |; `6.2 将生成的so 文件复制到app\src\main\jniLibs 目录下(四种库和文件夹一起复制)
: r* e! G' t- J. C) u7 g6.3 将SWIG生成的add.java 和addJNI.java 文件复制 app\src\main\java\com\example\jni 目录下(与导出时package 参数一致)
: ?) L. ]3 `% J$ Q7 \# U; `! G6.4 测试程序如下,测试成功2 Q7 h) h2 s& C" ~1 x( P
$ C6 ~3 \* h/ Y: U- R( W% m
四.加载过程
3 B4 C: r) b8 ?! [$ L5 R+ B大家都知道,如果我们希望使用C/C++函数,将其加载到内存是基础,那到底什么时候加载,怎么加载的哪?" K3 _' d: w* e: G4 l
其实只要在调用前,可以在任何时候加载本地库,但是我们一般的做法是在java类static语句中加载,加载的方式是通过System.loadLibrary方法。需要注意的是System.loadLibrary的参数是动态库的名字,如上示例,在linux下为libadd.so,而在window下为add.dll。其实JNI对java程序员还是比较照顾的,他们只需要加载库文件,其他的都不需要做。native修饰的函数也已经被JNI自动生成,如果在*JNI.java中添加加载库的static函数,那么java程序员连加载库都免了,是不是感觉很方便。
7 D8 W3 e, t4 B6 \; e五.注册过程
1 {, i+ z: ]0 g: `8 h4 F! c读到这里,相信大家应该还是有一个很大的疑问,C库我已经将其加载,但是它又是怎么和java函数联系起来的哪,为什么我调用java函数而它会自动调用C库的函数,他们之间是怎么联系起来的哪?0 r+ N' E: z. B3 _
正常的java方法是在加载的过程中会被放到方法区,而native方法则没有被定义。这就必须存在一个注册的过程,即将C库中的函数(上例中add_wrap.c中的函数)与Java中Native方法(上例中addJIN.java中方法)建立对应。注册过程分为两种:. i! o9 Y# m. i6 f) @3 T( P; ?
1.静态注册, c3 ?& M% @" {+ F* n- a& X
当java层调用add方法时,如上例所示,他会从C库中查找Java_com_example_jni_addJNI_add方法,如果没有则报错,如果找到就将两个函数建立一个关联,以后调用add方法时就会直接调用Java_com_example_jni_addJNI_add函数。从这里可以看出,静态方法是通过函数名来建立java函数和C函数之间的对应的。所以它要求对应的C函数(add_wrap.c)必须遵循特定的格式。上例就是通过静态的方式进行注册。9 ^9 f# }8 J5 K
静态注册是有缺点的:
% N3 q" F) j/ F; b9 c(1)需要编译所有生命了native方法的java类,每个class文件都需要对应一个**JNI.java的文件。
- {; h1 w/ o  J, _8 g6 l(2)生成的JNI层函数名特别长,书写起来不方便+ A6 m( q5 h2 a6 N8 o( g! W
(3)初次调用native函数时需要根据函数名搜索对应的JNI层函数来建立连接,影响运行效率- |; `/ z1 v" X9 z5 b6 P& j
2.动态注册
/ d+ W# [# z' s: O. C1 v* G! m从静态注册中可以发现java函数和JNI函数是有一个数据结构来保存关联的。这个数据结构大致如下:+ x' b- V' ?9 v4 B9 o2 l, ~, _

* z, f, i7 Q- V9 h有一个指向这种数据结构的二级指针来保存所有的本地方法。所以我们就可以考虑在类加载的时候直接将本地方法和JNI函数建立连接,这种方式就是动态注册。- O7 s( B  d" G* l' O
而在java层我们只用了System.loadLibray方法,所以因该在这个方法中进行动态注册。顺藤摸瓜我们可以找到JNI_Onload函数,在执行System.loadLibrary方法时。
) Y" J! v# R5 D, F) ~' s% LJNI_Onload函数主要操作有两部分:
2 g: j6 a: W' D% T4 l1.通过JNIEnv的FindClass函数找到java层注册JNI的方法5 C6 d; M) g) x0 b
2.调用该函数
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

朋友一起走 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    16