Android中关于JNI 的学习(四)简单的例子,温故而知新

在第零篇文章简单地介绍了JNI编程的模式之后,后面两三篇文章,我们又针对JNI中的一些概念做了一些简单的介绍,也不知道我到底说的清楚没有,但相信很多童鞋跟我一样,在刚开始学习一个东西的时候,入门最好的方式就是一个现成的例子来参考,慢慢研究,再学习概念,再回过来研究代码,加深印象,从而开始慢慢掌握。

今天我们就再来做一个小Demo,这个例子会比前面稍微复杂一点,但是如果阅读过前面几篇文章的话,理解起来也还是很简单的。很多东西就是这样,未知的时候很可怕,理解了就很简单了。

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;
}

对应这四个方法,在上面都添加了一些注释,大家看着注释,自己研究一下逻辑,也就清楚了这几个方法实现的功能是什么,很简单的。

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文件进行编译,文件结构如下:

在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 的学习(四)简单的例子,温故而知新,布布扣,bubuko.com

时间: 2024-08-24 12:49:40

Android中关于JNI 的学习(四)简单的例子,温故而知新的相关文章

Android中关于JNI 的学习(零)简单的例子,简单地入门

Android中JNI的作用,就是让Java能够去调用由C/C++实现的代码,为了实现这个功能,需要用到Anrdoid提供的NDK工具包,在这里不讲如何配置了,好麻烦,配置了好久... 本质上,Java去调用C/C++的代码其实就是去调用C/C++提供的方法,所以,第一步,我们要创建一个类,并且定义一个Native方法,如下: JniTest类: public class JniTest { public native String getTestString(); } 可以看到,在这个方法的前

Android中关于JNI 的学习(五)在C文件中使用LogCat

事实上,本文是在Peter Jerde的How much information can be stored by ordering 52 playing cards文章基础上翻译.改编和扩展而来的.当然这是经过Jerde本人首肯的. 注意本文方法并非最优,也没有完全利用所有的信息空间,只是简单的尝试. 有数字的地方就有信息.所以扑克牌中保存信息不是什么新鲜事. PDF文档点这里:下载 原文(英文)点这里:访问 这里有两个DEMO. 编码DEMO,解码DEMO 首先是"DEEP IN SHALL

Android中关于JNI 的学习(三)在JNI层访问Java端对象

前面两篇文章简单介绍了JNI层跟Java层的一些对应关系,包括方法名,数据类型和方法名称等,相信在理论层面,能够很好地帮助我们去了解JNI在Native本地开发中的作用,对JNI的一些概念也有了一个初步的认识,由于表达能力或者理解还是有限,有些地方讲得不是很清楚,如果各位朋友有觉得云里雾里,欢迎大家留言一起学习. 概念上的理解有助于我们更好地认识JNI,而一些实际点的例子则能够更好地帮我们从代码上去掌握并应用JNI. 在第一篇文章,我们是从一个小例子来入门学习的,在其中,我们通过JNI层函数返回

Android中关于JNI 的学习(二)对于JNI方法名,数据类型和方法签名的一些认识

处理特征数据 及 代码 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26240241 输入文件:  1. 归一化后的特征文件, 第1列是标签, 其余列是特征; 2. 特征最大值向量文件: 前3列是标准格式, 其余列是最大值; 输出文件: 1. 符合SVM训练数据的特征格式; 2. Mat存储的标准XML文件; 代码: /* 处理特征数据程序 By C.L.Wang 数据格式: 特征数据: 第1列是标签, 其余列是特征; 最大

Android中关于JNI 的学习(零)简单的样例,简单地入门

Android中JNI的作用,就是让Java可以去调用由C/C++实现的代码,为了实现这个功能.须要用到Anrdoid提供的NDK工具包,在这里不讲怎样配置了,好麻烦,配置了好久. . . 本质上,Java去调用C/C++的代码事实上就是去调用C/C++提供的方法.所以,第一步,我们要创建一个类,而且定义一个Native方法.例如以下: JniTest类: public class JniTest { public native String getTestString(); } 能够看到,在这

Android中关于JNI 的学习(四)简单的样例,温故而知新

在第零篇文章简单地介绍了JNI编程的模式之后.后面两三篇文章,我们又针对JNI中的一些概念做了一些简单的介绍,也不知道我究竟说的清楚没有.但相信非常多童鞋跟我一样.在刚開始学习一个东西的时候,入门最好的方式就是一个现成的样例来參考,慢慢研究,再学习概念.再回过来研究代码,加深印象,从而開始慢慢掌握. 今天我们就再来做一个小Demo.这个样例会比前面略微复杂一点.可是假设阅读过前面几篇文章的话,理解起来也还是非常easy的. 非常多东西就是这样.未知的时候非常可怕.理解了就非常easy了. 1)我

Android中关于JNI 的学习(三)在JNI层訪问Java端对象

前面两篇文章简介了JNI层跟Java层的一些相应关系,包含方法名,数据类型和方法名称等,相信在理论层面.可以非常好地帮助我们去了解JNI在Native本地开发中的作用,对JNI的一些概念也有了一个初步的认识,因为表达能力或者理解还是有限,有些地方讲得不是非常清楚.假设各位朋友有认为云里雾里,欢迎大家留言一起学习. 概念上的理解有助于我们更好地认识JNI.而一些实际点的样例则可以更好地帮我们从代码上去掌握并应用JNI. 在第一篇文章,我们是从一个小样例来入门学习的,在当中,我们通过JNI层函数返回

Android中关于JNI 的学习(六)JNI中注冊方法的实现

在前面的样例中,我们会发现,当在Java类中定义一个方法的时候,例如以下: public class ParamTransferTest { public static int testval = 1; public native void changeTestVal(); 则在相应的JNI层中,由javah生成的头文件和其相应的C文件,其方法名称必须例如以下: JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTes

Android中关于JNI 的学习(一)对于JNIEnv的一些认识

一个简单的样例让我们初步地了解JNI的作用,可是关于JNI中的一些概念还是须要了解清楚,才可以更好的去利用它来实现我们想要做的事情. 那么C++和Java之间的是怎样通过JNI来进行互相调用的呢? 我们知道.在Android中,当Java文件被编译成dex文件之后,会由类载入器载入到Dalvik VM(DVM)中,由DVM来进行解释,翻译成机器语言之后,才干由机器来执行. 而对于C/C++来说,其源码经由Android提供的NDK工具包,能够编译成可运行动态库(即.so文件).之后.Java和C