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

二、要求
     

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

<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)传输的数据都正确。