Android中关于JNI 的学习(四)简单的样例,温故而知新
在第零篇文章简单地介绍了JNI编程的模式之后。后面两三篇文章,我们又针对JNI中的一些概念做了一些简单的介绍,也不知道我究竟说的清楚没有。但相信非常多童鞋跟我一样。在刚開始学习一个东西的时候,入门最好的方式就是一个现成的样例来參考,慢慢研究,再学习概念。再回过来研究代码,加深印象,从而開始慢慢掌握。
今天我们就再来做一个小Demo。这个样例会比前面略微复杂一点。可是假设阅读过前面几篇文章的话,理解起来也还是非常easy的。
非常多东西就是这样。未知的时候非常可怕。理解了就非常easy了。
1)我们首先定义一个Java类,里面包括几个native方法,例如以下:
public class ParamTransferTest {
	public static int testval = 1;
	public native void changeTestVal();
	public native int add(int x, int y);
	public native String addTail(String tail);
	public native int[] changeArray(int[] arr);
}
2)利用javah工具生成相应的头文件。例如以下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_lms_jni_ParamTransferTest */ #ifndef _Included_com_lms_jni_ParamTransferTest
#define _Included_com_lms_jni_ParamTransferTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_lms_jni_ParamTransferTest
* Method: changeTestVal
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTestVal
(JNIEnv *, jobject); /*
* Class: com_lms_jni_ParamTransferTest
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_lms_jni_ParamTransferTest_add
(JNIEnv *, jobject, jint, jint); /*
* Class: com_lms_jni_ParamTransferTest
* Method: addTail
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_lms_jni_ParamTransferTest_addTail
(JNIEnv *, jobject, jstring); /*
* Class: com_lms_jni_ParamTransferTest
* Method: changeArray
* Signature: ([I)[I
*/
JNIEXPORT jintArray JNICALL Java_com_lms_jni_ParamTransferTest_changeArray
(JNIEnv *, jobject, jintArray); #ifdef __cplusplus
}
#endif
#endif
上面就生成了相应的方法,在上面我们能够看到前面文章所介绍过的方法名称以Java开头,方法签名等信息。对吧。
3)编写 C 文件。例如以下:
#include <stdio.h>
#include <stdlib.h>
#include "com_lms_jni_ParamTransferTest.h"
#include <android/log.h>
#include <jni.h>
#include <malloc.h> #define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) char* Jstring2CStr(JNIEnv * env, jstring str){
char * rtn = NULL; jclass clsstring = (*env)->FindClass(env, "java/lang/String");//通过FindClass方法获得Java的String类
jstring strencode = (*env)->NewStringUTF(env, "UTF-8");//调用NewStringUTF方法,获得"UTF-8"字符串,作为编码
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");//获取String类的getBytes方法
jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, str, mid, strencode);//调用String类的getBytes方法 jsize alen = (*env)->GetArrayLength(env, barr);//获得数组长度
jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);//获得数组的首地址。C/C++中数组的首元素就是一个指针
if(alen > 0){
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}//上面这一步是将数组的值拷贝到一个char*的数组中,也就是C/C++的char数组。由于C/C++没有字符串概念,最后以0结尾。
(*env)->ReleaseByteArrayElements(env, barr, ba, 0);//释放内存
return rtn;
} /*
* Class: com_lms_jni_ParamTransferTest
* Method: changeTestVal
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTestVal
(JNIEnv * env, jobject obj){
jclass clazz = (*env)->GetObjectClass(env,obj);//获得obj相应的类。也就是ParamTransferTest
jint val = (*env)->GetStaticIntField(env, clazz,
(*env)->GetStaticFieldID(env, clazz,"testval","I"));//获取字段testval的值
LOGI("before change testval = %d", val);//加入Log信息
val = val + 1;
LOGI("after change testval = %d", val);
(*env)->SetStaticIntField(env, clazz,(*env)->GetStaticFieldID(env, clazz,"testval","I"),val);//设置字段testval的值
} /*
* Class: com_lms_jni_ParamTransferTest
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_lms_jni_ParamTransferTest_add
(JNIEnv * env, jobject obj, jint x, jint y){
LOGI("x = %d", x);
LOGI("y = %d", y);
return x + y; //返回參数x和y的和
} /*
* Class: com_lms_jni_ParamTransferTest
* Method: addTail
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_lms_jni_ParamTransferTest_addTail
(JNIEnv * env, jobject obj, jstring str){ char* p = Jstring2CStr(env,str);//将Java中的string转化为C/C++中的char数组
LOGI("str = %s", p);
char* newStr = " Tail ";
return (*env)->NewStringUTF(env, strcat(p, newStr));//调用strcat函数连接两个char数组,将通过NewStringUTF返回。
} /*
* Class: com_lms_jni_ParamTransferTest
* Method: changeArray
* Signature: ([I)[I
*/
JNIEXPORT jintArray JNICALL Java_com_lms_jni_ParamTransferTest_changeArray
(JNIEnv * env, jobject obj, jintArray ja){
int len = (*env)->GetArrayLength(env, ja);/获取数组长度
LOGI("len = %d", len);
LOGI("address = %#x", &ja); jint* arr = (*env)->GetIntArrayElements(env, ja, 0);//将数组中的全部元素存入以jint*为首地址的数组中(数组的首地址就是数组名) int i = 0;
for(;i < len; i++){
LOGI("arr[%d] = %d", i, *(arr + i));
*(arr + i) += 10;
}
//数组中每一个元素的值加上10return ja;由于GetIntArrayElements的最后一个參数是0,即表明获取的数组是不复制的。即它们操作同一块内存,所以返回哪个数组都是ok的
return ja;
}
相应这四个方法,在上面都加入了一些凝视。大家看着凝视。自己研究一下逻辑。也就清楚了这几个方法实现的功能是什么,非常easy的。
4)编写Android.mk文件,例如以下:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := com_lms_jni_HwDemo LOCAL_SRC_FILES := \
HwDemo.c \
JniTest.c \
ParamTransferTest.c LOCAL_LDLIBS += -llog include $(BUILD_SHARED_LIBRARY)
在这里有一点的注意的是,编译多个文件的时候,要利用反斜杠 "\"来进行换行,区分不同的C/C++文件。
而这里LOCAL_LDLIBS是JNI中运用log所须要加入的动态包。下一篇文章会讲。
5)编写好Android.mk文件之后,就利用ndk-build文件进行编译。文件结构例如以下:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGlubWlhbnNoZW5n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
在jni文件夹下执行ndk-build,例如以下:
到这里,关于JNI层的实现就结事了。以下就是Java端的事情。
6)Activity中调用,例如以下:
public class HwDemo extends Activity {
	static {
		System.loadLibrary("com_lms_jni_HwDemo");//载入动态包,名称就是Android.mk中的Module名称。
	}
	public native String printHello();	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		...
		TextView tvAdd = (TextView)findViewById(R.id.tvAdd);
		TextView tvString = (TextView)findViewById(R.id.tvString);
		TextView tvArray = (TextView)findViewById(R.id.tvArray);
		TextView tvChangeTestVal = (TextView)findViewById(R.id.tvChangeTestVal);
		ParamTransferTest ptt = new ParamTransferTest();
		//调用changeTestVa()方法
		ptt.changeTestVal();
		tvChangeTestVal.setText("" + ptt.testval);
		//调用add方法
		int sum = ptt.add(1, 2);
		tvAdd.setText(String.valueOf(sum));
		//调用addTail方法
		tvString.setText(ptt.addTail("lms"));
		//调用changeArray方法
		int[] newArr = ptt.changeArray(new int[]{1,2});
		StringBuilder sb = new StringBuilder();
		sb.append("[");
		sb.append(newArr[0]).append(",").append(newArr[1]);
		sb.append("]");
		tvArray.setText(sb.toString());
	}
}
7)结果显示:
关于假设在Java层调用JNI方法,还有在JNI层调用Java的方法,并操作Java的对象,相信经过这一个样例,再结合前面几篇文章所讲的东西,大家应该可以对JNI的作用和应用有一个比較主要的了解了。
Android中底层框架的实现,尤其是在启动的时候,Native层去载入一个系统核心服务,或者启动Zygote虚拟机的时候,用到了大量的JNI层面的框架。大家假设对JNI熟悉了解了,再去了解这些框架的东西。会有非常大的帮助的。
结束。
Android中关于JNI 的学习(四)简单的样例,温故而知新的更多相关文章
- Android中关于JNI 的学习(零)简单的样例,简单地入门
		
