您的位置:首页 > 移动开发 > Android开发

Android NDK开发----- Java与C互相调用实例详解

2014-02-24 17:12 696 查看
一、概述

对于大部分应用开发者来说可能都不怎么接触到NDK,但如果涉及到硬件操作的话就不得不使用NDK了。使用NDK还有另一个原因,就是C/C++的效率比较高,因此我们可以把一些耗时的操作放在NDK中实现。

关于java与c/c++的互相调用,网上有一大堆的文章介绍。但仔细观察可以发现,基本都是讲在java中调用一个本地方法,然后由该本地方法直接返回一个参数给java(例如,在java中定义的本地方法为private int callJNI(int i))。但在大多数时候要求的并不是由开发者在java层主动去调JNI中的函数来返回想要的数据,而是由JNI主动去调java中的函数。举个最简单的例子,Android中的Camera,图像数据由内核一直往上传到java层,然而这些数据的传递并不需要开发者每一次主动去调用来JNI中的函数来获取,而是由JNI主动传给用java中方法,这类似于Linux驱动机制中的异步通知。

二、要求

用NDK实现Java与C/C++互调,实现int,string,byte[]这三种类型的互相传递。

三、实现

下面的实现中,每次java调用JNI中的某个函数时,最后会在该函数里回调java中相应的方法而不是直接返回一个参数。可能你会觉得这不还是每次都是由开发者来主动调用吗,其实这只是为了讲解而已,在实际应用中,回调java中的方法应该由某个事件(非java层)来触发。

新建工程MyCallback,修改main.xml文件,在里面添加3个Button,分别对应3种类型的调用和3个TextView分别显示由JNI回调java时传给java的数据。完整的main.xml文件如下:

<LinearLayout 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"

android:orientation="vertical"

tools:context=".MainActivity" >

<Button

android:id="@+id/intbutton"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="传给JNI一个整数 1:" />

<TextView

android:id="@+id/inttextview"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="接收到的整数:" />

<Button

android:id="@+id/stringbutton"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="传给JNI一个字符A:" />

<TextView

android:id="@+id/stringtextview"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="接收到的字符:" />

<Button

android:id="@+id/arraybutton"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="传给JNI一个数组12345:" />

<TextView

android:id="@+id/arraytextview"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="接收到的数组:" />

</LinearLayout>

修改MyCallbackActivity.java文件,定义了一个Handler,当JNI回调java的方法时,用来发送消息;实现3个Button的监听。如下:



import android.app.Activity;

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

public class MainActivity extends Activity

{

private Button intButton = null;

private Button stringButton = null;

private Button arrayButton = null;

private TextView intTextView = null;

private TextView stringTextView = null;

private TextView arrayTextView = null;



private Handler mHandler = null;



@Override

public void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);



intButton = (Button)this.findViewById(R.id.intbutton);

//注册按钮监听

intButton.setOnClickListener(new ClickListener());

stringButton = (Button)this.findViewById(R.id.stringbutton);

//注册按钮监听

stringButton.setOnClickListener(new ClickListener());

arrayButton = (Button)this.findViewById(R.id.arraybutton);

//注册按钮监听

arrayButton.setOnClickListener(new ClickListener());



intTextView = (TextView)this.findViewById(R.id.inttextview);

stringTextView = (TextView)this.findViewById(R.id.stringtextview);

arrayTextView = (TextView)this.findViewById(R.id.arraytextview);



//消息处理

mHandler = new Handler()

{

@Override

public void handleMessage(Message msg)

{

switch(msg.what)

{

//整型

case 0:

{

intTextView.setText(msg.obj.toString());

break;

}

//字符串

case 1:

{

stringTextView.setText(msg.obj.toString());

break;

}

//数组

case 2:

{ byte[] b = (byte[])msg.obj;

arrayTextView.setText(Byte.toString(b[0])+Byte.toString(b[1])+Byte.toString(b[2])+Byte.toString(b[3])+Byte.toString(b[4]));

break;

}

}



}



};





}



//按钮监听实现

public class ClickListener implements View.OnClickListener

{

@Override

public void onClick(View v)

{

// TODO Auto-generated method stub

switch(v.getId())

{

case R.id.intbutton:

{

//调用JNI中的函数

callJNIInt(1);

break;

}

case R.id.stringbutton:

{

//调用JNI中的函数

callJNIString("你好A");

break;

}

case R.id.arraybutton:

{

//调用JNI中的函数

callJNIByte(new byte[]{1,2,3,4,5});

break;

}

}

}



}





