Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
一.JNI概述
8 A' @( z2 a: @% K4 [JNI即java native interface,通俗的说,JNI是一种技术,通过这种技术可以做到以下两点:2 E( j/ a; W6 w* M- w/ E; D% D8 @
1.java程序中的函数可以调用C/C++ 编写的函数+ ?1 e9 G2 [* B& c
2.C/C++ 程序可以通过它调用java层的函数% _. f/ H6 Q2 d, o/ a
JNI主要是完成java和C/C++ 代码和交互,但是java为什么要调用C/C++ 程序哪,这是不是破坏了java的平台无关特性,其实java需要调用C/C++ 程序需要是出于以下几点:. p4 M0 D. w+ M( i5 r" w& ^0 f
1.早在java语言诞生前,很多程序都是由C/C++ 语言编写的,他们遍布在软件世界的各个角落。虽然java出世后备受追捧,但是并不能完全替代C/C++ ,很多功能还需要调用它们,重复制造轮子会影响用户的体验,而JNI技术则解决了这个问题。
1 n6 Q+ H/ t+ h% q9 x$ W" h) V2.C/C++ 程序的运行效率是高于java的(虽然后期推出了JIT技术),在很多在运行速度上要求比较高、调用比较频繁的场合就可能选用C/C++ 语言。
* e# p: |9 I9 A  i! o3 w: X3.有时需要和平台代码通信而又没有对应的Java API的时候,也需要使用C/C++ 代码编写好相应的代码供Java层使用。5 G2 c* ]% P9 Z" @# s: r
在Android平台上,JNI就是一座将Java世界和Native世界的天堑变成通途的桥,如下图所示,它展示了Android平台上JNI所处的位置。
: i9 r: Z# F2 r
8 Z. A% S, H0 A& P' }6 [7 \) \9 w- l0 m! n

: d- a$ h) q  b0 ^; q' r# }二.SWIG概述
* l: ^. {7 M* \: G  nSWIG是一个软件开发工具,它将用C和C ++编写的程序与各种高级编程语言连接起来。SWIG与不同类型的目标语言一起使用。SWIG可以作为测试和原型化C / C ++软件的工具。SWIG通常用于解析C / C ++接口并生成各种语言调用C / C ++代码所需的“粘合代码”。转化代码时是非常方便实用的,而且用起来也不是太复杂,最主要的是写一次代码可以为多种语言调用。工具是开源的,学习手册请查看 http://www.swig.org/1 T) A9 T/ q) c$ |% w9 o* K  [
三.示例5 G* H% S% l' e/ Y/ I
俗话说,百闻不如一见,我们先实现一个JNI调用的例子,然后再解析其原理。以下操作是在ubuntu环境下进行的。
7 r8 Z/ u' R6 ?. Q3 k4 |5 Q) j1.搭建编译环境: B' S, V& M& b( r' y2 P9 \" ^
Android studio,android 原生开发工具包(NDK),CMake,LLDB 调试工具。可以使用SDK 管理器安装这些组件:. c* G# K1 |) v* ?$ Q0 @0 P
1.1在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。
1 E% T: S2 |7 R; J4 q& Y1.2点击SDK Tools 标签。. f/ [2 n. ]% S/ {- l
1.3选中LLDB、CMake 和NDK 旁的复选框。, a: ?4 i  c' X
1.4点击Apply,然后在弹出式对话框中点击OK。3 o2 X5 g8 _6 F- X0 h% I
1.5安装完成后,点击Finish,然后点击OK。6 J. x& K& f5 w0 }' i
2.编写C代码7 ]6 }) M1 ~" I