Android中JNI的作用,就是让Java可以去调用由C/C++实现的代码,为了实现这个功能.须要用到Anrdoid提供的NDK工具包,在这里不讲怎样配置了,好麻烦,配置了好久. . . 本质上,J ...
 - Android中关于JNI 的学习(一)对于JNIEnv的一些认识
		
一个简单的样例让我们初步地了解JNI的作用.可是关于JNI中的一些概念还是需要了解清楚,才干够更好的去利用它来实现我们想要做的事情. 那么C++和Java之间的是怎样通过JNI来进行互相调用的呢? 我 ...
 - Android中关于JNI 的学习(三)在JNI层訪问Java端对象
		
前面两篇文章简介了JNI层跟Java层的一些相应关系,包含方法名,数据类型和方法名称等,相信在理论层面.可以非常好地帮助我们去了解JNI在Native本地开发中的作用,对JNI的一些概念也有了一个初步 ...
 - Android中关于JNI 的学习(六)JNI中注冊方法的实现
		
在前面的样例中,我们会发现,当在Java类中定义一个方法的时候,例如以下: public class ParamTransferTest { public static int testval = 1 ...
 - Android中关于JNI 的学习(五)在C文件里使用LogCat
		
Log是开发过程中.对于我们调试程序非常重要的一个工具,有非常多时候,我们正是通过Log才干够看清楚程序是不是真的依照我们想像中的模式在跑,从而定位到问题所在的地方.而在Android开发中,毫无疑问 ...
 - Android中的SQLite使用学习
		
Android中的SQLite使用学习 SQLite是非常流行的嵌入式关系型数据库,轻载, 速度快,而且是开源.在Android中,runtime提供SQLite,所以我们可以使用SQLite,而且是 ...
 - 深入理解Android(5)——从MediaScanner分析Android中的JNI
		
前面几篇介绍了Android中的JNI和基本用法,这一篇我们通过分析Android源代码中的JNI实例,来对JNI部分做一个总结. 一.通向两个不同世界的桥梁 在前面我们说过,JNI就像一个桥梁,将J ...
 - android中使用jni对字符串加解密实现分析
		
android中使用jni对字符串加解密实现分析 近期项目有个需求.就是要对用户的敏感信息进行加密处理,比方用户的账户password,手机号等私密信息.在java中,就对字符串的加解密我们能够使用A ...
 - 深入理解Android(2)——理解Android中的JNI(中)
		
阳光小强参加了CSDN博客之星评选,如果你觉得阳光小强的博客对你有所帮助,为小强投上一票吧:http://vote.blog.csdn.net/blogstar2014/details?usernam ...
 
随机推荐
- teamviewer13报错
			
用自己的笔记本电脑远程桌面AGV电脑在终端运行teamviewer报错如下: Init...CheckCPU: SSE2 support: yesChecking setup...Launching ...
 - 个人博客 V0.0.3 版本 ...
			
早就想弄个人博客网站,一直拖到现在...事情总是忙不完的,想能力提升的快,只能挤时间多练多写了,Keep On Unsleeping 以后原创的笔记都会在自己的博客网站写,博客园和简书就用来转发文章了 ...
 - Systemd入门教程:实战篇(转)
			
作者: 阮一峰 日期: 2016年3月 8日 上一篇文章,我介绍了 Systemd 的主要命令,今天介绍如何使用它完成一些基本的任务. 一.开机启动 对于那些支持 Systemd 的软件,安装的时候, ...
 - LRC CRC 纵向冗余码校验
			
LRC CRC 纵向冗余码校验 2010-01-26 11:00:15| 分类: 电气 | 标签: |字号大中小 订阅 1.LRC校验 LRC域是一个包含一个8位二进制值的字节.LRC值由 ...
 - 如何使用apache的 work模式还是 prefork 模式
			
注意: 2.4之前版本默认为prefork, 2.4已经变为event模式.三种模式比较: http://www.cnblogs.com/fnng/archive/2012/11/20/2779977 ...
 - python 常用系统参数
			
获取当前路径: os.path.abspath('.') 或os.getcwd() 获取上一级路径: print os.path.abspath('..') 获取上上一级路径: os.path.ab ...
 - 【Hadoop】Flink VS Spark?Drill VS Presto?
			
参考资料: drill 官网:http://drill.apache.org/ drill安装使用:https://segmentfault.com/a/1190000002652348 drill简 ...
 - 解决no declaration can be found for element 'context:component-scan'
			
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.spr ...
 - 配置多层NAT和端口映射实现外网访问内网
			
配置多层NAT和端口映射实现外网访问内网 背景和原理 通过配置NAT可以实现内网中不能直接访问外网的主机通过NAT代理访问内网,配置方法这里不再赘述(前文有介绍).本文以两层的NAT代理做模拟,通过端 ...
 - (总结)Linux下的暴力密码在线破解工具Hydra详解
			
(总结)Linux下的暴力密码在线破解工具Hydra详解 学习了:https://blog.csdn.net/yafeichang/article/details/53502869