天天看點

Android NDK開發----- Java與C互相調用執行個體詳解

一、概述

      對于大部分應用開發者來說可能都不怎麼接觸到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檔案如下:

<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編譯生成相應的庫。代碼都非常簡單,思路在一開始的時候已經說明了,下面看運作結果。

分别點選三個按鈕,效果如下:

Android NDK開發----- Java與C互相調用執行個體詳解

再看看LogCat輸出:

Android NDK開發----- Java與C互相調用執行個體詳解

可見兩個方向(java<--->JNI)傳輸的資料都正确。

摘自  lknlfy

http://blog.sina.com.cn/s/blog_972577b30101dawx.html