2 f3 g5 H0 q; m8 K) `  z3.编写SWIG的转化文件4 ~, A: d7 [7 N1 R0 l/ R+ d1 O

6 U/ r: M: g2 m( `4.生成JNI调用所需的文件* ]) H4 R1 ]/ d9 Y
使用命名swig -java -package com.example.jni add.i,会生成三个文件
& F+ K3 A1 I5 q; I9 K: Jadd_wrap.c:用于和java 对接的JNI函数
; ?( K: J  i+ jaddJNI.java:用于和JNI 对接的java 本地接口5 u+ A$ Z. s1 v' {1 x; d- l/ W. r
add.java:java 本地调用的接口,他调用addJNI.java 中的内容2 P6 ]7 J  K/ Y. @. B6 ?$ ^4 g5 e
cat add_wrap.c(因为生成的文件比较大,所以需要在), U- R0 J: I; M1 g9 e% j/ W
* _* E$ U, b2 u- d" m0 F* s9 B) b
cat addJNI.java
) ?8 G. ?4 l2 z# _# }9 g! |& s( q. y+ d; V7 M
cat add.java) m! D, |8 `* S4 q2 u

5 [* Y6 ^" x  k3 p& Y5.生成C库
: i* ^( D0 i3 ~7 d3 P% H5 U注意两点:
: f: E" r$ V  N3 i& a- t" _5.1 将刚生成都add_wrap.c文件添加到库,如果没有源码可以将add_wrap.c编译为独立的动态库文件
: q  z, v, b) j5 C( D- i5.2 添加jni.h文件(安装NDK后就会存在)到可查找的头文件目录中,add_wrap.c使用了jni.h文件
4 z3 [* H- p* P. V; }: G, D6 V' ?cat CMakeLists.txt" U1 B) @2 a, o# |
" C8 U! i% g; G$ w) q
执行脚本:
3 m7 }& m3 d8 \$ ~
. N; ~9 t" W, C/ \生成的目录如下
3 c. V" {/ A% w( u. jbuild
2 N, Z: C( q3 `├── arm64-v8a7 c' @6 T2 k6 a" J' H/ F' S
│ └── lib" l) n" m9 N+ n
│ └── libadd.so3 C' \" Y3 n" t7 t( W: D
├── armeabi-v7a, \8 f" }( d. d$ w) {
│ └── lib( ?3 ?3 A/ g- ]3 I5 U+ h
│ └── libadd.so- N3 f+ P2 w/ n! ^
├── x86
1 Y7 w7 q- x6 c7 f2 k! T│ └── lib
: }+ w& s5 |- O  R4 J│ └── libadd.so3 g1 S6 g! M) M# Z2 s/ f
└── x86_64
2 Z4 X8 y7 u, Z1 p( @└── lib1 K+ I! q! s, F+ w, B
└── libadd.so
' g- T3 _% q# j7 ^& I" ]将so文件移到lib同级目录下,删除lib文件(和Android Studio配置一致)
$ M, B& x+ p, K6.Android studio测试运行
/ Y' ~5 s. p* V& L6.1 Android studio创建新的项目,在向导的Configure your new project部分,选中Include C++ Support 复选框。
! R, _5 o- z8 J' I2 g6.2 将生成的so 文件复制到app\src\main\jniLibs 目录下(四种库和文件夹一起复制)
; Y% [; ]5 x* H, l0 H$ x# f6.3 将SWIG生成的add.java 和addJNI.java 文件复制 app\src\main\java\com\example\jni 目录下(与导出时package 参数一致)
9 `0 W" ^( m# [7 C2 Y: w: H+ L6.4 测试程序如下,测试成功, J) |9 ?# ?" r2 u0 C$ p: h2 R* V

' M/ B  f  \2 |7 w2 {四.加载过程/ g$ ?  a. `8 m4 B
大家都知道,如果我们希望使用C/C++函数,将其加载到内存是基础,那到底什么时候加载,怎么加载的哪?
$ l+ n, S9 ^, Q8 I3 Z其实只要在调用前,可以在任何时候加载本地库,但是我们一般的做法是在java类static语句中加载,加载的方式是通过System.loadLibrary方法。需要注意的是System.loadLibrary的参数是动态库的名字,如上示例,在linux下为libadd.so,而在window下为add.dll。其实JNI对java程序员还是比较照顾的,他们只需要加载库文件,其他的都不需要做。native修饰的函数也已经被JNI自动生成,如果在*JNI.java中添加加载库的static函数,那么java程序员连加载库都免了,是不是感觉很方便。! w  h; _" v" j" a5 t
五.注册过程4 h8 U! O2 H: p  Z* @' A
读到这里,相信大家应该还是有一个很大的疑问,C库我已经将其加载,但是它又是怎么和java函数联系起来的哪,为什么我调用java函数而它会自动调用C库的函数,他们之间是怎么联系起来的哪?/ ]( M$ ]3 Q+ ^9 J
正常的java方法是在加载的过程中会被放到方法区,而native方法则没有被定义。这就必须存在一个注册的过程,即将C库中的函数(上例中add_wrap.c中的函数)与Java中Native方法(上例中addJIN.java中方法)建立对应。注册过程分为两种:
( i) U; V/ O3 g- |1.静态注册
' ^. Q6 O3 W+ k8 u0 p6 x: o当java层调用add方法时,如上例所示,他会从C库中查找Java_com_example_jni_addJNI_add方法,如果没有则报错,如果找到就将两个函数建立一个关联,以后调用add方法时就会直接调用Java_com_example_jni_addJNI_add函数。从这里可以看出,静态方法是通过函数名来建立java函数和C函数之间的对应的。所以它要求对应的C函数(add_wrap.c)必须遵循特定的格式。上例就是通过静态的方式进行注册。
/ G; _# d3 X1 r静态注册是有缺点的:
7 v) ^* {1 {  O(1)需要编译所有生命了native方法的java类,每个class文件都需要对应一个**JNI.java的文件。5 ~' f( s2 l; Y7 j' i+ a) ?  l
(2)生成的JNI层函数名特别长,书写起来不方便  n6 k$ j6 b/ A  V3 ~9 L& g/ E
(3)初次调用native函数时需要根据函数名搜索对应的JNI层函数来建立连接,影响运行效率) f. I! A2 F& ^- y
2.动态注册8 I5 @! b/ s0 X: s
从静态注册中可以发现java函数和JNI函数是有一个数据结构来保存关联的。这个数据结构大致如下:& d7 H* ?- C2 Y' U5 u
& g$ d1 x1 q7 N  ?
有一个指向这种数据结构的二级指针来保存所有的本地方法。所以我们就可以考虑在类加载的时候直接将本地方法和JNI函数建立连接,这种方式就是动态注册。
# Y) H  j: f# L, E, J1 o! `6 G而在java层我们只用了System.loadLibray方法,所以因该在这个方法中进行动态注册。顺藤摸瓜我们可以找到JNI_Onload函数,在执行System.loadLibrary方法时。/ x2 ~" p, l9 L, a8 @3 K
JNI_Onload函数主要操作有两部分:! c: T+ b" }+ P  V! z% s6 l, m
1.通过JNIEnv的FindClass函数找到java层注册JNI的方法# G, z( }; ]& l- q
2.调用该函数
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

朋友一起走 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    16