这次尝试做 java 与 C++ 的自定义类做参数传递的 jni 实现。目标是在 java 里建一个存放 image 的类,类里有4个成员变量,分别是:像素格式,图片高度,图片宽度和图片数据流。传递这个类到 C++ 里,C++ 把内容写进去,java 显示图片。
在 android 工程里添加类文件 jData.java 如下:
package com.example.jnicall;
public class jData {
public int pixelFormat;
public int iHeightPixels; // Image dimension height
public int iWidthPixels; // Image dimension width
public byte pImage[]; // Pointer to the Image array.
}
MainActivity.java 修改如下:
package com.example.jnicall;
import java.nio.ByteBuffer;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
public class MainActivity extends Activity {
ImageView miv;
jData mjd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button mb1;
mjd = new jData();
miv = (ImageView)findViewById(R.id.imgAlbumArt);
mb1 = (Button)findViewById(R.id.button1);
mb1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
add(1,2);
_getImage(mjd);
showImage();
}
});
}
static {
System.load("/system/jni/libjnicall.so");
}
public native int add(int add1, int add2);
public native void _getImage(jData jdata);
private void showImage()
{
Bitmap bMap;
ByteBuffer buffer;
bMap = Bitmap.createBitmap(mjd.iWidthPixels, mjd.iHeightPixels, Bitmap.Config.RGB_565);
buffer = ByteBuffer.wrap(mjd.pImage);
bMap.copyPixelsFromBuffer(buffer);
miv.setImageBitmap(bMap);
}
}
新加一个 Button 按钮和 ImageView 控件用于显示图片。
布局文件如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView1"
android:layout_below="@+id/textView1"
android:layout_marginTop="19dp"
android:text="Button" />
<ImageView
android:id="@+id/imgAlbumArt"
android:layout_width="200px"
android:layout_height="200px"
android:layout_alignLeft="@+id/button1"
android:layout_below="@+id/button1"
android:layout_marginTop="31dp"
android:background="@drawable/albumart_mp_unknown"
android:contentDescription="@string/app_name" />
</RelativeLayout>
jni_call.cpp 文件修改如下:
#include "tools.h"
#include <jni.h>
#include "JNIHelp.h"
#include <android/log.h>
#define JNIREG_CLASS "com/example/jnicall/MainActivity"//指定要注册的类
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "jni_call", __VA_ARGS__)
#ifdef __cplusplus
extern "C" {
#endif
jint Java_com_example_jnicall_MainActivity_add(JNIEnv* env, jobject thiz, jint add1, jint add2){
LOGD("add = %d",add(add1, add2));
return add(add1, add2);
}
#ifdef __cplusplus
} // extern "C"
#endif
JNIEXPORT void JNICALL jni_getImage(JNIEnv* env, jclass jclazz, jobject jData)
{
jclass cls;
jfieldID fid;
jbyteArray jImageBytes;
if((cls = env->GetObjectClass(jData)) == NULL)
{
return ;
}
if ((fid = env->GetFieldID(cls, "pixelFormat", "I")) == NULL)
{
return ;
}
env->SetIntField(jData, fid, 1);
if((fid = env->GetFieldID(cls, "iHeightPixels", "I")) == NULL)
{
return ;
}
env->SetIntField(jData, fid, 50);
if ((fid = env->GetFieldID(cls, "iWidthPixels", "I")) == NULL)
{
return ;
}
env->SetIntField(jData, fid, 50);
FILE *fp;
fp = fopen("system/jni/image", "r");
if (fp == NULL) {
LOGD("jni_getImage --- p == NULL");
return ;
}
unsigned char *bufwi = (unsigned char*)malloc(5000);
fread(bufwi, 5000, 1, fp);
fclose(fp);
jImageBytes = env->NewByteArray(5000);
if (jImageBytes == NULL)
{
return ;
}
env->SetByteArrayRegion(jImageBytes, 0, 5000, (jbyte*)bufwi);
free(bufwi);
if((fid = env->GetFieldID(cls, "pImage", "[B")) == NULL)
{
return ;
}
env->SetObjectField(jData, fid, jImageBytes);
LOGD("jni_getImage");
return ;
}
static JNINativeMethod gMethods[] = {
{ "_getImage", "(Lcom/example/jnicall/jData;)V", (void*) jni_getImage }
};
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0])))
{
return JNI_FALSE;
}
return JNI_TRUE;
}
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv *env;
if ( (vm->GetEnv( (void**) &env, JNI_VERSION_1_4)) != JNI_OK) {
return -1;
}
if (!registerNatives(env)) { //注册
return -1;
}
return JNI_VERSION_1_4;
}
这时做接口扩展的优势就体现出来了,对比一下 JNINativeMethod 结构体
以前:{ "_getImage", "()V", (void*) jni_getImage }
现在:{ "_getImage", "(Lcom/example/jnicall/jData;)V", (void*) jni_getImage }
在第二项里,括号内是Lcom/example/jnicall/jData; 这个是有规范的,要以大写L开头,; 做结尾。
参照网上的说明 :在 Android Jni 编程中如何接收和返回 Java 不同类型变量或对象 http://blog.sina.com.cn/s/blog_4c451e0e0101339r.html
里面说:类(class):(则以 L 开头,以 ; 结尾,中间是用 / 隔开的 包 及 类名)
L包1/包n/类名$嵌套类名;
例子:
Landroid/os/FileUtils$FileStatus;
由于网上比较少自定义类传递的方法,所以我尝试了一下,是可以的。从 adnroid 工程可看出,包名是 com.example.jnicall 类名是 jData 。在 jni_getImage 函数里,
以前是:JNIEXPORT void JNICALL jni_getImage(JNIEnv* env, jclass jclazz) 只有两个参数
现在是:JNIEXPORT void JNICALL jni_getImage(JNIEnv* env, jclass jclazz, jobject jData) 三个参数,第三个参数就是从 java 里传下来的自定义类对象,统一用 jobject 表示。在 jni_getImage 函数里实现把读取出来的图片数据流写入 jData 类里面,这样 java 只要把类里的数据流转换成图片即可。
这里略为说一下 jni 对数据的操作,env->SetIntField(jData, fid, 1); 是写入对应 jDate 类里面的 pixelFormat 成员的值为1,若想更多了解可自行百度查询其他类型的写入方法。
对于 C++ 里面 char 数组转成 java 里的 byte 数组就有些复杂。
先定义 jbyteArray数组: jbyteArray jImageBytes; 然后给 jImageBytes 初始化分配空间:jImageBytes = env->NewByteArray(5000); 这个大小是根据我的图片而定的,我的图片是打开 /system/jni/ 目录下的一个 image 文件,并读取里面的图片数据流。初始化 jImageBytes 分配空间后,把 C或C++ 里的 char 数组转换成 byte 数组并写入:env->SetByteArrayRegion(jImageBytes, 0, 5000, (jbyte*)bufwi); SetByteArrayRegion 函数是 jni 里专门用于数组拷贝,这里是把 bufwi 拷贝到 jImageBytes 里,bufwi 是一个unsigned char 指针,用 SetByteArrayRegion 函数时,直接把 bufwi 强转为 jbyte 指针即可。拷贝完后,调用 env->SetObjectField(jData, fid, jImageBytes); 把 jImageBytes 写入 jData 的 pImage 里。SetObjectField 可用于对 java byte 数组的操作。
显示效果如下:
点击按钮读取图片并显示:
工程代码及image文件:http://download.youkuaiyun.com/detail/u013820413/6998891