Android NDK开发(1)----- Java与C互相调用实例详解
一、概述
对于大部分应用开发者来说可能都不怎么接触到NDK,但如果涉及到硬件操作的话就不得不使用NDK了。使用NDK还有另一个原因,就是C/C++的效率比较高,因此我们可以把一些耗时的操作放在NDK中实现。所以NDK主要做驱动的开发。
在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层)来触发。
步骤:
(1)Java类里的声明
package com.ljt.work;
import android.annotation.SuppressLint;
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 LjtndkActivity extends Activity{
//本地方法,由java调用
private native void callJNIInt(int i);
private native void callJNIString(String s);
private native void callJNIByte(byte[] b);
static
{
//加载本地库
System.loadLibrary(\"myjni\");
}
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;
@SuppressLint(\"HandlerLeak\")
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.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;
}
}
}
};
}
/*下面定义被JNI调用的方法*/
//被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);
}
//按钮监听事件
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(\"你好!我是从JNI函数中返回一个字符串\");
break;
}
case R.id.arraybutton:
{
//调用JNI中的函数
callJNIByte(new byte[]{1,2,3,4,5});
break;
}
}
}
}
}
(2)在工程的根目录下新建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)
callback.c文件如下:
#include #include #include #include #include #include #include #include #include #include #include #include #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO,
\"native-activity\", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN,
\"native-activity\", __VA_ARGS__))
/**********传输整数*************
两个方向都没有乱码问题
*/
JNIEXPORT void JNICALL Java_com_ljt_work_LjtndkActivity_callJNIInt
(JNIEnv* env, jobject obj , jint i){
//找到java中的类
jclass cls = (*env)->FindClass(env,\"com/ljt/work/LjtndkActivity\");
//再找类中的方法,调用callbackInt方法传输整数
jmethodID mid = (*env)->GetMethodID(env, cls, \"callbackInt\", \"(I)V\");
if (mid == NULL)
{
LOGI(\"int error\");
return;
}
//打印接收到的数据
LOGI(\"from java int: %d\",i);
//回调java中的方法
(*env)->CallVoidMethod(env, obj, mid ,i);
}
/********传输字符串*************
两个方向都没有乱码问题
*/
JNIEXPORT void JNICALL Java_com_ljt_work_LjtndkActivity_callJNIString
(JNIEnv* env, jobject obj , jstring s){
//找到java中的类
jclass cls = (*env)->FindClass(env,\"com/ljt/work/LjtndkActivity\");
//再找类中的方法callbackString
jmethodID mid = (*env)->GetMethodID(env, cls, \"callbackString\", \"(Ljava/lang/String;)V\");
if (mid == NULL)
{
LOGI(\"string error\");
return;
}
const char *ch;
//获取由java传过来的字符串
ch = (*env)->GetStringUTFChars(env, s, NULL);
//打印
LOGI(\"from java string: %s\",ch);
(*env)->ReleaseStringUTFChars(env, s, ch);
//回调java中的方法
(*env)->CallVoidMethod(env, obj, mid ,(*env)->NewStringUTF(env,\"你好!我是一个字符串haha\"));
}
/********传输数组(byte[])*************
*/
JNIEXPORT void JNICALL Java_com_ljt_work_LjtndkActivity_callJNIByte
(JNIEnv* env, jobject obj ,jbyteArray b){
//找到java中的类
jclass cls = (*env)->FindClass(env,\"com/ljt/work/LjtndkActivity\");
//再找类中的方法callbackByte
jmethodID mid = (*env)->GetMethodID(env, cls, \"callbackByte\", \"([B)V\");
if (mid == NULL)
{
LOGI(\"byte[] error\");
return;
}
//获取数组长度
jsize length = (*env)->GetArrayLength(env,b);
LOGI(\"length: %d\",length);
//获取接收到的数据
int i;
jbyte* p = (*env)->GetByteArrayElements(env,b,NULL);
//打印
for(i=0;i{LOGI(\"%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);
}
/********相加*************
*/
JNIEXPORT jlong JNICALL Java_com_ljt_work_LjtndkActivity_AddJNInum
(JNIEnv* env, jobject obj ,jlong x,jlong y){
return x+y;
}
进入H:\\android-ndk-r9d\\samples\\LJTNDKwork\\bin\\classes目录
用javah -jni
ljt@ljt-PC /cygdrive/H/android-ndk-r9d/samples/LJTNDKwork/bin/classes
$ javah -jni com.ljt.work.LjtndkActivity
.\\com\\ljt\\work\\LjtndkActivity.class: “android.annotation.Su
警告:无法找到类型为
ppressLint”的注释方法“value()”: 未找到 android.annotation.SuppressLint 的类文
件
1 警告
然后将到的.h库文件声明到.c文件中
#include #include 然后进入H:\\android-ndk-r9d\\samples\\LJTNDKwork
$NDK/ndk-build
ljt@ljt-PC /cygdrive/H/android-ndk-r9d/samples/LJTNDKwork
$ $NDK/ndk-build
[armeabi] Cygwin : Generating dependency file converter script
[armeabi] Compile thumb : myjni <= callback.c
[armeabi] SharedLibrary : libmyjni.so
[armeabi] Install : libmyjni.so => libs/armeabi/libmyjni.so
调试:
04-09 21:07:13.728: I/native-activity(555): from java string: 你好!我是一个字符串haha
04-09 21:07:13.728: W/dalvikvm(555): JNI WARNING: input is not valid
Modified UTF-8: illegal continuation byte 0xe3
04-09 21:07:13.728: W/dalvikvm(555): string: '���!����һ�����haha'
04-09 21:07:13.728: W/dalvikvm(555): in Lcom/ljt/work/LjtndkActivity;.callJNIString:(Ljava/lang/String;)V (NewStringUTF)