Java與C互相調用實例詳解

轉自:http://www.cnblogs.com/lknlfy/archive/2012/03/13/2394153.html

一、概述

      對於大部分應用開發者來說可能都不怎麼接觸到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編譯生成相應的庫。代碼都非常簡單,思路在一開始的時候已經說明了,下面看運行結果。

分別點擊三個按鈕,效果如下:




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章