android JNI学习四

来源:互联网 发布:报告编制软件 编辑:程序博客网 时间:2024/05/20 20:48

这次尝试做 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/类名$嵌套类名;              类名      jobject
例子:
Landroid/os/FileUtils$FileStatus;  FileStatus  jobject

由于网上比较少自定义类传递的方法,所以我尝试了一下,是可以的。从 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.csdn.net/detail/u013820413/6998891

0 0