一、概述
对于大部分应用开发者来说可能都不怎么接触到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文件如下:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <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的监听。如下:
- package com.nan.callback;
- 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 MyCallbackActivity 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;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- 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;
- }
- }
- }
- };
- }
- //按钮监听实现
- 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)
callback.c文件如下:
- #include <string.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <jni.h>
- #include <android/log.h>
- #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_nan_callback_MyCallbackActivity_callJNIInt( JNIEnv* env, jobject obj , jint i)
- {
- //找到java中的类
- jclass cls = (*env)->FindClass(env, "com/nan/callback/MyCallbackActivity");
- //再找类中的方法
- 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_nan_callback_MyCallbackActivity_callJNIString( JNIEnv* env, jobject obj , jstring s)
- {
- //找到java中的类
- jclass cls = (*env)->FindClass(env, "com/nan/callback/MyCallbackActivity");
- //再找类中的方法
- 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_nan_callback_MyCallbackActivity_callJNIByte( JNIEnv* env, jobject obj , jbyteArray b)
- {
- //找到java中的类
- jclass cls = (*env)->FindClass(env, "com/nan/callback/MyCallbackActivity");
- //再找类中的方法
- 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<length;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);
- }
利用ndk-build编译生成相应的库。代码都非常简单,思路在一开始的时候已经说明了,下面看运行结果。
分别点击三个按钮,效果如下:
再看看LogCat输出:
可见两个方向(java<--->JNI)传输的数据都正确。