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

知识养成了思想,思想同时又在融化知识。这篇文章主要讲述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_HwDemoLOCAL_SRC_FILES := HwDemo.c JniTest.c ParamTransferTest.cLOCAL_LDLIBS += -lloginclude $(BUILD_SHARED_LIBRARY)


在这里有一点的注意的是。编译多个文件的时候,要利用反斜杠 "\"来进行换行。区分不同的C/C++文件。
而这里LOCAL_LDLIBS是JNI中运用log所须要加入的动态包,下一篇文章会讲。

5)编写好Android.mk文件之后。就利用ndk-build文件进行编译。文件结构例如以下:
Android中关于JNI 的学习简单的样例,温故而知新

文章图片




在jni文件夹下执行ndk-build,例如以下:
Android中关于JNI 的学习简单的样例,温故而知新

文章图片


到这里,关于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)结果显示:
Android中关于JNI 的学习简单的样例,温故而知新

文章图片

关于假设在Java层调用JNI方法,还有在JNI层调用Java的方法,并操作Java的对象,相信经过这一个样例。再结合前面几篇文章所讲的东西,大家应该可以对JNI的作用和应用有一个比較主要的了解了。
Android中底层框架的实现。尤其是在启动的时候,Native层去载入一个系统核心服务,或者启动Zygote虚拟机的时候,用到了大量的JNI层面的框架,大家假设对JNI熟悉了解了,再去了解这些框架的东西。会有非常大的帮助的。
【Android中关于JNI 的学习简单的样例,温故而知新】结束。










    推荐阅读