android系统更新中如何将JNI整合到系统中

android中使用jni对字符串加解密实现分析
时间: 00:27:48
&&&& 阅读:342
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&
android中使用jni对字符串加解密实现分析
最近项目有个需求,就是要对用户的敏感信息进行加密处理,比如用户的账户密码,手机号等私密信息。在java中,就对字符串的加解密我们可以使用AES算法加密字符串,使用它的好处就不必多说了,但我们又知道android的源代码是可以被反编译的,所以使用纯Java方式的AES加密是不安全的,所以想到了使用android中的jni来对字符串加解密处理,它会产生一个.so文件,更重要的是它通过C/C++代码实现,所以安全行比较高,它可以被反编译成机器码,但几乎不能被还原反编译,那么下面就详细介绍下这种的加密处理。
鉴于完全使用C/C++代码进行字符串的加解密,我们需要考虑不同系统平台上数据类型的差异问题,这里推荐另一种易于实现的方法,即使用Java中的AES加解密逻辑,而将AES加解密所需要的核心秘钥放入到C中,通过调用jni来从静态类库中读取需要的秘钥,具体实现如下:
项目代码结构图:
Java中的AES算法逻辑:
class SecurityUtil {
& & private
& & private
static SecretKey
key;& & & & & & & & & & & &
& & private
static AlgorithmParameterSpec
paramSpec; & &
& & private
static Cipher
ecipher; & & & & & & & & &
& & static {
& & System.loadLibrary(&cwtlib&);
& & keyValue = getKeyValue();
& & iv = getIv();
& & if(null
!= keyValue &&&
& & null !=
& & KeyGenerator
& & & & & & try { &
& & & & & & & & kgen = KeyGenerator.getInstance(&AES&);
& & & & & & & & kgen.init(128,
new SecureRandom(keyValue)); &
& & & & & & & & key =
kgen.generateKey(); &
& & & & & & & & paramSpec =
new IvParameterSpec(iv); &
& & & & & & & & ecipher = Cipher.getInstance(&AES/CBC/PKCS5Padding&);&
& & & & & & } catch (NoSuchAlgorithmException
& & & & & & } catch (NoSuchPaddingException
& & & & & & } &
& & public
byte[] getKeyValue();
& & public
byte[] getIv();
& & public
static String encode(String
& & & & String str =
& & & & try {& & & & & & &
& & & & & & //用密钥和一组算法参数初始化此 cipher &
& & & & & & ecipher.init(Cipher.ENCRYPT_MODE,
paramSpec); &
& & & & & & //加密并转换成16进制字符串
& & & & & & str = asHex(ecipher.doFinal(msg.getBytes()));
& & & & } catch (BadPaddingException
& & & & } catch (InvalidKeyException
& & & & } catch (InvalidAlgorithmParameterException
& & & & } catch (IllegalBlockSizeException
& & & & } &
& & & & return
& & public
static String decode(String
value) { &
& & & & try { &
& & & & & & ecipher.init(Cipher.DECRYPT_MODE,
paramSpec); &
& & & & & & return
new String(ecipher.doFinal(asBin(value)));
& & & & } catch (BadPaddingException
& & & & } catch (InvalidKeyException
& & & & } catch (InvalidAlgorithmParameterException
& & & & } catch (IllegalBlockSizeException
& & & & } &
& & & & return
& & private
static String asHex(byte
buf[]) { &
& & & & StringBuffer strbuf =
new StringBuffer(buf.length
& & & & int
& & & & for (i = 0;
buf.length;
i++) { &
& & & & & & if (((int)
buf[i] & 0xff) & 0x10)//小于十前面补零
& & & & & & & & strbuf.append(&0&);
& & & & & & strbuf.append(Long.toString((int)
buf[i] & 0xff, 16)); &
& & & & } &
& & & & return
strbuf.toString(); &
& & private
byte[] asBin(String
& & & & if (src.length()
& & & & & & return
& & & & byte[]
encrypted =
byte[src.length() / 2]; &
& & & & for (int
src.length() / 2;
i++) { &
& & & & & & int
high = Integer.parseInt(src.substring(i
* 2, i * 2 + 1), 16);//取高位字节
& & & & & & int
low = Integer.parseInt(src.substring(i
* 2 + 1, i * 2 + 2), 16);//取低位字节
& & & & & & encrypted[i]
= (byte) (high * 16 +
& & & & } &
& & & & return
encrypted; &
C中的读取秘钥的实现:
&cwtlib.h&
char keyValue[] = {
21, 25, 21, -45, 25, 98, -55, -45, 10, 35, -45, 35,
& & 26, -5, 25, -65, -78, -99, 85, 45, -5, 10, -0, 11,
& & -35, -48, -98, 65, -32, 14, -67, 25, 36, -56, -45, -5,
& & 12, 15, 35, -15, 25, -14, 62, -25, 33, -45, 55, 12, -8
char iv[] =& {
-33, 32, -25, 25, 35, -27, 55, -12, -15, 23, 45, -26, 32, 5 - 2, 74, 54
JNIEXPORT jbyteArray JNICALL Java_com_cwtlib_aesencript_SecurityUtil_getKeyValue
& (JNIEnv *env, jclass obj)
& & jbyteArray kvArray = (*env)-&NewByteArray(env,
sizeof(keyValue));
jbyte *bytes = (*env)-&GetByteArrayElements(env,kvArray,0);
for (i = 0; i &
sizeof(keyValue);i++)
bytes[i] = (jbyte)keyValue[i];
(*env)-&SetByteArrayRegion(env,kvArray, 0,
sizeof(keyValue),bytes);
(*env)-&ReleaseByteArrayElements(env,kvArray,bytes,0);
return kvA
JNIEXPORT jbyteArray JNICALL Java_com_cwtlib_aesencript_SecurityUtil_getIv
& (JNIEnv *env, jclass obj)
& & jbyteArray ivArray = (*env)-&NewByteArray(env,
sizeof(iv));
jbyte *bytes = (*env)-&GetByteArrayElements(env,ivArray, 0);
for (i = 0; i &
sizeof(iv); i++)
bytes[i] = (jbyte)iv[i];
(*env)-&SetByteArrayRegion(env,ivArray, 0,
sizeof(iv), bytes);
(*env)-&ReleaseByteArrayElements(env,ivArray,bytes,0);
return ivA
在android中如何调用:
class MainActivity
extends Activity {
final String
&MainActivity&;
private String
encriptStr =
// 加密的字符串
void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
enstr = SecurityUtil.encode(encriptStr);
Log.d(TAG,
&加密后:& +
destr = SecurityUtil.decode(enstr);
Log.d(TAG,
&解密后:& +
这里以一个手机号为例进行加解密处理,具体的效果图可以在日志中查看到,具体如下。
加解密的对照效果图:
好了,到这里我已经罗列出了所有主要文件实现如果有问题可以在评论或是群()中进行讨论。另外,原创作品来自不易,转载请注明出处,谢谢。
标签:&&&&&&&&&&&&原文:http://blog.csdn.net/why_2012_gogo/article/details/
教程昨日排行
&&国之画&&&& &&&&&&
&& &&&&&&&&&&&&&&
鲁ICP备号-4
打开技术之扣,分享程序人生!图文详解android中JNI开发步骤总结作者:课课家教育&http://www.kokojia.com点击数:10331发布时间: 11:06:35  &&JNI是 Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。
这几天一直在研究JNI的开发过程,顺便把NDK环境搭建一起总结下。在环境下开发jni需要c/c++编译器的支持,网络上我看很多人使用cygwin。呵呵我不是很喜欢使用它,感觉安装起来挺麻烦的。我使用GNUStep。
& & & 下载gnustep
  下载安装后,验证是否成功。打开GNUstep-&Shell,输入make -v 和 gcc -v命令,如图所示。
  下载NDK,下载完后解压即可。
  配置ndk环境变量,gnustep是模拟的环境的,打开gnustep的安装目录下的G:\\softinstall\\GNUstep\\GNUstep\\GNUstep.conf文件,添加以下内容:
  说明如果不知道ndk目录在linux下应该是在哪里,你可以打开gnustep的命令窗口,输入mount,就可以找到对应的盘符。
  验证环境变量,如下图。
  以上就配置成功了。
  下载进入正题啦。
& & &Jni的开发步骤
  打开eclipse,新建工程名为testJni。在activity中添加以下代码
  复制内容到剪贴板代码:
  编译后的文件在bin目录下,通过javah命令生成c/c++的文件头。如下图
  会在项目目录下生成jni/com_xzw_jni_TestJni.h。
  头文件代码如下:
   这里我们可以根据头文件编写c代码
  接下来编写 .mk,该文件可以直接从NDK的samples下的hello-jni的jni文件下直接靠过来改改就可以了。也贴下代码哈。
  其中你只需要该LOCAL_MODULE和LOCAL_SRC_FILES就可以了。
  说明:LOCAL_MODULE是描述模块的,用来给java调用的模块名,会生成对应的libtestJni.so
  LOCAL_SRC_FILES就是源文件啦,多个文件空格隔开即可。
  接下来,我们要开始编译生成so文件咯。
  打开gnustep的命令窗口,进入到项目底下,输入$NDK/ndk-build命令,即可自动生成libs/armeabi/libtestJni.so文件。
  接下来就是java调用了。直接上代码
  运行结果如下
  以上就是jni的开发步骤了。
  在这里我还要补充下:
  在我们开发过程中,改一个c/c++的文件,我们都要手动去编译一下有点儿麻烦。这里我们可以使用让eclipse帮助我们自己编译。
  右击jni工程的properties--&Builders--&NEW --&;Program 可以看到以下内容:
  argument:--login -c "cd /e/myWorkSpace/android/hellJni && $NDK/ndk-build"
  切换到Refresh 标签页
  切换到Build Options标签页
  这样就完成了配置,点击确定可看到控制台自动编译程序了
& JNI开发需要 下载gnustep和配置ndk环境变量,& 本文主要是介绍android中JNI开发步骤,JNI开发& &步骤详细请查看上文中图文详解,本篇文章还简单补充了一个修改的内容,修改一个c/c++的文件的图文详解内容。标签:赞(27)踩(3)分享到:上一篇:下一篇:最新教程热门教程评论()您需要登录后才可以评论请[][]最新评论暂无评论~移动开发为你推荐推荐查看热门资讯热门图书温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
本人从事消费性电子产品软件开发工作.工作之余喜欢读哲学/军事/科学技术方面的书,喜欢独立思索.本着借鉴西方巨哲的思想,结合工作实践的点滴体会,赢得未来的愿望,借助网络良好的推广平台,进行思想交流,希望能给人一些启示.
LOFTER精选
网易考拉推荐
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
首先要看这个SO是不是JNI规范的SO,比如有没有返回JNI不直接支持的类型。也就是说这个SO是不是可以直接当作JNI来调用。如果答案是否定的,你只能选第二个方案。如果答案是肯定的,还要看你是不是希望这个SO的库直接暴露给JAVA层,如果答案是否定的,你只能选第二个方案,比如你本身也是一个库的提供者。一般如果你只有SO,就说明这个是别人提供给你的,你可以要求对方给你提供配套的JAVA调用文件。1、这个要看这个SO是不是符合JNI调用的规范。还要看你自己的意愿。2、因为第二种方法最灵活,各种情况都可以实现。3、可以看能不能直接从JAVA调用的最简单的方法就是看SO里的函数名是不是Java_XXX_XXX_XXX格式的是就可以,你可以自己写一个配套的JAVA文件,注意一下SO函数名和JAVA函数名的转换规则,或者向SO提供方索要;不是的话就选第二种方案吧。1、检查所需文件是否齐全使用第三方动态库,应该至少有2个文件,一个是动态库(.so),另一个是包含动态库API声明的头文件(.h)2、封装原动态库原动态库文件不包含jni接口需要的信息,所以我们需要对其进行封装,所以我们的需求是:将libadd.so 里面的API封装成带jni接口的动态3、编写库的封装函数libaddjni.c根据前面生成的com_android_libjni_LibJavaHeader.h 文件,编写libaddjni.c,用来生成libaddjni.so&Android中可能会用到第三方的软件包,这包括Java包.jar和Native包.so。jar包既可通过Eclipse开发环境集成,也可通过编译源码集成,看你的工作环境。&假定自己开发的程序为MyMaps,需要用到BaiduMaps的库,包括baidumapapi.jar和libBMapApiEngine_v1_3_1.so。&一、Eclipse中集成第三方jar包及.so动态库&MyMaps工程下创建目录libs以及libs/armeabi,把baidumapapi.jar放在的libs/目录下,把libBMapApiEngine_v1_3_1.so放在libs/armeabi/下。&Eclipse中把第三方jar包baidumapapi.jar打包到MyMaps的步骤:&1.&&&&&&右击工程,选择Properties;2.&&&&&&Java Build Path,选择Libraries;3.&&&&&&Libraries页面点击右面按钮“Add Library…”;4.&&&&&&选择“User Library”,点击“Next”;5.&&&&&&点击“User Libraries”按钮;6.&&&&&&在弹出界面中,点击“New…”;7.&&&&&&输入“User library name”,点击“OK”确认;8.&&&&&&返回之后,选择刚刚创建的User library,右面点击“AddJARs”;9.&&&&&&选择MyMaps/libs/下的baidumapapi.jar;10.&&确认,返回。&这样,编译之后,该jar包就会被打进MyMaps.apk中,libBMapApiEngine_v1_3_1.so也被打包在lib/armeabi/中。程序运行过程中,libBMapApiEngine_v1_3_1.so被放在/data/data/&yourAppPackage&/lib/下,加载动态库时系统会从程序的该lib/目录下查找.so库。&二、源码中集成第三方集成jar包及.so动态库&Android源码中MyMaps放在packages/apps下。MyMaps下创建目录libs以及libs/armeabi,并把baidumapapi.jar放在libs/,把libBMapApiEngine_v1_3_1.so放在libs/armeabi。&2.1 修改Android.mk文件&Android.mk文件如下:[plain]&&1 集成jar包LOCAL_STATIC_JAVA_LIBRARIES取jar库的别名,可以任意取值;LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES指定prebuiltjar库的规则,格式:别名:jar文件路径。注意:别名一定要与LOCAL_STATIC_JAVA_LIBRARIES里所取的别名一致,且不含.jar;jar文件路径一定要是真实的存放第三方jar包的路径。编译用BUILD_MULTI_PREBUILT。2 集成.so动态库LOCAL_PREBUILT_LIBS指定prebuilt so的规则,格式:别名:so文件路径。注意:别名一般不可改变,特别是第三方jar包使用.so库的情况,且不含.so;so文件路径一定要是真实的存放第三方so文件的路径。编译拷贝用BUILD_MULTI_PREBUILT。&2.2 加入到GRANDFATHERED_USER_MODULES&在文件user_tags.mk中,把libBMapApiEngine_v1_3_1加入到GRANDFATHERED_USER_MODULES中[plain]&&user_tags.mk可以是build/core下的,也可以是$(TARGET_DEVICE_DIR)下的,推荐修改$(TARGET_DEVICE_DIR)下的。&2.3 编译结果&MyMaps.apk编译生成在out/target/product/&YourProduct&/system/app/下;libBMapApiEngine_v1_3_1.so放在out/target/product/&YourProduct&/system/lib/下,这也是系统加载动态库时搜索的路径。&
阅读(51267)|
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
在LOFTER的更多文章
loftPermalink:'',
id:'fks_',
blogTitle:'Android如何调用第三方SO库',
blogAbstract:'问题描述:Android如何调用第三方SO库;已知条件:',
blogTag:'',
blogUrl:'blog/static/9',
isPublished:1,
istop:false,
modifyTime:7,
publishTime:4,
permalink:'blog/static/9',
commentCount:5,
mainCommentCount:5,
recommendCount:1,
bsrk:-100,
publisherId:0,
recomBlogHome:false,
currentRecomBlog:false,
attachmentsFileIds:[],
groupInfo:{},
friendstatus:'none',
followstatus:'unFollow',
pubSucc:'',
visitorProvince:'',
visitorCity:'',
visitorNewUser:false,
postAddInfo:{},
mset:'000',
remindgoodnightblog:false,
isBlackVisitor:false,
isShowYodaoAd:false,
hostIntro:'本人从事消费性电子产品软件开发工作.工作之余喜欢读哲学/军事/科学技术方面的书,喜欢独立思索.本着借鉴西方巨哲的思想,结合工作实践的点滴体会,赢得未来的愿望,借助网络良好的推广平台,进行思想交流,希望能给人一些启示.',
hmcon:'0',
selfRecomBlogCount:'0',
lofter_single:''
{list a as x}
{if x.moveFrom=='wap'}
{elseif x.moveFrom=='iphone'}
{elseif x.moveFrom=='android'}
{elseif x.moveFrom=='mobile'}
${a.selfIntro|escape}{if great260}${suplement}{/if}
{list a as x}
推荐过这篇日志的人:
{list a as x}
{if !!b&&b.length>0}
他们还推荐了:
{list b as y}
转载记录:
{list d as x}
{list a as x}
{list a as x}
{list a as x}
{list a as x}
{if x_index>4}{break}{/if}
${fn2(x.publishTime,'yyyy-MM-dd HH:mm:ss')}
{list a as x}
{if !!(blogDetail.preBlogPermalink)}
{if !!(blogDetail.nextBlogPermalink)}
{list a as x}
{if defined('newslist')&&newslist.length>0}
{list newslist as x}
{if x_index>7}{break}{/if}
{list a as x}
{var first_option =}
{list x.voteDetailList as voteToOption}
{if voteToOption==1}
{if first_option==false},{/if}&&“${b[voteToOption_index]}”&&
{if (x.role!="-1") },“我是${c[x.role]}”&&{/if}
&&&&&&&&${fn1(x.voteTime)}
{if x.userName==''}{/if}
网易公司版权所有&&
{list x.l as y}
{if defined('wl')}
{list wl as x}{/list}Android NDK开发:JNI基础篇 - 简书
Android NDK开发:JNI基础篇
1. JNI 概念
JNI 全称 Java Native Interface,Java 本地化接口,可以通过 JNI 调用系统提供的 API。操作系统,无论是 Linux,Windows 还是 Mac OS,或者一些汇编语言写的底层硬件驱动都是 C/C++ 写的。Java和C/C++不同 ,它不会直接编译成平台机器码,而是编译成虚拟机可以运行的Java字节码的.class文件,通过JIT技术即时编译成本地机器码,所以有效率就比不上C/C++代码,JNI技术就解决了这一痛点,JNI 可以说是 C 语言和 Java 语言交流的适配器、中间件,下面我们来看看JNI调用示意图:来自
JNI 调用示意图
JNI技术通过JVM调用到各个平台的API,虽然JNI可以调用C/C++,但是JNI调用还是比C/C++编写的原生应用还是要慢一点,不过对高性能计算来说,这点算不得什么,享受它的便利,也要承担它的弊端。
1.2 JNI 与 NDK 区别
JNI:JNI是一套编程接口,用来实现Java代码与本地的C/C++代码进行交互;
NDK: NDK是Google开发的一套开发和编译工具集,可以生成动态链接库,主要用于Android的JNI开发;
2. JNI 作用
扩展:JNI扩展了JVM能力,驱动开发,例如开发一个wifi驱动,可以将手机设置为无限路由;
高效: 本地代码效率高,游戏渲染,音频视频处理等方面使用JNI调用本地代码,C语言可以灵活操作内存;
复用: 在文件压缩算法 7zip开源代码库,机器视觉 OpenCV开放算法库等方面可以复用C平台上的代码,不必在开发一套完整的Java体系,避免重复发明轮子;
特殊: 产品的核心技术一般也采用JNI开发,不易破解;
JNI在Android中作用:
JNI可以调用本地代码库(即C/C++代码),并通过 Dalvik 虚拟机与应用层和应用框架层进行交互,Android中JNI代码主要位于应用层和应用框架层;
应用层: 该层是由JNI开发,主要使用标准JNI编程模型;
应用框架层: 使用的是Android中自定义的一套JNI编程模型,该自定义的JNI编程模型弥补了标准JNI编程模型的不足;
补充知识点:
Java语言执行流程:
编译字节码:Java编译器编译 .java源文件,获得.class 字节码文件;
装载类库:使用类装载器装载平台上的Java类库,并进行字节码验证;
Java虚拟机:将字节码加入到JVM中,Java解释器和即时编译器同时处理字节码文件,将处理后的结果放入运行时系统;
调用JVM所在平台类库:JVM处理字节码后,转换成相应平台的操作,调用本平台底层类库进行相关处理;
Java 语言执行流程
Java一次编译到处执行: JVM在不同的操作系统都有实现,Java可以一次编译到处运行,字节码文件一旦编译好了,可以放在任何平台的虚拟机上运行;
3. 查看 jni.h 文件源码方法
jni.h 头文件就是为了让 C/C++ 类型和 Java 原始类型相匹配的头文件定义。
可以通过点击 Android项目的含有#include &jni.h&的头文件或 C/C++ 文件跳转到 jni.h 头文件查看;
如果没有这样的文件的话,可以在 Android Studio 上新建一个类,随便写一个 native 方法,然后点击红色的方法,AS 会自动生成一个对应的 C 语言文件jnitest.c,就可以找到 jni.h 文件了
或者,通过 javah 命令javah cn.cfanr.testjni.JniTest,就可以生成对应头文件cn_cfanr_testjni_JniTest.h:
javah 生成的
4. JNI 数据类型映射
由头文件代码可以看到,jni.h有很多类型预编译的定义,并且区分了 C 和 C++的不同环境。
#ifdef HAVE_INTTYPES_H
# include &inttypes.h&
typedef uint8_
/* unsigned 8 bits */
typedef int8_
/* signed 8 bits */
typedef uint16_
/* unsigned 16 bits */
typedef int16_
/* signed 16 bits */
typedef int32_
/* signed 32 bits */
typedef int64_
/* signed 64 bits */
typedef fl
/* 32-bit IEEE 754 */
typedef dou
/* 64-bit IEEE 754 */
typedef unsi
/* unsigned 8 bits */
/* signed 8 bits */
/* unsigned 16 bits */
typedef sh
/* signed 16 bits */
/* signed 32 bits */
/* signed 64 bits */
typedef fl
/* 32-bit IEEE 754 */
typedef dou
/* 64-bit IEEE 754 */
/* "cardinal indices and sizes" */
#ifdef __cplusplus
* Reference types, in C++
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
typedef _jobject*
typedef _jclass*
typedef _jstring*
typedef _jarray*
typedef _jobjectArray*
typedef _jbooleanArray* jbooleanA
#else /* not __cplusplus */
* Reference types, in C.
typedef void*
typedef jo
typedef job
typedef jo
typedef jarray
typedef jarray
当是C++环境时,jobject, jclass, jstring, jarray 等都是继承自_jobject类,而在 C 语言环境是,则它的本质都是空类型指针typedef void*
4.1 基本数据类型
下图是Java基本数据类型和本地类型的映射关系,这些基本数据类型都是可以直接在 Native 层直接使用的:
基本数据类型映射
4.2 引用数据类型
另外,还有引用数据类型和本地类型的映射关系:
引用数据类型映射
需要注意的是,
1)引用类型不能直接在 Native 层使用,需要根据 JNI 函数进行类型的转化后,才能使用;
2)多维数组(含二维数组)都是引用类型,需要使用 jobjectArray 类型存取其值;
例如,二维整型数组就是指向一位数组的数组,其声明使用方式如下:
//获得一维数组的类引用,即jintArray类型
jclass intArrayClass = env-&FindClass("[I");
//构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为length,类型为 jsize
jobjectArray obejctIntArray
env-&NewObjectArray(length ,intArrayClass , NULL);
4.3 方法和变量 ID
同样不能直接在 Native 层使用。当 Native 层需要调用 Java 的某个方法时,需要通过 JNI 函数获取它的 ID,根据 ID 调用 JNI 函数获取该方法;变量的获取也是类似。ID 的结构体如下:
struct _jfieldID;
/* opaque structure */
typedef struct _jfieldID* jfieldID;
/* field IDs */
struct _jmethodID;
/* opaque structure */
typedef struct _jmethodID* jmethodID;
/* method IDs */
5. JNI 描述符
5.1域描述符
1)基本类型描述符
下面是基本的数据类型的描述符,除了 boolean 和 long 类型分别是 Z 和 J 外,其他的描述符对应的都是Java类型名的大写首字母。另外,void 的描述符为 V
基本类型描述符
2)引用类型描述符
一般引用类型描述符的规则如下,注意不要丢掉“;”
L + 类描述符 + ;
如,String 类型的域描述符为:
Ljava/lang/S
数组的域描述符特殊一点,如下,其中有多少级数组就有多少个“[”,数组的类型为类时,则有分号,为基本类型时没有分号
[ + 其类型的域描述符
描述符为 [I
double[] 描述符为 [D
String[] 描述符为 [Ljava/lang/S
Object[] 描述符为 [Ljava/lang/O
描述符为 [[I
double[][] 描述符为 [[D
对应在 jni.h
获取 Java 的字段的 native 函数如下,name为 Java 的字段名字,sig 为域描述符
(*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
(*GetObjectField)(JNIEnv*, jobject, jfieldID);
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
{ return functions-&GetFieldID(this, clazz, name, sig); }
jobject GetObjectField(jobject obj, jfieldID fieldID)
{ return functions-&GetObjectField(this, obj, fieldID); }
具体使用,后面会讲到
5.2 类描述符
类描述符是类的完整名称:包名+类名,java 中包名用 . 分割,jni 中改为用 / 分割
如,Java 中 java.lang.String 类的描述符为 java/lang/String
native 层获取 Java 的类对象,需要通过 FindClass() 函数获取, jni.h 的函数定义如下:
(*FindClass)(JNIEnv*, const char*);
jclass FindClass(const char* name)
{ return functions-&FindClass(this, name); }
字符串参数就是类的引用类型描述符,如 Java 对象 cn.cfanr.jni.JniTest,对应字符串为Lcn/cfanr/jni/JniT 如下:
jclass jclazz = env-&FindClass("Lcn/cfanr/jni/JniT");
详细用法的例子,后面会讲到。
5.3 方法描述符
方法描述符需要将所有参数类型的域描述符按照声明顺序放入括号,然后再加上返回值类型的域描述符,其中没有参数时,不需要括号,如下规则:
(参数……)返回类型
Java 层方法
JNI 函数签名
String getString()
Ljava/lang/S
int sum(int a, int b)
void main(String[] args) ——& ([Ljava/lang/S)V
另外,对应在 jni.h 获取 Java 方法的 native 函数如下,其中 jclass 是获取到的类对象,name 是 Java 对应的方法名字,sig 就是上面说的方法描述符
(*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
{ return functions-&GetMethodID(this, clazz, name, sig); }
不过在实际编程中,如果使用 javah 工具来生成对应的 native 代码,就不需要手动编写对应的类型转换了。
6. JNIEnv 分析
JNIEnv 是 jni.h
文件最重要的部分,它的本质是指向函数表指针的指针(JavaVM也是),函数表里面定义了很多 JNI 函数,同时它也是区分 C 和 C++环境的(由上面介绍描述符时也可以看到),在 C 语言环境中,JNIEnv 是strut JNINativeInterface*的指针别名。
struct _JNIE
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIE
#if defined(__cplusplus)
typedef _JNIEnv JNIE
//C++中的 JNIEnv 类型
typedef _JavaVM JavaVM;
typedef const struct JNINativeInterface* JNIE
//C语言的 JNIEnv 类型
typedef const struct JNIInvokeInterface* JavaVM;
6.1 JNIEnv 特点
JNIEnv 是一个指针,指向一组 JNI 函数,通过这些函数可以实现 Java 层和 JNI 层的交互,就是说通过 JNIEnv 调用 JNI 函数可以访问 Java 虚拟机,操作 Java 对象;
所有本地函数都会接收 JNIEnv 作为第一个参数;(不过 C++ 的JNI 函数已经对 JNIEnv 参数进行了封装,不用写在函数参数上)
用作线程局部存储,不能在线程间共享一个 JNIEnv 变量,也就是说 JNIEnv 只在创建它的线程有效,不能跨线程传递;相同的 Java 线程调用本地方法,所使用的 JNIEnv 是相同的,一个 native 方法不能被不同的 Java 线程调用;
6.2 JavaEnv 和 JavaVM 的关系
1)每个进程只有一个 JavaVM(理论上一个进程可以拥有多个 JavaVM 对象,但 Android 只允许一个),每个线程都会有一个 JNIEnv,大部分 JNIAPI 通过 JNIEnv 调用;也就是说,JNI 全局只有一个 JavaVM,而可能有多个 JNIEnv;
2)一个 JNIEnv 内部包含一个 Pointer,Pointer 指向 Dalvik 的 JavaVM 对象的 Function Table,JNIEnv 内部的函数执行环境来源于 Dalvik 虚拟机;
3)Android 中每当一个Java 线程第一次要调用本地 C/C++
代码时,Dalvik 虚拟机实例会为该 Java 线程产生一个 JNIEnv 指针;
4)Java 每条线程在和 C/C++
互相调用时,JNIEnv 是互相独立,互不干扰的,这样就提升了并发执行时的安全性;
5)当本地的 C/C++ 代码想要获得当前线程所想要使用的 JNIEnv 时,可以使用 Dalvik VM 对象的 JavaVM* jvm-&GetEnv()方法,该方法会返回当前线程所在的 JNIEnv*;
6)Java 的 dex 字节码和 C/C++ 的 .so
同时运行 Dalvik VM 之内,共同使用一个进程空间;
6.3 C 语言的 JNIEnv
由上面代码可知,C 语言的JNIEnv 就是const struct JNINativeInterface*,而 JNIEnv* env就等价于JNINativeInterface** env,env 实际是一个二级指针,所以想要得到 JNINativeInterface 结构体中定义的函数指针,就需要先获取 JNINativeInterface 的一级指针对象*env,然后才能通过一级指针对象调用 JNI 函数,例如:
(*env)-&NewStringUTF(env, "hello")
struct JNINativeInterface {
reserved0;
reserved1;
reserved2;
reserved3;
(*GetVersion)(JNIEnv *);
(*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*, jsize);
(*FindClass)(JNIEnv*, const char*);
(*FromReflectedMethod)(JNIEnv*, jobject);
(*FromReflectedField)(JNIEnv*, jobject);
/* spec doesn't show jboolean parameter */
(*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);
(*GetSuperclass)(JNIEnv*, jclass);
(*IsAssignableFrom)(JNIEnv*, jclass, jclass);
/* spec doesn't show jboolean parameter */
(*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean);
//……定义了一系列关于 Java 操作的函数
6.4 C++的 JNIEnv
由typedef _JNIEnv JNIE可知,C++的 JNIEnv 是 _JNIEnv 结构体,而 _JNIEnv 结构体定义了 JNINativeInterface 的结构体指针,内部定义的函数实际上是调用 JNINativeInterface 的函数,所以C++的 env 是一级指针,调用时不需要加 env 作为函数的参数,例如:env-&NewStringUTF(env, "hello")
struct _JNIEnv {
/* it does not seem to be entirely opaque */
const struct JNINativeInterface*
#if defined(__cplusplus)
jint GetVersion()
{ return functions-&GetVersion(this); }
jclass DefineClass(const char *name, jobject loader, const jbyte* buf, jsize bufLen)
{ return functions-&DefineClass(this, name, loader, buf, bufLen); }
jclass FindClass(const char* name)
{ return functions-&FindClass(this, name); }
jmethodID FromReflectedMethod(jobject method)
{ return functions-&FromReflectedMethod(this, method); }
jfieldID FromReflectedField(jobject field)
{ return functions-&FromReflectedField(this, field); }
jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic)
{ return functions-&ToReflectedMethod(this, cls, methodID, isStatic); }
jclass GetSuperclass(jclass clazz)
{ return functions-&GetSuperclass(this, clazz); }
7. JNI 的两种注册方式
Java 的 native 方法是如何链接 C/C++中的函数的呢?可以通过静态和动态的方式注册JNI。
7.1静态注册
原理:根据函数名建立 Java 方法和 JNI 函数的一一对应关系。流程如下:
先编写 Java 的 native 方法;
然后用 javah 工具生成对应的头文件,执行命令 javah packagename.classname可以生成由包名加类名命名的 jni 层头文件,或执行命名javah -o custom.h packagename.classname,其中 custom.h 为自定义的文件名;
实现 JNI 里面的函数,再在Java中通过System.loadLibrary加载 so 库即可;
静态注册的方式有两个重要的关键词 JNIEXPORT 和 JNICALL,这两个关键词是宏定义,主要是注明该函数式 JNI 函数,当虚拟机加载 so 库时,如果发现函数含有这两个宏定义时,就会链接到对应的 Java 层的 native 方法。
由前面3. 查看 jni.h 文件源码方法生成头文件的方法,重新创建一个cn.cfanr.test_jni.Jni_Test.java的类
public class Jni_Test {
private static native int swap();
private static native void swap(int a, int b);
private static native void swap(String a, String b);
private native void swap(int[] arr, int a, int b);
private static native void swap_0(int a, int b);
用 javah 工具生成以下头文件:
#include &jni.h&
/* Header for class cn_cfanr_test_jni_Jni_Test */
#ifndef _Included_cn_cfanr_test_jni_Jni_Test
#define _Included_cn_cfanr_test_jni_Jni_Test
#ifdef __cplusplus
extern "C" {
cn_cfanr_test_jni_Jni_Test
* Signature: ()I
JNIEXPORT jint JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap__
(JNIEnv *, jclass);
// 凡是重载的方法,方法后面都会多一个下划线
cn_cfanr_test_jni_Jni_Test
* Signature: (II)V
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap__II
(JNIEnv *, jclass, jint, jint);
cn_cfanr_test_jni_Jni_Test
* Signature: (Ljava/lang/SLjava/lang/S)V
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap__Ljava_lang_String_2Ljava_lang_String_2
(JNIEnv *, jclass, jstring, jstring);
cn_cfanr_test_jni_Jni_Test
* Signature: ([III)V
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap___3III
(JNIEnv *, jobject, jintArray, jint, jint);
// 非 static 的为 jobject
cn_cfanr_test_jni_Jni_Test
* Signature: (II)V
JNIEXPORT void JNICALL Java_cn_cfanr_test_1jni_Jni_1Test_swap_10
(JNIEnv *, jclass, jint, jint);
// 不知道为什么后面没有 II
#ifdef __cplusplus
可以看出 JNI 的调用函数的定义是按照一定规则命名的:
JNIEXPORT 返回值 JNICALL Java_全路径类名_方法名_参数签名(JNIEnv* , jclass, 其它参数);
其中 Java_ 是为了标识该函数来源于 Java。经检验(不一定正确),如果是重载的方法,则有“参数签名”,否则没有;另外如果使用的是 C++,在函数前面加上 extern “C”(表示按照 C 的方式编译),函数命名后面就不需要加上“参数签名”。
另外还需要注意几点特殊规则:(参考: 2.2.1 本地方法名解析)
1. 包名或类名或方法名中含下划线 _ 要用 _1 连接;
2. 重载的本地方法命名要用双下划线 __ 连接;
3. 参数签名的斜杠 “/” 改为下划线 “_” 连接,分号 “;” 改为 “_2” 连接,左方括号 “[” 改为 “_3” 连接;
另外,对于 Java 的 native 方法,static 和非 static 方法的区别在于第二个参数,static 的为 jclass,非 static 的 为 jobject;JNI 函数中是没有修饰符的。
实现比较简单,可以通过 javah 工具将 Java代码的 native 方法直接转化为对应的native层代码的函数;
javah 生成的 native 层函数名特别长,可读性很差;
后期修改文件名、类名或函数名时,头文件的函数将失效,需要重新生成或手动改,比较麻烦;
程序运行效率低,首次调用 native 函数时,需要根据函数名在 JNI 层搜索对应的本地函数,建立对应关系,有点耗时;
7.2 动态注册
原理:直接告诉 native 方法其在JNI 中对应函数的指针。通过使用 JNINativeMethod 结构来保存 Java native 方法和 JNI 函数关联关系,步骤:
先编写 Java 的 native 方法;
编写 JNI 函数的实现(函数名可以随便命名);
利用结构体 JNINativeMethod 保存Java native方法和 JNI函数的对应关系;
利用registerNatives(JNIEnv* env)注册类的所有本地方法;
在 JNI_OnLoad 方法中调用注册方法;
在Java中通过System.loadLibrary加载完JNI动态库之后,会调用JNI_OnLoad函数,完成动态注册;
//JNINativeMethod结构体
typedef struct {
const char*
//Java中native方法的名字
const char*
//Java中native方法的描述符
//对应JNI函数的指针
} JNINativeM
* @param clazz java类名,通过 FindClass 获取
* @param methods JNINativeMethod 结构体指针
* @param nMethods 方法个数
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
//JNI_OnLoad
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
由于篇幅原因,具体的静态注册、动态注册、数据类型映射和描述符的练习放到下一篇文章:。
注:文章通过阅读 JNI 的文档和参照网上的博客总结出来的,如有错误,还望指出!
扩展阅读:
对贡献有激情,对回报有信心!
-------------------------
个人博客: http://cfanr.cn
GitHub: https://github.com/navyifanr
CSDN: http://blog.csdn.net/navyifanr
Android跨进程通信IPC整体内容如下 1、Android跨进程通信IPC之1——Linux基础2、Android跨进程通信IPC之2——Bionic3、Android跨进程通信IPC之3——关于&JNI&的那些事4、Android跨进程通信IPC之4——Android...
_ 声明: 对原文格式以及内容做了细微的修改和美化, 主要为了方便阅读和理解 _ 一. 基础 Java Native Interface (JNI) 标准是Java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI 是本地编程接口,它使得在 Java 虚拟机...
最近看到了很多关于热补的开源项目——Depoxed(阿里)、AnFix(阿里)、DynamicAPK(携程)等,它们都用到了JNI编程,并且JNI编程也贯穿了Android系统,学会JNI编程对于我们学习研究Android源码、Android安全以及Android安全加固等...
注:原文地址 紧接上篇:Android NDK开发:JNI基础篇 | cfanr,这篇主要介绍 JNI Native 层调用Java 层的代码(涉及JNI 数据类型映射和描述符的使用)和如何动态注册 JNI。 1. Hello World NDK 在开始实战练习前,你需要先...
本人为初学者,文章写得不好,如有错误,请大力怼我 或者看这里 如何使用jni进行开发 本文主要针对Android环境进行NDK\Native\Jni开发进行介绍 使用2.2版本之前的Android Studio进行ndk开发是比较繁琐的,如果你还在使用旧版本的Android...
程序员大部分的时间都是在和代码打交道,因此,对于文本编辑器一定不会陌生了。编辑器是处理文本的工具。 就像趁手的兵器对武林高手的辅助作用一样,强大的编辑器也会使编码工作事半功倍,趣味十足。 可是,什么样的编辑器可以称之为强大呢?江湖中,流传着关于两大编辑器的传说, 有关最强编...
最近在项目工地上,陪同监理dick指导工作,边工作边听得到超级个体,不务正业了,可是没影响工作,老外也没意见,所以说时间算是充分利用了起来。时不时会思考,如何结合跨学科的信息与知识,融汇起来,形成自己的创新思维与管理方法。 比如说,小到工作方法与使用工具,工作方法,使用管理...
当我看到问题的时候,我就在成为问题制造的一部分。为什么我觉得付钱付1200多去买衣服是一个问题?我经常处于做还是不做的纠结犹豫中,很多事都是这样,总是要想很久,想在大脑里预演做,然后是预演不做,然后又会各自纠结于做还是不做,做了有哪些不好不做有哪些不好,我买衣服的时候,其实...
1、女人的改变 中午翻到很多以前旅游的照片,曾经也是女汉子一枚,那个清汤寡水稚嫩的自己也看不下去。当时的我是怎么忍受着过每一天的,我想问问以前的自己。 Z.D.从新加坡回来之后第一句话就说“你比以前有女人味了”。是啊,这样的女人味琢磨了很久。打耳洞折磨了一年多,刚开始在老家...

我要回帖

更多关于 android是安卓系统吗 的文章

 

随机推荐