Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
一.JNI概述
% w/ q) j9 f) |! `8 m$ sJNI即java native interface,通俗的说,JNI是一种技术,通过这种技术可以做到以下两点:6 P# p8 R, p7 u! j5 R
1.java程序中的函数可以调用C/C++ 编写的函数* E1 ^' r' I, B# Z# r) @7 w2 f
2.C/C++ 程序可以通过它调用java层的函数2 y8 r! B: J: |
JNI主要是完成java和C/C++ 代码和交互,但是java为什么要调用C/C++ 程序哪,这是不是破坏了java的平台无关特性,其实java需要调用C/C++ 程序需要是出于以下几点:
- g0 Q0 z' Z+ n( x5 G- y1 S1.早在java语言诞生前,很多程序都是由C/C++ 语言编写的,他们遍布在软件世界的各个角落。虽然java出世后备受追捧,但是并不能完全替代C/C++ ,很多功能还需要调用它们,重复制造轮子会影响用户的体验,而JNI技术则解决了这个问题。8 Y2 U; S* @$ W; s1 w9 C- b0 W
2.C/C++ 程序的运行效率是高于java的(虽然后期推出了JIT技术),在很多在运行速度上要求比较高、调用比较频繁的场合就可能选用C/C++ 语言。
" x. Q0 D3 u6 E0 }2 H3.有时需要和平台代码通信而又没有对应的Java API的时候,也需要使用C/C++ 代码编写好相应的代码供Java层使用。
' \. H* P" R. ?. y在Android平台上,JNI就是一座将Java世界和Native世界的天堑变成通途的桥,如下图所示,它展示了Android平台上JNI所处的位置。
' v& Z2 n) ~1 i2 \6 R
) }+ O, r# s9 e# i2 }9 r2 ]
+ ]8 S  o9 X" Q1 U$ p6 m! `& U
二.SWIG概述2 p( X: [) w) A5 M4 @2 I
SWIG是一个软件开发工具,它将用C和C ++编写的程序与各种高级编程语言连接起来。SWIG与不同类型的目标语言一起使用。SWIG可以作为测试和原型化C / C ++软件的工具。SWIG通常用于解析C / C ++接口并生成各种语言调用C / C ++代码所需的“粘合代码”。转化代码时是非常方便实用的,而且用起来也不是太复杂,最主要的是写一次代码可以为多种语言调用。工具是开源的,学习手册请查看 http://www.swig.org/
2 m" M0 G: l/ h7 m% A三.示例
. E6 P- v1 t( Z. V俗话说,百闻不如一见,我们先实现一个JNI调用的例子,然后再解析其原理。以下操作是在ubuntu环境下进行的。- w$ d! \3 V1 i' E! H' Q. Q! U& t
1.搭建编译环境8 m; o9 F' W% k5 A
Android studio,android 原生开发工具包(NDK),CMake,LLDB 调试工具。可以使用SDK 管理器安装这些组件:% e+ @+ U; [" H* T6 a  a
1.1在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。
5 A* p. b2 @8 P# ^* |1.2点击SDK Tools 标签。  l& C9 x0 b2 d4 j1 q* o  c
1.3选中LLDB、CMake 和NDK 旁的复选框。! o( E9 @& }- k9 s
1.4点击Apply,然后在弹出式对话框中点击OK。
1 j% x, L( r9 l  P! j% c6 T1.5安装完成后,点击Finish,然后点击OK。3 P, y7 V, @2 O. E$ }0 a9 K
2.编写C代码6 f' w0 c( k# F6 I# l; t

) Z! `: i: q5 v' H; D* V0 @& b! n9 X3.编写SWIG的转化文件
' \6 _3 W' q, }) _2 V/ h+ N" P3 r4 c0 t% d5 V6 x
4.生成JNI调用所需的文件; W& c5 y$ }' M2 s. \
使用命名swig -java -package com.example.jni add.i,会生成三个文件
! z; a/ n" e/ `! k( cadd_wrap.c:用于和java 对接的JNI函数4 Y  e  d: z1 h" L# A
addJNI.java:用于和JNI 对接的java 本地接口
% F5 Q3 \. a9 j1 O6 Padd.java:java 本地调用的接口,他调用addJNI.java 中的内容
) u6 B6 Z0 N/ `3 Tcat add_wrap.c(因为生成的文件比较大,所以需要在)
" M3 j1 I( g" O7 B
" y1 L4 l2 G4 E# L4 vcat addJNI.java
1 N1 `# v7 [$ H1 e+ h7 V- [: `; d5 N
cat add.java) H( x/ j% i: P4 u

# w2 Q2 S% g$ x! g' i: z+ x" N5.生成C库
6 o- ^; g$ C# O  p- w7 e注意两点:
: I) I& A9 ]; [) y5.1 将刚生成都add_wrap.c文件添加到库,如果没有源码可以将add_wrap.c编译为独立的动态库文件' e8 |% v2 t2 a8 Z4 i' z4 O4 l( X
5.2 添加jni.h文件(安装NDK后就会存在)到可查找的头文件目录中,add_wrap.c使用了jni.h文件* D1 \  }" c$ c' V
cat CMakeLists.txt3 ]& e& W4 a1 P: M% J+ \( I, q
" w+ f# H# d$ x4 S* g
执行脚本:" E, m8 N  Z: f0 \" Q3 h7 H. O
, d. C: R7 z0 I# Q. D: @
生成的目录如下
2 R# a$ L& z7 P3 ^- z# xbuild
7 w1 p! H; a( a5 \' W2 F* L! [├── arm64-v8a
1 \1 D7 k7 F) e' G# H2 [│ └── lib
3 O: L' |' H) \$ u/ o9 T│ └── libadd.so
  }+ n, {. a# E/ W2 _5 x0 C5 b├── armeabi-v7a' y' q6 B) ], p, n  N4 u
│ └── lib1 X! V" i& Z+ R  @
│ └── libadd.so
2 C1 @7 i' a, {9 ^├── x867 y" P  s0 ~6 Q' j. d" z
│ └── lib
$ ?8 P4 Z" i6 n) o9 A│ └── libadd.so
- Z- z9 Y2 @1 ^  J( d└── x86_64
8 C- g' Y, {8 H* T- W: Q; p4 _└── lib
4 a" L5 D4 U; t, G+ ]' P8 K└── libadd.so
0 I9 Z0 B% K) Q% [* j9 ]将so文件移到lib同级目录下,删除lib文件(和Android Studio配置一致)
, y5 H6 c2 e- L0 K& v6.Android studio测试运行2 {6 N4 K8 b  T, K1 L
6.1 Android studio创建新的项目,在向导的Configure your new project部分,选中Include C++ Support 复选框。7 c0 O8 L% r9 _1 ]2 j. q
6.2 将生成的so 文件复制到app\src\main\jniLibs 目录下(四种库和文件夹一起复制)
8 u* Y; C' X& R" d1 c. r6.3 将SWIG生成的add.java 和addJNI.java 文件复制 app\src\main\java\com\example\jni 目录下(与导出时package 参数一致)
' k. d9 F" ], O6.4 测试程序如下,测试成功
1 I3 ?: S) @0 z+ k( ~/ |3 y3 ?; t1 o9 a* {. @/ D
四.加载过程
' Z0 {0 l, d. H4 w大家都知道,如果我们希望使用C/C++函数,将其加载到内存是基础,那到底什么时候加载,怎么加载的哪?
1 W' [0 \% c; M- k1 E其实只要在调用前,可以在任何时候加载本地库,但是我们一般的做法是在java类static语句中加载,加载的方式是通过System.loadLibrary方法。需要注意的是System.loadLibrary的参数是动态库的名字,如上示例,在linux下为libadd.so,而在window下为add.dll。其实JNI对java程序员还是比较照顾的,他们只需要加载库文件,其他的都不需要做。native修饰的函数也已经被JNI自动生成,如果在*JNI.java中添加加载库的static函数,那么java程序员连加载库都免了,是不是感觉很方便。; L# U7 ?4 W9 ?
五.注册过程
/ h6 O: U- x+ T! A读到这里,相信大家应该还是有一个很大的疑问,C库我已经将其加载,但是它又是怎么和java函数联系起来的哪,为什么我调用java函数而它会自动调用C库的函数,他们之间是怎么联系起来的哪?' b  t6 u, f( }9 r; X& e  ]2 m
正常的java方法是在加载的过程中会被放到方法区,而native方法则没有被定义。这就必须存在一个注册的过程,即将C库中的函数(上例中add_wrap.c中的函数)与Java中Native方法(上例中addJIN.java中方法)建立对应。注册过程分为两种:: P+ L" U- ~4 ?$ w6 T( L- N6 C
1.静态注册
4 t* T& c: [4 U! V当java层调用add方法时,如上例所示,他会从C库中查找Java_com_example_jni_addJNI_add方法,如果没有则报错,如果找到就将两个函数建立一个关联,以后调用add方法时就会直接调用Java_com_example_jni_addJNI_add函数。从这里可以看出,静态方法是通过函数名来建立java函数和C函数之间的对应的。所以它要求对应的C函数(add_wrap.c)必须遵循特定的格式。上例就是通过静态的方式进行注册。
# e) D) J$ P0 |- r9 k3 ]! m( T静态注册是有缺点的:/ l& O) A5 u% \" M; c
(1)需要编译所有生命了native方法的java类,每个class文件都需要对应一个**JNI.java的文件。# l5 B- X- y1 w8 t' k/ t7 ~: J
(2)生成的JNI层函数名特别长,书写起来不方便
0 H: I! }* p7 q+ b, ^(3)初次调用native函数时需要根据函数名搜索对应的JNI层函数来建立连接,影响运行效率6 a& c" `8 B, V9 Y5 L4 @
2.动态注册3 s; W# c5 d, G$ C: N
从静态注册中可以发现java函数和JNI函数是有一个数据结构来保存关联的。这个数据结构大致如下:5 a! }6 g& b5 O9 P* W: c

- U& Z4 D  c$ Q0 P9 `有一个指向这种数据结构的二级指针来保存所有的本地方法。所以我们就可以考虑在类加载的时候直接将本地方法和JNI函数建立连接,这种方式就是动态注册。$ ]- M- X  l" k) b' {) D/ t
而在java层我们只用了System.loadLibray方法,所以因该在这个方法中进行动态注册。顺藤摸瓜我们可以找到JNI_Onload函数,在执行System.loadLibrary方法时。
4 |9 i& w; n3 U; q: j/ RJNI_Onload函数主要操作有两部分:
$ x. h7 P, x, B; Y0 A1.通过JNIEnv的FindClass函数找到java层注册JNI的方法2 J0 W% q4 S  Z) Q6 b
2.调用该函数
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

朋友一起走 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    16