Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
一.JNI概述
2 Q! E0 d$ R/ t1 h) l8 z; e4 @JNI即java native interface,通俗的说,JNI是一种技术,通过这种技术可以做到以下两点:$ O9 x  P( [! a! X( ?
1.java程序中的函数可以调用C/C++ 编写的函数
0 V! y6 c) i* Z2.C/C++ 程序可以通过它调用java层的函数9 B0 D. `  |9 _% j1 ?- H
JNI主要是完成java和C/C++ 代码和交互,但是java为什么要调用C/C++ 程序哪,这是不是破坏了java的平台无关特性,其实java需要调用C/C++ 程序需要是出于以下几点:
+ d. o4 k( K; W" H# ^5 [4 i6 g1.早在java语言诞生前,很多程序都是由C/C++ 语言编写的,他们遍布在软件世界的各个角落。虽然java出世后备受追捧,但是并不能完全替代C/C++ ,很多功能还需要调用它们,重复制造轮子会影响用户的体验,而JNI技术则解决了这个问题。! l. `& Q4 C0 I% P
2.C/C++ 程序的运行效率是高于java的(虽然后期推出了JIT技术),在很多在运行速度上要求比较高、调用比较频繁的场合就可能选用C/C++ 语言。, E/ v* O$ \% x
3.有时需要和平台代码通信而又没有对应的Java API的时候,也需要使用C/C++ 代码编写好相应的代码供Java层使用。
% E  c0 Y9 o- H在Android平台上,JNI就是一座将Java世界和Native世界的天堑变成通途的桥,如下图所示,它展示了Android平台上JNI所处的位置。7 b8 z! @/ i5 A3 v: S: q0 j
5 f, C, e" j, b" l2 ?
6 \: Q1 y- }; J, e, S2 m/ C

$ q2 Y- u5 v; S5 x' \二.SWIG概述
* b+ L  Y  N% L; ]- P4 v; w# k/ XSWIG是一个软件开发工具,它将用C和C ++编写的程序与各种高级编程语言连接起来。SWIG与不同类型的目标语言一起使用。SWIG可以作为测试和原型化C / C ++软件的工具。SWIG通常用于解析C / C ++接口并生成各种语言调用C / C ++代码所需的“粘合代码”。转化代码时是非常方便实用的,而且用起来也不是太复杂,最主要的是写一次代码可以为多种语言调用。工具是开源的,学习手册请查看 http://www.swig.org/
* q/ ^6 s9 Y; r% K$ l" D三.示例3 {8 [: w$ O! k: _6 f7 a
俗话说,百闻不如一见,我们先实现一个JNI调用的例子,然后再解析其原理。以下操作是在ubuntu环境下进行的。
* g9 o0 N  ^+ j3 |! B1.搭建编译环境& p, \; O2 N1 ?, F8 o& K
Android studio,android 原生开发工具包(NDK),CMake,LLDB 调试工具。可以使用SDK 管理器安装这些组件:
9 h! k: I# m- Z- T% @, Z( p1.1在打开的项目中,从菜单栏选择Tools > Android > SDK Manager。% v8 f8 o0 u$ A$ H% p/ \, o, `
1.2点击SDK Tools 标签。
* z* d% {- C9 d5 e$ U1.3选中LLDB、CMake 和NDK 旁的复选框。
9 a/ S0 a& Q4 H& p0 i" G" ^" ~' m1.4点击Apply,然后在弹出式对话框中点击OK。( ^5 s  t& p5 u+ a. t' T
1.5安装完成后,点击Finish,然后点击OK。
: t0 D5 b( e' i2.编写C代码, q3 g1 L! J  F3 U2 p$ C; @

2 \, E: d# _5 S. |3.编写SWIG的转化文件" [7 \8 U+ R9 [" i* l6 Z. ]: |/ y. t
4 n& N5 U  ^! `+ X6 R7 [" @1 {5 e& F* H1 ]
4.生成JNI调用所需的文件6 f3 J) i; E* t( @# B6 m5 z6 E, |2 h& Q
使用命名swig -java -package com.example.jni add.i,会生成三个文件! H; E( w, H3 h+ Y
add_wrap.c:用于和java 对接的JNI函数
: [* }2 I  E; e# |& XaddJNI.java:用于和JNI 对接的java 本地接口
2 z$ b9 L+ z* @% [9 K) B0 H- kadd.java:java 本地调用的接口,他调用addJNI.java 中的内容, _4 Q$ Y) ^$ |6 F! R% Y
cat add_wrap.c(因为生成的文件比较大,所以需要在)5 D( p1 L/ k' a# y! N" \
$ d: `+ I& |  G# Y. T- t
cat addJNI.java( e0 F: @7 k0 f4 A- u  c
5 e0 M8 T3 z+ {4 R* r
cat add.java
& m! ~% l6 i$ y2 T" [" D
$ u+ u' y3 E9 e0 M5.生成C库! T# K+ ~6 q# M, q; g7 {
注意两点:
0 o  h7 e4 N( O7 B# J& g  U  `5.1 将刚生成都add_wrap.c文件添加到库,如果没有源码可以将add_wrap.c编译为独立的动态库文件
9 }9 c4 D. z8 h5.2 添加jni.h文件(安装NDK后就会存在)到可查找的头文件目录中,add_wrap.c使用了jni.h文件6 L7 i- r! }/ c, ?& t: P: w2 P
cat CMakeLists.txt
- X" V7 k' x* i- G$ H7 ]
2 V9 d, B9 I# O执行脚本:( s: @9 ]; d  @5 f5 c' U
$ N# y1 I' z' ?* F; Q) G1 }
生成的目录如下7 I! ?6 S# H5 A+ K' D5 |
build: n$ A% A5 |6 ?" m
├── arm64-v8a/ w, R: y! X" n7 v$ L9 r/ t" G
│ └── lib
# A, w3 G" N: X) j# [│ └── libadd.so
3 D% g5 A7 \' ]+ J5 I├── armeabi-v7a
" i6 s4 @# m, Y# I. R5 j│ └── lib* B; [/ y& b/ g2 X2 X7 G
│ └── libadd.so* D$ r; |4 r2 n. r
├── x86
; h9 F$ Z" z# s! P# w5 J│ └── lib
' f4 v1 y) N& L4 ^8 U) J6 l6 D" A│ └── libadd.so
6 H' K9 [  h/ d1 s: \, k  m└── x86_64( H' J- o* Z7 F' _6 ]' U
└── lib' a% h8 `/ C4 q9 x- D
└── libadd.so
$ `+ k0 V7 z, f; A( F# j将so文件移到lib同级目录下,删除lib文件(和Android Studio配置一致)
( p/ j* U. H# m( ]3 c: R3 _6.Android studio测试运行0 \/ X4 C: K) ?) d
6.1 Android studio创建新的项目,在向导的Configure your new project部分,选中Include C++ Support 复选框。
) _. b, x. w( I5 z" O4 ~$ x6.2 将生成的so 文件复制到app\src\main\jniLibs 目录下(四种库和文件夹一起复制)
/ x  E' [4 V9 s4 }6.3 将SWIG生成的add.java 和addJNI.java 文件复制 app\src\main\java\com\example\jni 目录下(与导出时package 参数一致)9 W* Y2 J' L7 x/ [! \0 j& n' Q
6.4 测试程序如下,测试成功" k/ x8 z  I7 Z4 [6 Z+ N9 P' `! w9 |" Q

" ?$ o9 w* b0 r( o* }: W四.加载过程
& x8 E% E+ M* X0 ^5 Z" t5 c大家都知道,如果我们希望使用C/C++函数,将其加载到内存是基础,那到底什么时候加载,怎么加载的哪?. L7 J) E5 \8 l4 G8 O
其实只要在调用前,可以在任何时候加载本地库,但是我们一般的做法是在java类static语句中加载,加载的方式是通过System.loadLibrary方法。需要注意的是System.loadLibrary的参数是动态库的名字,如上示例,在linux下为libadd.so,而在window下为add.dll。其实JNI对java程序员还是比较照顾的,他们只需要加载库文件,其他的都不需要做。native修饰的函数也已经被JNI自动生成,如果在*JNI.java中添加加载库的static函数,那么java程序员连加载库都免了,是不是感觉很方便。! T2 j9 K) r3 \3 F
五.注册过程
6 p! w1 D+ P% G$ F, |* ?' K读到这里,相信大家应该还是有一个很大的疑问,C库我已经将其加载,但是它又是怎么和java函数联系起来的哪,为什么我调用java函数而它会自动调用C库的函数,他们之间是怎么联系起来的哪?
! C) Y( e3 @9 @( ]# r+ s; C1 q正常的java方法是在加载的过程中会被放到方法区,而native方法则没有被定义。这就必须存在一个注册的过程,即将C库中的函数(上例中add_wrap.c中的函数)与Java中Native方法(上例中addJIN.java中方法)建立对应。注册过程分为两种:
; I+ d; \" W* _7 o- ^. k7 M1.静态注册* l$ H- I8 d) [/ F* S& M: J
当java层调用add方法时,如上例所示,他会从C库中查找Java_com_example_jni_addJNI_add方法,如果没有则报错,如果找到就将两个函数建立一个关联,以后调用add方法时就会直接调用Java_com_example_jni_addJNI_add函数。从这里可以看出,静态方法是通过函数名来建立java函数和C函数之间的对应的。所以它要求对应的C函数(add_wrap.c)必须遵循特定的格式。上例就是通过静态的方式进行注册。
( u8 X* ?0 E/ n( \9 G: w+ P( V, x静态注册是有缺点的:
) O' }* b7 }/ P(1)需要编译所有生命了native方法的java类,每个class文件都需要对应一个**JNI.java的文件。) f) n6 f2 p0 X1 k! I0 |
(2)生成的JNI层函数名特别长,书写起来不方便" Q8 R1 l0 k' z9 h
(3)初次调用native函数时需要根据函数名搜索对应的JNI层函数来建立连接,影响运行效率* G# m$ w) B* g
2.动态注册2 ~" `6 k8 r8 K+ ^$ ]; N  ?* S
从静态注册中可以发现java函数和JNI函数是有一个数据结构来保存关联的。这个数据结构大致如下:
, T' N* z) X7 f  v
- c  ~8 Z# g- a4 C. X: o有一个指向这种数据结构的二级指针来保存所有的本地方法。所以我们就可以考虑在类加载的时候直接将本地方法和JNI函数建立连接,这种方式就是动态注册。% u3 M/ w6 T8 o, S
而在java层我们只用了System.loadLibray方法,所以因该在这个方法中进行动态注册。顺藤摸瓜我们可以找到JNI_Onload函数,在执行System.loadLibrary方法时。1 m7 A3 b3 W+ \, L9 `
JNI_Onload函数主要操作有两部分:
' q8 L+ y7 N! F. K) O1.通过JNIEnv的FindClass函数找到java层注册JNI的方法5 [3 F  a+ A9 P% i
2.调用该函数
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

朋友一起走 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    16