在第零篇文章简单地介绍了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熟悉了解了,再去了解这些框架的东西,会有很大的帮助的。
结束。
分享到:
相关推荐
该app为android系统下简单的jni示例,是学习JNI的好例子,为入门者提供简单的JNI环境配置示例
Android中调用JNI例子,对应博客http://blog.csdn.net/wwj_748/article/details/28300451
这是一个androidstudio环境下的jni的学习,里面包含了基本数据类型转化,引用数据类型转化,json格式数据类型的转化等
Android中调用JNI例子.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
android中jni的简单使用示例代码
android JNI学习三的最后代码实例
android JNI学习四里最后实例代码和文件
Android中实现JNI的AES加解密源代码,绝对可以运行!
Android中的JNI测试程序 最简单的实现JNI的测试代码,配合我的博客的博文学习
Android 串口读写程序 JNI代码程序
Android JNI串口通讯【实战例子】 学习NDK开发很好的例子,也可以直接用在项目中
本程序是在Eclipse中创建的一个Android Application,该项目中是在JNI中利用OpenGL ES库和OpenCV库进行一副图片的渲染。
Android NDK JNI 经典实例Android NDK JNI 经典实例Android NDK JNI 经典实例Android NDK JNI 经典实例Android NDK JNI 经典实例Android NDK JNI 经典实例Android NDK JNI 经典实例
android通过JNI访问硬件LED,包含应用程序APP,JNI代码,和LED驱动程序。
Android studio 下 NDK Jni 开发 简单demo 谢谢
Android JNI Android JNI 用C函数写本地库读写文件,底层调用小例子用C函数写本地库读写文件,底层调用小例子
上次发的代码: http://download.csdn.net/detail/iloveyoueveryday/6909583 好多人说调不通,于是做了个小例子,改了一些东西(其实还是一样的),给大家看看,要是还有问题,就说明是你的NDK环境没有设置好了
Android JNI 部分学习文档 http://blog.csdn.net/liuhaomatou/article/details/8659088
此时,VM扮演着桥梁的角色,让Java与C组件能通过标准的JNI介面而相互沟通。 应用层的Java类是在虚拟机(VM: Vitual Machine)上执行的,而C件不是在VM上执行,那么Java程式又如何要求VM去载入(Load)所指定的C组件呢?...
在jni中获取android 设备的mac地址