在使用java的jni调用C++接口时候, 我们会先把数据转换成基本类型, 比如int, float, double, int[]等等, 一方面减低参数类型转换上的繁琐, 另一方面也许也能减少耦合. 实际应用时候, 可能会遇到希望一个jni接口返回多个参数的情况, 这情况下, 貌似就不得不用自定义类型参数的转换了. 这篇文章, 介绍的就是一个自定义类型参数转换的android例子程序.
这个例子是二维数组自定义类的, 其实三维四维甚至更多纬的数组的使用方法都是如此类推的.遇到这种需求时候,就能轻松加愉快的解决了.
关于android jni ndk编程入门, 可以参考这篇文章.
1. 在java代码中加入一个返回自定义类型的native接口:
package com.jnitest; import android.app.Activity; import android.os.Bundle; public class JnitestActivity extends Activity { /** Called when the activity is first created. */ static { System.loadLibrary("test-jni"); } native static PointF[][] createPointFs(int len1, int len2); //返回PointF的数组,PointF是一个自定义的类 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); PointF[][] points = createPointFs(2, 2); for (int i = 0; i < points.length; i++) { for (int j = 0; j < points[0].length; j++) { System.out.println(points[i][j].x + "," + points[i][j].y); } } } }
下面是PointF.java
package com.jnitest;
public class PointF
{
public float x;
public float y;
public PointF(float xx, float yy)
{
x = xx;
y = yy;
}
}
2. 使用javah命令生成C/C++的头文件.(这篇文章有介绍如何使用javah命令生成头文件)
生成下图红色框框内的头文件
3. 实现.cpp文件,代码如下:
#include "com_jnitest_JnitestActivity.h"
#include <stdio.h>
JNIEXPORT jobjectArray JNICALL Java_com_jnitest_JnitestActivity_createPointFs
(JNIEnv *jenv, jclass jcls, jint jlen1, jint jlen2)
{
//convert parameter to C/C++ type
int len1 = (int) jlen1;
int len2 = (int) jlen2;
//create java type PointF
jclass objectClass = (jenv)->FindClass("com/jnitest/PointF");
jobjectArray jpointfs1 = (jenv)->NewObjectArray((jsize) len2, objectClass, NULL);
jobjectArray pointfArrayArray = (jenv)->NewObjectArray((jsize) len1, (jenv)->GetObjectClass(jpointfs1), NULL);
jmethodID cid = (jenv)->GetMethodID(objectClass, "<init>", "(FF)V");
for (int j = 0; j < len1; j++)
{
jobjectArray jpointfarray = (jenv)->NewObjectArray((jsize) len2, objectClass, NULL);
for (int i = 0; i < len2; i++)
{
jfloat jx = (jfloat) i;
jfloat jy = (jfloat) j;
jobject pointF = (jenv)->NewObject(objectClass, cid, jx, jy);
(jenv)->SetObjectArrayElement(jpointfarray, i, pointF);
(jenv)->DeleteLocalRef(pointF);
}
(jenv)->SetObjectArrayElement(pointfArrayArray, j, jpointfarray);
(jenv)->DeleteLocalRef(jpointfarray);
}
(jenv)->DeleteLocalRef(jpointfs1);
return pointfArrayArray;
}
下面一点一点的解释上面这些代码:
1) int len1 = (int) jlen1;
把java的int类型转换成,C/C++的int类型,直接转换就可以了.
2) jclass objectClass = (jenv)->FindClass("com/jnitest/PointF");
获得java自定义类PointF, FindClass的参数,指明的是从source file开始的PointF类的路径
3) jobjectArray jpointfs1 = (jenv)->NewObjectArray((jsize) len2, objectClass, NULL);
创建个PointF的数组(参数objectClass指明了这个数组的元素类型), 数组的长度为len2
4) jobjectArray pointfArrayArray = (jenv)->NewObjectArray((jsize) len1, (jenv)->GetObjectClass(jpointfs1), NULL);
这里是创建一个以PointF的数组为元素的数组, 实际上它是一个PointF的二维数组, 因为上面定义的java native函数返回的就是一个PointF[][].
5) jmethodID cid = (jenv)->GetMethodID(objectClass, "<init>", "(FF)V");
获得PointF构造函数的ID.
"<init>",这里指明的是函数名字,构造函数就写"<init>".
"(FF)V",圆括号内表示参数类型,FF代表,有两个参数都是float类型.圆括号后的V,代表函数返回类型是void.关于函数签名, 这里和这里都有比较详细的对照表和说明.
6) jobject pointF = (jenv)->NewObject(objectClass, cid, jx, jy);
创建一个PointF类型的对象. objectClass是要创建的对象的类型, cid是构造函数的ID, jx和jy是构造函数的两个参数.
7) (jenv)->SetObjectArrayElement(jpointfarray, i, pointF);
这行代码就容易理解, 往jpointfarray数组给元素赋值, i 是要赋值元素的下标, pointF就是要赋的值.
8) (jenv)->DeleteLocalRef(pointF);
删除引用计数. 这里为什么要删除引用技术呢, 因为(6)里面每次都会new一个对象, 引用计数会加1, 当引用技术超过某个数(好像是500,具体忘记了)就会crash. 这篇文章介绍了LocalRef这个问题.
4. 编译so文件,并运行程序,查看logcat中的输出如下.
可以看到创建了一个2*2的二位数组