Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
一.JNI概述, c* Y6 F/ E; C4 v% Y/ i
JNI即java native interface,通俗的说,JNI是一种技术,通过这种技术可以做到以下两点:* J3 J0 H) u2 m/ W. s7 z: p
1.java程序中的函数可以调用C/C++ 编写的函数2 z* s" l: r5 d) a' a
2.C/C++ 程序可以通过它调用java层的函数
" W- w! s# Q* q  R5 ~, `5 u, Q2 UJNI主要是完成java和C/C++ 代码和交互,但是java为什么要调用C/C++ 程序哪,这是不是破坏了java的平台无关特性,其实java需要调用C/C++ 程序需要是出于以下几点:$ p- J" H7 E6 a" Z9 a0 G
1.早在java语言诞生前,很多程序都是由C/C++ 语言编写的,他们遍布在软件世界的各个角落。虽然java出世后备受追捧,但是并不能完全替代C/C++ ,很多功能还需要调用它们,重复制造轮子会影响用户的体验,而JNI技术则解决了这个问题。8 P$ x9 `2 \8 Q8 e; n+ h4 Q
2.C/C++ 程序的运行效率是高于java的(虽然后期推出了JIT技术),在很多在运行速度上要求比较高、调用比较频繁的场合就可能选用C/C++ 语言。" ^& r7 E# f# K! H
3.有时需要和平台代码通信而又没有对应的Java API的时候,也需要使用C/C++ 代码编写好相应的代码供Java层使用。
8 S& K& I2 H" J; E3 {4 `  f在Android平台上,JNI就是一座将Java世界和Native世界的天堑变成通途的桥,如下图所示,它展示了Android平台上JNI所处的位置。
7 s* {# A& i& Z3 ]# E' b+ }+ a+ t9 a
1 T1 G( _4 h! t1 _- f$ Q# n' d( h( S5 X7 n3 w8 A

! @9 v7 s2 U: w1 P9 T. ?5 ^3 C二.SWIG概述
$ J5 s1 I( N7 s- D7 B( rSWIG是一个软件开发工具,它将用C和C ++编写的程序与各种高级编程语言连接起来。SWIG与不同类型的目标语言一起使用。SWIG可以作为测试和原型化C / C ++软件的工具。SWIG通常用于解析C / C ++接口并生成各种语言调用C / C ++代码所需的“粘合代码”。转化代码时是非常方便实用的,而且用起来也不是太复杂,最主要的是写一次代码可以为多种语言调用。工具是开源的,学习手册请查看 http://www.swig.org/8 F: ]2 n- C6 W( X+ R
三.示例
) |6 c; I, O+ x# L. `! [+ [! g, ^俗话说,百闻不如一见,我们先实现一个JNI调用的例子,然后再解析其原理。以下操作是在ubuntu环境下进行的。
/ n9 W3 d5 j. E' _1.搭建编译环境
3 g) Z; t1 O% y0 n# ]Android studio,android 原生开发工具包(NDK),CMake,LLDB 调试工具。可以使用SDK 管理器安装这些组件:! }" M+ ~0 p. e* m, q) {+ f6 _
1.1在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。  m4 |1 a1 M5 @3 b
1.2点击SDK Tools 标签。5 h9 P0 a" D. M
1.3选中LLDB、CMake 和NDK 旁的复选框。0 K! h$ ]9 n, ~
1.4点击Apply,然后在弹出式对话框中点击OK。
9 U; G. e: E0 e# F, C1.5安装完成后,点击Finish,然后点击OK。
# s. X5 V% j! T2.编写C代码, e1 J! j5 h# n. z5 b5 I' ~  |
5 z; H! R# Z7 Z4 a' J8 G" V4 b
3.编写SWIG的转化文件
* o+ S2 X7 G6 Q1 G
# z2 ^0 t; J0 }5 b6 M: E. A% I+ e7 Q4.生成JNI调用所需的文件  M/ [# `5 ~& n$ g3 r3 f
使用命名swig -java -package com.example.jni add.i,会生成三个文件
( \' v# d! ~& y5 Vadd_wrap.c:用于和java 对接的JNI函数
  t+ g5 G: i8 U! r) U+ `; T6 AaddJNI.java:用于和JNI 对接的java 本地接口
  p% ]6 K: ]$ d+ B7 Z& Zadd.java:java 本地调用的接口,他调用addJNI.java 中的内容
$ e' p& t1 X; G; Zcat add_wrap.c(因为生成的文件比较大,所以需要在)
' n6 i4 F! q# T% ^4 `( A: t  [' F' e2 ]7 ]& |
cat addJNI.java
% q  d8 Z' V+ u0 O5 O  t/ x5 V4 h/ t4 X$ [) m
cat add.java% B* Q. Y  O2 j# C! \: Q) G# i
3 g# e  G) g8 f" H9 A
5.生成C库- e( J" B( {% k' A" j6 `4 W$ L7 X; ?, W
注意两点:
% W# w, j5 l+ h7 y5 s5.1 将刚生成都add_wrap.c文件添加到库,如果没有源码可以将add_wrap.c编译为独立的动态库文件
' }" s" L, d: m  Y5.2 添加jni.h文件(安装NDK后就会存在)到可查找的头文件目录中,add_wrap.c使用了jni.h文件4 u9 Q) |* |0 D# {; N+ F& D2 e6 l- {
cat CMakeLists.txt
. h" F, O1 v% L
* l3 r, e! N8 Z3 u" r: z执行脚本:
! K9 D1 e  q8 p. B9 K
1 c) F; f. b: b* A0 m) P生成的目录如下
6 o; {9 O; s7 G$ {  H2 pbuild- Y( U5 R1 U/ E) z
├── arm64-v8a) N/ f: l  k3 e9 L/ |
│ └── lib% E8 [# \! w$ \+ \
│ └── libadd.so
2 C3 ~, V2 U* k5 x4 @├── armeabi-v7a: A+ v; a3 f; x. m, P. s8 H
│ └── lib
, D! l/ q& o5 f5 J3 W│ └── libadd.so& G% e3 _+ m  i& g! t8 r& h$ r% f
├── x86
! C/ p1 o" q5 Q+ E7 {' [│ └── lib
$ m+ q; p  l$ u" i- O│ └── libadd.so1 s" Q( ]% Q4 ~# E8 A. C* x: N
└── x86_64
# B2 m$ W& g  d+ p/ |/ _5 @, n- d' @└── lib
5 L3 ~1 A4 [9 r└── libadd.so
+ r& g) o$ b  b4 d将so文件移到lib同级目录下,删除lib文件(和Android Studio配置一致)
. D: T* \! P! K* ?; Q. |5 K6.Android studio测试运行
. U) z% b/ c1 m8 }6.1 Android studio创建新的项目,在向导的Configure your new project部分,选中Include C++ Support 复选框。
) n6 @! K8 h3 l- ^' P6 Z; H% R6.2 将生成的so 文件复制到app\src\main\jniLibs 目录下(四种库和文件夹一起复制)
# h2 k  {$ u9 O% }8 Z% |% g6.3 将SWIG生成的add.java 和addJNI.java 文件复制 app\src\main\java\com\example\jni 目录下(与导出时package 参数一致)
2 U1 e. t( n8 g  A: t7 x) H5 j6.4 测试程序如下,测试成功
. K, v- R4 [' K
; R: p) ]2 u. d0 h. Z- C  O四.加载过程* p+ t2 g3 m+ {4 _! Y) C
大家都知道,如果我们希望使用C/C++函数,将其加载到内存是基础,那到底什么时候加载,怎么加载的哪?4 c) }: v9 r7 s) B
其实只要在调用前,可以在任何时候加载本地库,但是我们一般的做法是在java类static语句中加载,加载的方式是通过System.loadLibrary方法。需要注意的是System.loadLibrary的参数是动态库的名字,如上示例,在linux下为libadd.so,而在window下为add.dll。其实JNI对java程序员还是比较照顾的,他们只需要加载库文件,其他的都不需要做。native修饰的函数也已经被JNI自动生成,如果在*JNI.java中添加加载库的static函数,那么java程序员连加载库都免了,是不是感觉很方便。
5 c0 K+ e3 g- l1 n( t" x五.注册过程
* m* F: q0 P  N* S, M; w读到这里,相信大家应该还是有一个很大的疑问,C库我已经将其加载,但是它又是怎么和java函数联系起来的哪,为什么我调用java函数而它会自动调用C库的函数,他们之间是怎么联系起来的哪?
- Y) }5 [! S+ x0 k* N1 C# Z正常的java方法是在加载的过程中会被放到方法区,而native方法则没有被定义。这就必须存在一个注册的过程,即将C库中的函数(上例中add_wrap.c中的函数)与Java中Native方法(上例中addJIN.java中方法)建立对应。注册过程分为两种:
: E, f9 x& I1 b) N1.静态注册
& ^9 _7 A4 s8 x5 J+ D当java层调用add方法时,如上例所示,他会从C库中查找Java_com_example_jni_addJNI_add方法,如果没有则报错,如果找到就将两个函数建立一个关联,以后调用add方法时就会直接调用Java_com_example_jni_addJNI_add函数。从这里可以看出,静态方法是通过函数名来建立java函数和C函数之间的对应的。所以它要求对应的C函数(add_wrap.c)必须遵循特定的格式。上例就是通过静态的方式进行注册。. G* s' V  {- h; E& \% c. w
静态注册是有缺点的:* m8 _7 e' J5 F+ }0 I. ?- p3 D
(1)需要编译所有生命了native方法的java类,每个class文件都需要对应一个**JNI.java的文件。* H, y+ u8 H& k2 e/ q
(2)生成的JNI层函数名特别长,书写起来不方便
+ L* Z: I" f5 G$ f& Y9 X* p" U(3)初次调用native函数时需要根据函数名搜索对应的JNI层函数来建立连接,影响运行效率
. |5 `3 r4 j4 i3 d! _0 @7 P0 U2.动态注册% u5 \2 U* n$ ]0 j' ^" p
从静态注册中可以发现java函数和JNI函数是有一个数据结构来保存关联的。这个数据结构大致如下:# [# ^8 n  `9 N  s/ ~  _

5 C1 `, h( |4 F* v. N/ A% ~% S有一个指向这种数据结构的二级指针来保存所有的本地方法。所以我们就可以考虑在类加载的时候直接将本地方法和JNI函数建立连接,这种方式就是动态注册。
! L( I& v; P0 t" j& _) j- S; \6 [而在java层我们只用了System.loadLibray方法,所以因该在这个方法中进行动态注册。顺藤摸瓜我们可以找到JNI_Onload函数,在执行System.loadLibrary方法时。3 w* J7 r" a2 Q- Y4 c$ ?6 ^
JNI_Onload函数主要操作有两部分:5 `# f6 e7 Y* h$ l5 d/ ^3 v
1.通过JNIEnv的FindClass函数找到java层注册JNI的方法
3 v/ k- z% D8 p  b2.调用该函数
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

朋友一起走 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    16