//被JNI调用,参数由JNI传入

private void callbackInt(int i)

{

Message msg = new Message();

//消息类型

msg.what = 0;

//消息内容

msg.obj = i;

//发送消息

mHandler.sendMessage(msg);

}



//被JNI调用,参数由JNI传入

private void callbackString(String s)

{

Message msg = new Message();

//消息类型

msg.what = 1;

//消息内容

msg.obj = s;

//发送消息

mHandler.sendMessage(msg);

}



//被JNI调用,参数由JNI传入

private void callbackByte(byte[] b)

{

Message msg = new Message();

//消息类型

msg.what = 2;

//消息内容

msg.obj = b;

//发送消息

mHandler.sendMessage(msg);

}



//本地方法,由java调用

private native void callJNIInt(int i);

private native void callJNIString(String s);

private native void callJNIByte(byte[] b);



static

{

//加载本地库

System.loadLibrary("myjni");

}



}

最后就是本篇随笔的“重头戏”,在工程的根目录下新建jni文件夹,在里面添加一个Android.mk文件和一个callback.c文件,Android.mk文件如下:



LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := myjni

LOCAL_SRC_FILES := callback.c

LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

#include <string.h>

#include <jni.h>

JNIEXPORT void JNICALL Java_com_example_mycallback_MainActivity_callJNIInt( JNIEnv* env, jobject obj , jint i)

{

//找到java中的类

jclass cls = (*env)->FindClass(env, "com/example/mycallback/MainActivity");

//再找类中的方法

jmethodID mid = (*env)->GetMethodID(env, cls, "callbackInt", "(I)V");

if (mid == NULL)

{

printf("int error");

return;

}

//打印接收到的数据

printf("from java int: %d",i);

//回调java中的方法

(*env)->CallVoidMethod(env, obj, mid ,i);



}

JNIEXPORT void JNICALL Java_com_example_mycallback_MainActivity_callJNIString( JNIEnv* env, jobject obj , jstring s)

{

//找到java中的类

jclass cls = (*env)->FindClass(env, "com/example/mycallback/MainActivity");

//再找类中的方法

jmethodID mid = (*env)->GetMethodID(env, cls, "callbackString", "(Ljava/lang/String;)V");

if (mid == NULL)

{

printf("string error");

return;

}

const char *ch;

//获取由java传过来的字符串

ch = (*env)->GetStringUTFChars(env, s, NULL);

//打印

printf("from java string: %s",ch);

(*env)->ReleaseStringUTFChars(env, s, ch);

//回调java中的方法

(*env)->CallVoidMethod(env, obj, mid ,(*env)->NewStringUTF(env,"你好haha"));

}

JNIEXPORT void JNICALL JNICALL Java_com_example_mycallback_MainActivity_callJNIByte( JNIEnv* env, jobject obj , jbyteArray b)

{

//找到java中的类

jclass cls = (*env)->FindClass(env, "com/example/mycallback/MainActivity");

//再找类中的方法

jmethodID mid = (*env)->GetMethodID(env, cls, "callbackByte", "([B)V");

if (mid == NULL)

{

// LOGI("byte[] error");

return;

}



//获取数组长度

jsize length = (*env)->GetArrayLength(env,b);

printf("length: %d",length);

//获取接收到的数据

int i;

jbyte* p = (*env)->GetByteArrayElements(env,b,NULL);

//打印

for(i=0;i<length;i++)

{

printf("%d",p[i]);

}

char c[5];

c[0] = 1;c[1] = 2;c[2] = 3;c[3] = 4;c[4] = 5;

//构造数组

jbyteArray carr = (*env)->NewByteArray(env,length);

(*env)->SetByteArrayRegion(env,carr,0,length,c);

//回调java中的方法

(*env)->CallVoidMethod(env, obj, mid ,carr);

}

利用ndk-build编译生成相应的库。代码都非常简单,思路在一开始的时候已经说明了,下面看运行结果。

分别点击三个按钮,效果如下:





再看看LogCat输出:





可见两个方向(java<--->JNI)传输的数据都正确。

摘自 lknlfy
http://blog.sina.com.cn/s/blog_972577b30101dawx.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: