Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
一.JNI概述: I# M1 O5 V  H4 s6 f
JNI即java native interface,通俗的说,JNI是一种技术,通过这种技术可以做到以下两点:" ?, ~1 }/ C5 ?/ J& ~
1.java程序中的函数可以调用C/C++ 编写的函数/ A8 c8 B4 [9 m8 ]0 |3 T
2.C/C++ 程序可以通过它调用java层的函数
9 s+ |. E$ C7 X4 O' K$ xJNI主要是完成java和C/C++ 代码和交互,但是java为什么要调用C/C++ 程序哪,这是不是破坏了java的平台无关特性,其实java需要调用C/C++ 程序需要是出于以下几点:
8 Q8 J$ @2 o' ^: ?( H0 I1.早在java语言诞生前,很多程序都是由C/C++ 语言编写的,他们遍布在软件世界的各个角落。虽然java出世后备受追捧,但是并不能完全替代C/C++ ,很多功能还需要调用它们,重复制造轮子会影响用户的体验,而JNI技术则解决了这个问题。
5 i  y1 X+ S2 @/ V7 z$ H: Q2.C/C++ 程序的运行效率是高于java的(虽然后期推出了JIT技术),在很多在运行速度上要求比较高、调用比较频繁的场合就可能选用C/C++ 语言。+ Z0 f( I0 [% i$ {( F" w9 S- Q& }
3.有时需要和平台代码通信而又没有对应的Java API的时候,也需要使用C/C++ 代码编写好相应的代码供Java层使用。
& q) ~9 j" E/ z5 g4 t在Android平台上,JNI就是一座将Java世界和Native世界的天堑变成通途的桥,如下图所示,它展示了Android平台上JNI所处的位置。# R0 v* o! p/ j. u( [
% |# m3 e3 K. n. u+ S2 L6 x

8 u" d! p& g& H, L, d! o4 j$ W3 f8 ^% F& p$ p
二.SWIG概述: o9 a5 z2 V4 z+ A
SWIG是一个软件开发工具,它将用C和C ++编写的程序与各种高级编程语言连接起来。SWIG与不同类型的目标语言一起使用。SWIG可以作为测试和原型化C / C ++软件的工具。SWIG通常用于解析C / C ++接口并生成各种语言调用C / C ++代码所需的“粘合代码”。转化代码时是非常方便实用的,而且用起来也不是太复杂,最主要的是写一次代码可以为多种语言调用。工具是开源的,学习手册请查看 http://www.swig.org/$ h  P" F+ b7 [* g4 N+ m3 X1 w2 }9 A
三.示例5 [) z0 o5 ]1 N# r/ r
俗话说,百闻不如一见,我们先实现一个JNI调用的例子,然后再解析其原理。以下操作是在ubuntu环境下进行的。
: }$ ?. u; a4 J0 k# _  a9 v: v* o1 G1.搭建编译环境
! \& ?  ?. u! _" s: i% A6 ^Android studio,android 原生开发工具包(NDK),CMake,LLDB 调试工具。可以使用SDK 管理器安装这些组件:2 X2 ~6 m# V) E# i6 S3 Q2 C% v
1.1在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。# K6 s) q9 G+ X
1.2点击SDK Tools 标签。6 z2 i8 M' A2 k3 i% M# F6 z
1.3选中LLDB、CMake 和NDK 旁的复选框。2 e. G- j( C4 U7 `
1.4点击Apply,然后在弹出式对话框中点击OK。/ m) r. E8 I0 j8 K+ ]0 ^
1.5安装完成后,点击Finish,然后点击OK。" ~5 u8 f6 z+ ~1 d
2.编写C代码
6 g6 X' Q1 W+ }3 y7 Q: ^1 Z7 U- a9 j* G# g, N) i1 N6 E- |2 s
3.编写SWIG的转化文件- L7 l# L. j9 |  x1 K( {

9 Y$ f% d) Z% S  k8 Z( g4.生成JNI调用所需的文件8 _8 [, E# M) e6 O! V1 T( ^
使用命名swig -java -package com.example.jni add.i,会生成三个文件
$ b( K2 T  E# Z$ a- M* G" J( jadd_wrap.c:用于和java 对接的JNI函数
0 K$ R# C+ S; G: zaddJNI.java:用于和JNI 对接的java 本地接口" \. i0 A& P1 _) S  z# k
add.java:java 本地调用的接口,他调用addJNI.java 中的内容
/ I, H6 I( ~1 L, o  J2 Ycat add_wrap.c(因为生成的文件比较大,所以需要在), s. `/ K) _; @! Y- V" J1 @+ i
' q! w( q( ?' s
cat addJNI.java
2 \% W. g# P0 j7 C( {" i# y9 A9 N' `9 ^0 E! J+ K# `
cat add.java
# p/ i. ^2 q( C7 m+ J7 h" X% C# |! A  [* r8 x
5.生成C库
. S0 B! G7 S6 s7 n: J+ S. l, r* Q' I注意两点:0 ?1 b2 z2 a' j, P( P. \0 v# l/ f
5.1 将刚生成都add_wrap.c文件添加到库,如果没有源码可以将add_wrap.c编译为独立的动态库文件! H! }7 v" M7 E
5.2 添加jni.h文件(安装NDK后就会存在)到可查找的头文件目录中,add_wrap.c使用了jni.h文件$ f0 n$ s5 X# i( Y
cat CMakeLists.txt
% h% c5 P8 g6 W, y- Q* j; Q# D  M; J9 q' m
执行脚本:$ ~$ v4 Q6 p6 Z  {1 N( D

# k+ {- v5 @) j3 C生成的目录如下- o- O2 D  k6 ]6 m7 |: j
build
  x9 {5 S7 B: K6 k( @0 t1 w- ^├── arm64-v8a: B: ?* w7 Z# t
│ └── lib  L* S3 p  k6 R3 x$ M
│ └── libadd.so
, c" T1 X- P2 m' ?6 b├── armeabi-v7a
8 A* Z9 B0 ?. Y& ]/ ^& `. V: e│ └── lib7 W$ o* b. a1 k- s
│ └── libadd.so
+ p0 V5 g' c8 S0 x# R├── x865 b3 E& o8 M: f
│ └── lib% \' j) f# G. x
│ └── libadd.so3 S" a2 d; ^/ Z$ _( C1 D
└── x86_643 u/ O8 [9 W: o+ g4 Q6 x2 |$ G
└── lib
! U% L( K$ N" r/ J6 ?└── libadd.so
" }# b% j. L' M  b9 Y3 n3 j将so文件移到lib同级目录下,删除lib文件(和Android Studio配置一致)
5 {3 c& i! b9 _( F- V6.Android studio测试运行
0 _- k+ d1 X1 B- Y0 R9 X6.1 Android studio创建新的项目,在向导的Configure your new project部分,选中Include C++ Support 复选框。; ~5 u( |7 ?/ D- ?
6.2 将生成的so 文件复制到app\src\main\jniLibs 目录下(四种库和文件夹一起复制)
3 w1 D# G- d' l6.3 将SWIG生成的add.java 和addJNI.java 文件复制 app\src\main\java\com\example\jni 目录下(与导出时package 参数一致), y- R! \: o; s) H# S" a2 a
6.4 测试程序如下,测试成功3 a3 G( l5 ?& R. E9 u, T" z6 w1 N

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

本版积分规则

成为第一个吐槽的人

朋友一起走 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    16