Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
一.JNI概述# k3 `  g* A+ l, c; |
JNI即java native interface,通俗的说,JNI是一种技术,通过这种技术可以做到以下两点:
1 H, q. V7 O& i: Z( V0 |1.java程序中的函数可以调用C/C++ 编写的函数
- P( _! A- b) o) J4 ~8 R- k2.C/C++ 程序可以通过它调用java层的函数
& D* Y1 n- P" g. N# DJNI主要是完成java和C/C++ 代码和交互,但是java为什么要调用C/C++ 程序哪,这是不是破坏了java的平台无关特性,其实java需要调用C/C++ 程序需要是出于以下几点:
3 Z4 z8 {. m5 |9 x  E/ N1.早在java语言诞生前,很多程序都是由C/C++ 语言编写的,他们遍布在软件世界的各个角落。虽然java出世后备受追捧,但是并不能完全替代C/C++ ,很多功能还需要调用它们,重复制造轮子会影响用户的体验,而JNI技术则解决了这个问题。
( O9 a( ~" ]' }* ?+ ?+ ~1 j2.C/C++ 程序的运行效率是高于java的(虽然后期推出了JIT技术),在很多在运行速度上要求比较高、调用比较频繁的场合就可能选用C/C++ 语言。/ b' y- C) W! l, p6 Z7 c
3.有时需要和平台代码通信而又没有对应的Java API的时候,也需要使用C/C++ 代码编写好相应的代码供Java层使用。
' B* H( |3 i( w! |在Android平台上,JNI就是一座将Java世界和Native世界的天堑变成通途的桥,如下图所示,它展示了Android平台上JNI所处的位置。
) s  X! p% S3 L9 q" k6 A% [6 G' F- t8 `3 L
% ]6 q8 k  H2 G+ R- `
/ D9 {, c2 j* N3 M
二.SWIG概述
: R! A9 M$ X7 h2 i: `SWIG是一个软件开发工具,它将用C和C ++编写的程序与各种高级编程语言连接起来。SWIG与不同类型的目标语言一起使用。SWIG可以作为测试和原型化C / C ++软件的工具。SWIG通常用于解析C / C ++接口并生成各种语言调用C / C ++代码所需的“粘合代码”。转化代码时是非常方便实用的,而且用起来也不是太复杂,最主要的是写一次代码可以为多种语言调用。工具是开源的,学习手册请查看 http://www.swig.org/
6 O2 J5 i  ~1 I& a三.示例' o5 N# `1 L: {, r) F9 c
俗话说,百闻不如一见,我们先实现一个JNI调用的例子,然后再解析其原理。以下操作是在ubuntu环境下进行的。
( Q) U% Z3 Q; [( d) K2 I6 G1.搭建编译环境+ Q2 g# |6 b! ]% V/ l* S" L+ b
Android studio,android 原生开发工具包(NDK),CMake,LLDB 调试工具。可以使用SDK 管理器安装这些组件:+ W6 T" o4 E8 f8 e& Z
1.1在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。
$ z" e9 h. m$ Y! G2 S" H' }8 S6 p2 F& c; J1.2点击SDK Tools 标签。  j$ H% p0 N+ ^/ ?) a% ^, Q( `1 |
1.3选中LLDB、CMake 和NDK 旁的复选框。
% Y3 Y  q: |7 y- D1.4点击Apply,然后在弹出式对话框中点击OK。
/ [' t+ [6 y! y1.5安装完成后,点击Finish,然后点击OK。
1 @1 j/ ?8 j' ~! R5 p0 }2.编写C代码
( g6 H. Z2 z# I/ v, w% \: _  p: s
3 A- r- e, J& p. V  v/ q3.编写SWIG的转化文件' o# @+ u* D$ U0 e) Y
' q4 r7 v1 g9 A( L* Z  w0 h( m  h
4.生成JNI调用所需的文件
4 G/ h  ^' E5 H使用命名swig -java -package com.example.jni add.i,会生成三个文件* p  G! b! }3 i2 M3 I# ~% h2 V
add_wrap.c:用于和java 对接的JNI函数
2 q2 {7 a. U- P0 R/ p2 GaddJNI.java:用于和JNI 对接的java 本地接口3 D7 `; ]5 f" E' \  A3 }  p+ U3 o
add.java:java 本地调用的接口,他调用addJNI.java 中的内容7 `2 i. G8 w9 q( Y. ]
cat add_wrap.c(因为生成的文件比较大,所以需要在)
1 r# @0 O& k0 F6 T, L8 ^
3 \8 e1 G6 y2 s3 E( f3 g5 Ecat addJNI.java& p9 u5 H: F) G8 v) P4 p5 S# P

9 I" g- C6 w- E! T% Hcat add.java( q4 s3 P' s. p3 h5 Y" q) H2 w

  ?' y. c* O& Q) h5.生成C库
6 E; Y2 E/ |" _2 \7 t. E" X注意两点:
4 n" K$ q  o& o3 S5 H) P8 w5.1 将刚生成都add_wrap.c文件添加到库,如果没有源码可以将add_wrap.c编译为独立的动态库文件
# Y8 d6 N" h3 a! N5.2 添加jni.h文件(安装NDK后就会存在)到可查找的头文件目录中,add_wrap.c使用了jni.h文件+ W0 S0 V! w* b! f' l3 K
cat CMakeLists.txt% T2 L" R, ~0 k' b+ u
" Z% f$ p! I+ m
执行脚本:+ ~6 E/ [& W- R
! w. d( Z; {0 k) m
生成的目录如下, H- P7 d: w, b4 i, `
build, H3 ^6 A" G/ M( s6 P
├── arm64-v8a0 F! l/ C' z* p4 y+ [
│ └── lib$ O: i$ a/ P+ ^5 {9 q
│ └── libadd.so
0 B; v( N/ s, D├── armeabi-v7a5 E5 R: S3 A1 j
│ └── lib: I% C: P1 f- `, y) L
│ └── libadd.so6 I5 L0 i. X  O
├── x861 L9 r, f6 K* A6 a# y9 A
│ └── lib
8 V, {& m, R: F( f│ └── libadd.so  V  \8 n2 t5 F! V1 ]7 _4 h
└── x86_64, N0 f$ M$ N7 f/ Z6 E% f) m* W* M
└── lib# S; a7 ~# |/ f9 E
└── libadd.so
8 q; p8 ^2 L. ?- r4 Z5 B( E7 h将so文件移到lib同级目录下,删除lib文件(和Android Studio配置一致)* Y- }, g- c/ _- B8 |1 ~# S
6.Android studio测试运行
7 e2 z( j; z0 l4 _/ L8 x6.1 Android studio创建新的项目,在向导的Configure your new project部分,选中Include C++ Support 复选框。. k$ n( v& C! ~2 M& w2 q0 ~* V
6.2 将生成的so 文件复制到app\src\main\jniLibs 目录下(四种库和文件夹一起复制), W* S/ Y* q+ ^5 v' V$ L
6.3 将SWIG生成的add.java 和addJNI.java 文件复制 app\src\main\java\com\example\jni 目录下(与导出时package 参数一致). N; t' R1 z6 `  z/ i  G, b
6.4 测试程序如下,测试成功
2 B/ O! Z0 ~: t& A* f
9 B3 B5 W# b* O% K4 J四.加载过程( o) g( @; K6 D1 G6 x
大家都知道,如果我们希望使用C/C++函数,将其加载到内存是基础,那到底什么时候加载,怎么加载的哪?
3 y# ]$ b) R9 q+ t其实只要在调用前,可以在任何时候加载本地库,但是我们一般的做法是在java类static语句中加载,加载的方式是通过System.loadLibrary方法。需要注意的是System.loadLibrary的参数是动态库的名字,如上示例,在linux下为libadd.so,而在window下为add.dll。其实JNI对java程序员还是比较照顾的,他们只需要加载库文件,其他的都不需要做。native修饰的函数也已经被JNI自动生成,如果在*JNI.java中添加加载库的static函数,那么java程序员连加载库都免了,是不是感觉很方便。
+ R( S; y0 f. R3 N8 g# J! s7 [, n: P五.注册过程- ?. A0 B- E/ F
读到这里,相信大家应该还是有一个很大的疑问,C库我已经将其加载,但是它又是怎么和java函数联系起来的哪,为什么我调用java函数而它会自动调用C库的函数,他们之间是怎么联系起来的哪?
3 T7 g3 D6 }$ w正常的java方法是在加载的过程中会被放到方法区,而native方法则没有被定义。这就必须存在一个注册的过程,即将C库中的函数(上例中add_wrap.c中的函数)与Java中Native方法(上例中addJIN.java中方法)建立对应。注册过程分为两种:
6 Y5 I- m: g* I8 _, p1 i$ o1.静态注册
0 z  K' |2 i, l+ h当java层调用add方法时,如上例所示,他会从C库中查找Java_com_example_jni_addJNI_add方法,如果没有则报错,如果找到就将两个函数建立一个关联,以后调用add方法时就会直接调用Java_com_example_jni_addJNI_add函数。从这里可以看出,静态方法是通过函数名来建立java函数和C函数之间的对应的。所以它要求对应的C函数(add_wrap.c)必须遵循特定的格式。上例就是通过静态的方式进行注册。
0 s. ]% J1 G/ N( f; o- i静态注册是有缺点的:
* M; e8 k! |, t( V9 c(1)需要编译所有生命了native方法的java类,每个class文件都需要对应一个**JNI.java的文件。1 b/ d9 R# O6 ~/ c; L! e
(2)生成的JNI层函数名特别长,书写起来不方便& l$ E; H! b" R! c& b
(3)初次调用native函数时需要根据函数名搜索对应的JNI层函数来建立连接,影响运行效率
* H* P9 G+ u7 P2.动态注册4 Y+ x+ Q  d1 T& ?( S- I  C
从静态注册中可以发现java函数和JNI函数是有一个数据结构来保存关联的。这个数据结构大致如下:
4 X: x" r8 r9 o# g! r# M
& u- x+ N0 I. |' P4 q/ k有一个指向这种数据结构的二级指针来保存所有的本地方法。所以我们就可以考虑在类加载的时候直接将本地方法和JNI函数建立连接,这种方式就是动态注册。8 L* U: D, G0 o9 _) Z  q' t
而在java层我们只用了System.loadLibray方法,所以因该在这个方法中进行动态注册。顺藤摸瓜我们可以找到JNI_Onload函数,在执行System.loadLibrary方法时。
  f% Y0 Y% ~9 kJNI_Onload函数主要操作有两部分:
6 e8 b0 C4 f# \  S) ]" a1.通过JNIEnv的FindClass函数找到java层注册JNI的方法
; b/ s6 j3 V7 L# R; d& |2 m) ~( m: X2.调用该函数
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

朋友一起走 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    16