一、概述
对于大部分应用开发者来说可能都不怎么接触到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文件如下:
1 26 7 13 14 20 21 27 28 34 35 41 42 48 49 50
修改MyCallbackActivity.java文件,定义了一个Handler,当JNI回调java的方法时,用来发送消息;实现3个Button的监听。如下:
1 package com.nan.callback; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.os.Handler; 6 import android.os.Message; 7 import android.view.View; 8 import android.widget.Button; 9 import android.widget.TextView; 10 11 12 public class MyCallbackActivity extends Activity 13 { 14 private Button intButton = null; 15 private Button stringButton = null; 16 private Button arrayButton = null; 17 private TextView intTextView = null; 18 private TextView stringTextView = null; 19 private TextView arrayTextView = null; 20 21 private Handler mHandler = null; 22 23 24 /** Called when the activity is first created. */ 25 @Override 26 public void onCreate(Bundle savedInstanceState) 27 { 28 super.onCreate(savedInstanceState); 29 setContentView(R.layout.main); 30 31 intButton = (Button)this.findViewById(R.id.intbutton); 32 //注册按钮监听 33 intButton.setOnClickListener(new ClickListener()); 34 stringButton = (Button)this.findViewById(R.id.stringbutton); 35 //注册按钮监听 36 stringButton.setOnClickListener(new ClickListener()); 37 arrayButton = (Button)this.findViewById(R.id.arraybutton); 38 //注册按钮监听 39 arrayButton.setOnClickListener(new ClickListener()); 40 41 intTextView = (TextView)this.findViewById(R.id.inttextview); 42 stringTextView = (TextView)this.findViewById(R.id.stringtextview); 43 arrayTextView = (TextView)this.findViewById(R.id.arraytextview); 44 45 //消息处理 46 mHandler = new Handler() 47 { 48 @Override 49 public void handleMessage(Message msg) 50 { 51 switch(msg.what) 52 { 53 //整型 54 case 0: 55 { 56 intTextView.setText(msg.obj.toString()); 57 break; 58 } 59 //字符串 60 case 1: 61 { 62 stringTextView.setText(msg.obj.toString()); 63 break; 64 } 65 //数组 66 case 2: 67 { byte[] b = (byte[])msg.obj; 68 arrayTextView.setText(Byte.toString(b[0])+Byte.toString(b[1])+Byte.toString(b[2])+Byte.toString(b[3])+Byte.toString(b[4])); 69 break; 70 } 71 } 72 73 } 74 75 }; 76 77 78 } 79 80 //按钮监听实现 81 public class ClickListener implements View.OnClickListener 82 { 83 84 @Override 85 public void onClick(View v) 86 { 87 // TODO Auto-generated method stub 88 switch(v.getId()) 89 { 90 case R.id.intbutton: 91 { 92 //调用JNI中的函数 93 callJNIInt(1); 94 break; 95 } 96 case R.id.stringbutton: 97 { 98 //调用JNI中的函数 99 callJNIString("你好A"); 100 break; 101 } 102 case R.id.arraybutton: 103 { 104 //调用JNI中的函数 105 callJNIByte(new byte[]{1,2,3,4,5}); 106 break; 107 } 108 } 109 } 110 111 } 112 113 114 //被JNI调用,参数由JNI传入 115 private void callbackInt(int i) 116 { 117 Message msg = new Message(); 118 //消息类型 119 msg.what = 0; 120 //消息内容 121 msg.obj = i; 122 //发送消息 123 mHandler.sendMessage(msg); 124 } 125 126 //被JNI调用,参数由JNI传入 127 private void callbackString(String s) 128 { 129 Message msg = new Message(); 130 //消息类型 131 msg.what = 1; 132 //消息内容 133 msg.obj = s; 134 //发送消息 135 mHandler.sendMessage(msg); 136 } 137 138 //被JNI调用,参数由JNI传入 139 private void callbackByte(byte[] b) 140 { 141 Message msg = new Message(); 142 //消息类型 143 msg.what = 2; 144 //消息内容 145 msg.obj = b; 146 //发送消息 147 mHandler.sendMessage(msg); 148 } 149 150 //本地方法,由java调用 151 private native void callJNIInt(int i); 152 private native void callJNIString(String s); 153 private native void callJNIByte(byte[] b); 154 155 static 156 { 157 //加载本地库 158 System.loadLibrary("myjni"); 159 } 160 161 }
最后就是本篇随笔的“重头戏”,在工程的根目录下新建jni文件夹,在里面添加一个Android.mk文件和一个callback.c文件,Android.mk文件如下:
1 LOCAL_PATH := $(call my-dir) 2 3 include $(CLEAR_VARS) 4 5 LOCAL_MODULE := myjni 6 LOCAL_SRC_FILES := callback.c 7 8 LOCAL_LDLIBS := -llog 9 10 include $(BUILD_SHARED_LIBRARY)
callback.c文件如下:
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 #include 11 #include 12 13 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native-activity", __VA_ARGS__)) 14 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "native-activity", __VA_ARGS__)) 15 16 17 18 /**********传输整数************* 19 20 */ 21 JNIEXPORT void JNICALL Java_com_nan_callback_MyCallbackActivity_callJNIInt( JNIEnv* env, jobject obj , jint i) 22 { 23 //找到java中的类 24 jclass cls = (*env)->FindClass(env, "com/nan/callback/MyCallbackActivity"); 25 //再找类中的方法 26 jmethodID mid = (*env)->GetMethodID(env, cls, "callbackInt", "(I)V"); 27 if (mid == NULL) 28 { 29 LOGI("int error"); 30 return; 31 } 32 //打印接收到的数据 33 LOGI("from java int: %d",i); 34 //回调java中的方法 35 (*env)->CallVoidMethod(env, obj, mid ,i); 36 37 } 38 39 /********传输字符串************* 41 */ 42 JNIEXPORT void JNICALL Java_com_nan_callback_MyCallbackActivity_callJNIString( JNIEnv* env, jobject obj , jstring s) 43 { 44 //找到java中的类 45 jclass cls = (*env)->FindClass(env, "com/nan/callback/MyCallbackActivity"); 46 //再找类中的方法 47 jmethodID mid = (*env)->GetMethodID(env, cls, "callbackString", "(Ljava/lang/String;)V"); 48 if (mid == NULL) 49 { 50 LOGI("string error"); 51 return; 52 } 53 const char *ch; 54 //获取由java传过来的字符串 55 ch = (*env)->GetStringUTFChars(env, s, NULL); 56 //打印 57 LOGI("from java string: %s",ch); 58 (*env)->ReleaseStringUTFChars(env, s, ch); 59 //回调java中的方法 60 (*env)->CallVoidMethod(env, obj, mid ,(*env)->NewStringUTF(env,"你好haha")); 61 62 } 63 64 /********传输数组(byte[])************* 65 */ 66 JNIEXPORT void JNICALL Java_com_nan_callback_MyCallbackActivity_callJNIByte( JNIEnv* env, jobject obj , jbyteArray b) 67 { 68 //找到java中的类 69 jclass cls = (*env)->FindClass(env, "com/nan/callback/MyCallbackActivity"); 70 //再找类中的方法 71 jmethodID mid = (*env)->GetMethodID(env, cls, "callbackByte", "([B)V"); 72 if (mid == NULL) 73 { 74 LOGI("byte[] error"); 75 return; 76 } 77 78 //获取数组长度 79 jsize length = (*env)->GetArrayLength(env,b); 80 LOGI("length: %d",length); 81 //获取接收到的数据 82 int i; 83 jbyte* p = (*env)->GetByteArrayElements(env,b,NULL); 84 //打印 85 for(i=0;i NewByteArray(env,length); 94 (*env)->SetByteArrayRegion(env,carr,0,length,c); 95 //回调java中的方法 96 (*env)->CallVoidMethod(env, obj, mid ,carr); 97 }
利用ndk-build编译生成相应的库。代码都非常简单,思路在一开始的时候已经说明了,下面看运行结果。
分别点击三个按钮,效果如下:
再看看LogCat输出:
可见两个方向(java<--->JNI)传输的数据都正确。
附上完整工程代码,Android2.3的。