JNI编程示例

  • Post author:
  • Post category:其他


源代码地址:

链接:

百度网盘 请输入提取码

提取码: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环境下的编译设置过程:

  1. 右键点击JniDemo项目,弹出页面选择最下面的”Properties”选项。

  1. 在左侧栏选择”Builders”,右侧点击”New”新建一个native编译器。

  1. 新建后,指定ndk-builder.cmd的位置,以及待编译的android工程位置。

  1. “builer options”下勾选运行方式,选择jni目录

完成上述设置后,编译整个Android工程,会自动对jni代码进行编译。并自动生成一个lib文件夹,里面包括了生成的lib库文件。

2. 编译文件

除了我们自己编写的native代码,编译少不了Android.mk和Application.mk两个文件,它们用来指定编译的各种设置,这里仅给出本demo中的文件内容,有关Android.mk的详细介绍在这里列出官网的介绍说明:

Android.mk  |  Android NDK  |  Android Developers

  1. 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)

  1. Application.mk文件
APP_PLATFORM := android-9

APP_ABI :=  armeabi-v7a

APP_STL := stlport_shared
#APP_STL := c++_shared



版权声明:本文为duzj279原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。