源代码地址:
链接:
百度网盘 请输入提取码
提取码:gtlz
一、Demo介绍
为了更深入的理解Jni编程知识,我们通过一个简单demo把相关知识用实际代码串起来,会有更清晰的认识。在这个demo中,涉及数组处理、字符串处理、类成员变量访问修改等内容,基本涵盖了常见的用法。
相关介绍 |
涉及文件 (java代码/xml布局文件) |
demo界面示例 |
主界面 点击button进入对应示例 |
src/com.example.jnideo.main.java res/layout/main.xml |
|
加法计算器 输入两个float数,jni层完成计算后返回java层显示结果 |
src/com.example.jnideo.myadd.java res/layout/add.xml |
|
数组排序 给的1个int数组,调用jni层排序算法,再返回排序结果 |
src/com.example.jnideo.mysort.java res/layout/sort.xml |
|
字符串 给的小写字符串,jni层将其改为大写后返回展示 |
src/com.example.jnideo.myString.java res/layout/mystr.xml |
|
圆计算 给的圆的半径,底层计算好该圆的各种信息后返回对象 |
src/com.example.jnideo.myCircle.java res/layout/circle.xml |
|
变量访问 底层获取变量初始值并修改 |
src/com.example.jnideo.fieldAccess.java res/layout/field.xml |
|
1. 加法计算器
我们先通过EidtText获取用户输入的值,然后调用NativeMethod.nativeAdd()将两个float值传给底层Jni实现加法计算后返回给上层展示。
public void onClick(View v){
if(v == bt1){
String str1 = et1.getText().toString();
float v1 = getFloatValue(str1);
String str2 = et2.getText().toString();
float v2 = getFloatValue(str2);
//call native method
float ret = NativeMethod.nativeAdd(v1,v2);
et1.setText(String.valueOf(v1));
et2.setText(String.valueOf(v2));
tv.setText(String.valueOf(ret));
}
}
因为是基本数据类型,不涉及复杂的数据获取方法,因此Jni层代码比较简单:
JNIEXPORT jfloat JNICALL Java_com_example_jnidemo_NativeMethod_nativeAdd
(JNIEnv *env, jobject thisz, jfloat a,jfloat b)
{
float v = a+b;
return v;
}
2. 数组排序
在该例中,我们默认设定了一个数组arr,然后调用NativeMethod.nativeSort()方法让Jni层实现数组的排序。然后将返回的结果显示出来。
private int arr[] = {5,12,1,3,9,4,7};
public void onClick(View v){
if(v == bt_sort){
int[] arr_out = NativeMethod.nativeSort(arr,arr.length);
if(arr_out != null){
String strout = "[";
for(int m=0; m<arr_out.length-1; m++)
strout += String.valueOf(arr_out[m])+",";
strout += String.valueOf(arr_out[arr_out.length-1])+"]";
et2.setText(strout);
}
}
}
Jni层先利用env->GetIntArrayElements获取了输入数据的指针,然后申请了一个新空间完成数据的拷贝,拷贝完成后调用env->ReleaseIntArrayElements释放了对输入数组的引用。对于排序算法,直接调用std::sort函数完成。为了将排序完的数组返回回去,先通过(env)->NewIntArray申请数组对象,然后利用(env)->SetIntArrayRegion完成赋值。
JNIEXPORT jintArray JNICALL Java_com_example_jnidemo_NativeMethod_nativeSort
(JNIEnv *env,jobject thisz, jintArray arr, jint len)
{
jint *pdata = NULL;
pdata = env->GetIntArrayElements(arr,0);
if(pdata == NULL)
return NULL;
if(env->GetArrayLength(arr) != len)
return NULL;
int *p = new int[len];
memcpy(p,pdata,sizeof(int)*len);
env->ReleaseIntArrayElements(arr, pdata, 0);
std::sort(p,p+len);
jintArray arrsort = NULL;
arrsort = (env)->NewIntArray(len);
if(arrsort !=NULL)
(env)->SetIntArrayRegion(arrsort,0,len,(jint*)p);
delete []p;
return arrsort;
}
3. 字符串
该例中,我们默认定义了一个小写的字符串,调用Native.nativeString方法让Jni层完成大写的转换,然后返回结果展示。
private String strin = "this is a example";
public void onClick(View v){
if(v == bt_str){
String sout = NativeMethod.nativeString(strin);
et2.setText(sout);
}
}
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_NativeMethod_nativeString
(JNIEnv *env,jobject thisz, jstring s)
{
const char *str = env->GetStringUTFChars(s,0);
if(str == NULL)
return NULL;
int len = env->GetStringUTFLength(s);
char *pdata = new char[len+1];
memcpy(pdata,str,len+1);
env->ReleaseStringUTFChars(s,str);
for(int i=0; i<len; i++)
{
if('a'<=pdata[i] && pdata[i]<='z')
{
pdata[i] = pdata[i] - ('a'-'A');
}
}
jstring strout = env->NewStringUTF(pdata);
return strout;
}
4. 圆计算
该例是为了说明如果通过Jni返回一个对象,我们首先定义了名为circleData的类,里面还涵盖了圆的直径、面积、周长、面积是否大于10的成员变量。
package com.example.jnidemo;
public class circleData{
public float circumference;
public float area;
public float diameter;
public boolean bLarge;
}
我们希望输入圆的半径,然后Jni层完成相关计算后,以circleData对象形式返回给Java层展示。
public void onClick(View v){
if(v == bt){
String str1 = et1.getText().toString();
float radius = getFloatValue(str1);
circleData ret = NativeMethod.nativeCircle(radius);
if(ret != null){
String str = "圆的直径:"+ret.diameter+"\n";
str += "圆的周长:"+ret.circumference+"\n";
str += "圆的面积:"+ret.area+"\n";
if(ret.bLarge)
str+="圆的面积大于10\n";
else
str+="圆的面积小于10\n";
tv1.setText(str);
}
et1.setText(String.valueOf(radius));
}
}
JNIEXPORT jobject JNICALL Java_com_example_jnidemo_NativeMethod_nativeCircle
(JNIEnv *env,jobject thisz, jfloat r)
{
float pi = 3.2415926f;
jclass objCls = env->FindClass("com/example/jnidemo/circleData");
if(objCls == NULL)
return NULL;
jmethodID id_method = env->GetMethodID(objCls,"<init>","()V");
jobject data = env->NewObject(objCls,id_method,"()V");
if(data == NULL)
return NULL;
jfieldID id_cir = env->GetFieldID(objCls,"circumference","F");
if(id_cir != NULL){
float v1 = 2.f*pi*r;
env->SetFloatField(data,id_cir,v1);
}
jfieldID id_area = env->GetFieldID(objCls,"area","F");
if(id_area != NULL){
float v2 = pi*r*r;
env->SetFloatField(data,id_area,v2);
}
jfieldID id_diam = env->GetFieldID(objCls,"diameter","F");
if(id_diam != NULL){
float v1 = 2.f*r;
env->SetFloatField(data,id_diam,v1);
}
jfieldID id_blarg = env->GetFieldID(objCls,"bLarge","Z");
if(id_blarg != NULL){
if(pi*r*r > 10.f)
env->SetBooleanField(data,id_blarg,true);
else
env->SetBooleanField(data,id_blarg,false);
}
return data;
}
5. 变量访问
在该例中,我们底层获取NativeMethod中4个变量的值,底层做出修改后返回上层做展示。
public class NativeMethod{
static {
System.loadLibrary("NativeMethod");
}
public String name = "java_abc";
public boolean bSucess = false;
public int width = 100;
public int[] coord = null;
public native void accessField();
}
JNIEXPORT void JNICALL Java_com_example_jnidemo_NativeMethod_accessField
(JNIEnv *env,jobject thisz)
{
/* Get a reference to obj’s class */
jclass cls = env->GetObjectClass(thisz);
if(cls == NULL)
return;
jfieldID fid_width = env->GetFieldID(cls,"width","I");
if(fid_width != NULL)
{
jint jw = env->GetIntField(thisz,fid_width);
env->SetIntField(thisz,fid_width,jw*2);
}
jfieldID fid_coord = env->GetFieldID(cls,"coord","[I");
if(fid_coord != NULL)
{
jintArray arr = env->NewIntArray(4);
if(arr != NULL)
{
int tmp[4]={1,2,3,4};
env->SetIntArrayRegion(arr,0,4,tmp);
env->SetObjectField(thisz,fid_coord,arr);
env->DeleteLocalRef(arr);
}
}
/**/
jfieldID fid_sucess = env->GetFieldID(cls, "bSucess","Z");
if(fid_sucess != NULL)
{
jboolean jbln = env->GetBooleanField(thisz,fid_sucess);
if(jbln == false)
env->SetBooleanField(thisz,fid_sucess,true);
else
env->SetBooleanField(thisz,fid_sucess,false);
}
/* Look for the instance field ID in cls */
jfieldID fid_name = (env)->GetFieldID(cls, "name", "Ljava/lang/String;");
if(fid_name != NULL)
{
/* Read the instance field*/
jstring jstr = (jstring)env->GetObjectField(thisz, fid_name);
if(jstr!=NULL)
{
const char *str = env->GetStringUTFChars(jstr, NULL);
if (str != NULL)
{
env->ReleaseStringUTFChars(jstr, str);
/* Create a new string and overwrite the instance field */
jstr = env->NewStringUTF("native_abc");
if (jstr != NULL)
env->SetObjectField(thisz,fid_name,jstr);
}
}
}
}
二、JNI代码编译
为了编译native代码,我们先在src同级目录建一个jni目录,然后把native文件拷贝至该目录下,同时需要编写Andriod.mk和Application两个文件。通过NDK来实现native代码的编译。
1. 编译环境设置
下面介绍下Elipse环境下的编译设置过程:
- 右键点击JniDemo项目,弹出页面选择最下面的”Properties”选项。
- 在左侧栏选择”Builders”,右侧点击”New”新建一个native编译器。
- 新建后,指定ndk-builder.cmd的位置,以及待编译的android工程位置。
- “builer options”下勾选运行方式,选择jni目录
完成上述设置后,编译整个Android工程,会自动对jni代码进行编译。并自动生成一个lib文件夹,里面包括了生成的lib库文件。
2. 编译文件
除了我们自己编写的native代码,编译少不了Android.mk和Application.mk两个文件,它们用来指定编译的各种设置,这里仅给出本demo中的文件内容,有关Android.mk的详细介绍在这里列出官网的介绍说明:
Android.mk | Android NDK | Android Developers
- Android.mk文件
LOCAL_PATH := $(call my-dir) PVRSDKDIR := $(my-dir)/ include $(CLEAR_VARS) LOCAL_MODULE := NativeMethod LOCAL_SRC_FILES := mycalculation_jni.cpp include $(BUILD_SHARED_LIBRARY)
- Application.mk文件
APP_PLATFORM := android-9 APP_ABI := armeabi-v7a APP_STL := stlport_shared #APP_STL := c++_shared