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

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

2013-10-29 16:04 711 查看
一、概述

对于大部分应用开发者来说可能都不怎么接触到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>

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

package com.sym.mycallback;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

	private Button intButton = null, stringButton = null, arrayButton = null;
	private TextView intText = null, stringText = null, arrayText = null;
	private Handler handler = null;

	@SuppressLint("HandlerLeak")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		init();
		handler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				// TODO Auto-generated method stub
				super.handleMessage(msg);
				switch (msg.what) {
				case 0:
					intText.setText(msg.obj.toString());
					break;
				case 1:
					stringText.setText(msg.obj.toString());
					break;
				case 2:
					byte[] b = (byte[]) msg.obj;
					arrayText.setText(Byte.toString(b[0]) + Byte.toString(b[1])
							+ Byte.toString(b[2]) + Byte.toString(b[3])
							+ Byte.toString(b[4]));
					break;
				default:
					break;
				}
			}
		};
	}

	private void init() {
		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());
		intText = (TextView) this.findViewById(R.id.inttextview);
		stringText = (TextView) this.findViewById(R.id.stringtextview);
		arrayText = (TextView) this.findViewById(R.id.arraytextview);
	}

	private class ClickListener implements OnClickListener {

		@Override
		public void onClick(View v) {
			// TODO Auto-generated method stub
			switch (v.getId()) {
			case R.id.intbutton:
				callJNIInt(1);
				break;
			case R.id.stringbutton:
				callJNIString("hello a");
				break;
			case R.id.arraybutton:
				callJNIByte(new byte[]{1,2,3,4,5});
				break;

			default:
				break;
			}
		}

	}
	
	//被JNI调用,参数由JNI传入
	private void callbackInt(int i){
		Message msg = new Message();
		//消息类型
		msg.what = 0;
		//消息内容
		msg.obj = 1;
		//发送消息
		handler.sendMessage(msg);
	}
	
	//被JNI调用,参数由JNI传入
	private void callbackString(String s){
		Message msg = new Message();
		//消息类型
		msg.what = 1;
		//消息内容
		msg.obj = s;
		//发送消息
		handler.sendMessage(msg);
	}
	
	//被JNI调用,参数由JNI传入
	private void callbackByte(byte[] b){
		Message msg = new Message();
		//消息类型
		msg.what = 2;
		//消息内容
		msg.obj = b;
		//发送消息
		handler.sendMessage(msg);
	}
	
	private native void callJNIInt(int i);
	private native void callJNIString(String s);
	private native void callJNIByte(byte[] b);
	
	static{
		//加载本地库
		System.loadLibrary("myjni");
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

}

最后就是本篇随笔的“重头戏”,在工程的根目录下新建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__))

/**********传输整数*************
com.sym.mycallback
*/
JNIEXPORT void JNICALL Java_com_sym_mycallback_MainActivity_callJNIInt( JNIEnv* env, jobject obj , jint i)
{
    //找到java中的类
    jclass cls = (*env)->FindClass(env, "com/sym/mycallback/MainActivity");
    //再找类中的方法
    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_sym_mycallback_MainActivity_callJNIString( JNIEnv* env, jobject obj , jstring s)
{
    //找到java中的类
    jclass cls = (*env)->FindClass(env, "com/sym/mycallback/MainActivity");
    //再找类中的方法
    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,"hello a"));

}

/********传输数组(byte[])*************
*/
JNIEXPORT void JNICALL Java_com_sym_mycallback_MainActivity_callJNIByte( JNIEnv* env, jobject obj , jbyteArray b)
{
    //找到java中的类
    jclass cls = (*env)->FindClass(env, "com/sym/mycallback/MainActivity");
    //再找类中的方法
    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)传输的数据都正确
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: