Android NDK開發

一、概述
      對於大部分應用開發者來說可能都不怎麼接觸到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 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="fill_parent"
 4     android:layout_height="fill_parent"
 5     android:orientation="vertical" >
 6
 7     <Button
 8         android:id="@+id/intbutton"
 9         android:layout_width="fill_parent"
10         android:layout_height="wrap_content"
11         android:text="傳給JNI一個整數1"
12         />
13    
14     <TextView
15         android:id="@+id/inttextview"
16         android:layout_width="fill_parent"
17         android:layout_height="wrap_content"
18         android:text="接收到的整數:"
19         />
20    
21     <Button
22         android:id="@+id/stringbutton"
23         android:layout_width="fill_parent"
24         android:layout_height="wrap_content"
25         android:text="傳給JNI一個字符A"
26         />
27    
28     <TextView
29         android:id="@+id/stringtextview"
30         android:layout_width="fill_parent"
31         android:layout_height="wrap_content"
32         android:text="接收到的字符:"
33         />
34    
35     <Button
36         android:id="@+id/arraybutton"
37         android:layout_width="fill_parent"
38         android:layout_height="wrap_content"
39         android:text="傳給JNI一個數組12345"
40         />
41    
42     <TextView
43         android:id="@+id/arraytextview"
44         android:layout_width="fill_parent"
45         android:layout_height="wrap_content"
46         android:text="接收到的數組:"
47         />
48    
49
50 </LinearLayout>

修改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 #include <string.h>
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <sys/ioctl.h>
 6 #include <sys/types.h>
 7 #include <sys/stat.h>
 8 #include <fcntl.h>
 9
10 #include <jni.h>
11 #include <android/log.h>
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<length;i++)
86     {
87         LOGI("%d",p[i]);   
88     }
89
90     char c[5];
91     c[0] = 1;c[1] = 2;c[2] = 3;c[3] = 4;c[4] = 5;
92     //構造數組
93     jbyteArray carr = (*env)->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)傳輸的數據都正確。

typedef const struct JNINativeInterface *JNIEnv;


const struct JNINativeInterface ... = {


    NULL,
    NULL,
    NULL,
    NULL,
    GetVersion,             //獲取JNI版本號


    DefineClass,             //通過指定ClassLoader對象加載類
    FindClass,             //獲取指定的Class類對象


    FromReflectedMethod,    //通過指定的java.lang.reflect.Method對象獲取方法ID
    FromReflectedField,    //通過指定的java.lang.reflect.Field對象獲取字段ID
    ToReflectedMethod,    //通過指定的方法ID獲取java.lang.reflect.Method對象


    GetSuperclass,         //獲取指定類的父類
    IsAssignableFrom,         //判斷指定類是否繼承自某類或實現了某個接口


    ToReflectedField,         //通過指定的字段ID獲取java.lang.reflect.Field對象


    Throw,                 //拋出指定的Throwable對象
    ThrowNew,             //拋出指定的Throwable對象,並制定消息
    ExceptionOccurred,     //獲取未被清空或捕獲的異常
    ExceptionDescribe,     //同printStackTrace()
    ExceptionClear,         //清除已被拋出的異常
    FatalError,             //拋出致命錯誤


    PushLocalFrame,   //Push/PopLocalFrame函數提供了本地引用管理的簡便方法,在該函數對之間的代碼段,所有創建的顯式本地引用無需手動調用DeleteLocalReference函數釋放,PopLocalFrame函數會自動釋放這些本地引用
    PopLocalFrame,


    NewGlobalRef,         //創建全局引用,必須被顯式清除
    DeleteGlobalRef,         //清除全局引用
    DeleteLocalRef,         //清除本地引用,通常本地引用在原生代碼返回時會自動清除,但是對於大數據,比如在循環中頻繁創建Java對象,而該對象的只在一次循環中有效,這種情況下有必要顯式調用DeleteLocalRef以通知VM儘快釋放該對象。
    IsSameObject,         //判斷是否爲同一對象,可通過該方法判斷弱引用是否已被釋放(NULL)
    NewLocalRef,             //創建本地引用
    EnsureLocalCapacity,     //確保當前線程可以創建指定數量的本地引用


    AllocObject,             //創建一個新的對象,只分配內存,不調用任何構造方法(cdwang:應該是爲了方便通過CallNonvirtual<Type>Method方法調用父類的構造函數初始化對象)
    NewObject,             //通過指定的構造函數創建對象,變長參數形式
    NewObjectV,             //同上,va_list形式
    NewObjectA,             //同上,數組形式


    GetObjectClass,         //獲取對象的Class類對象
    IsInstanceOf,             //同instanceOf


    GetMethodID,         //獲取實例(非靜態)方法的ID


    CallObjectMethod,     //調用實例方法,object表示返回值類型
    CallObjectMethodV,
    CallObjectMethodA,
    CallBooleanMethod,
    CallBooleanMethodV,
    CallBooleanMethodA,
    CallByteMethod,
    CallByteMethodV,
    CallByteMethodA,
    CallCharMethod,
    CallCharMethodV,
    CallCharMethodA,
    CallShortMethod,
    CallShortMethodV,
    CallShortMethodA,
    CallIntMethod,
    CallIntMethodV,
    CallIntMethodA,
    CallLongMethod,
    CallLongMethodV,
    CallLongMethodA,
    CallFloatMethod,
    CallFloatMethodV,
    CallFloatMethodA,
    CallDoubleMethod,
    CallDoubleMethodV,
    CallDoubleMethodA,
    CallVoidMethod,
    CallVoidMethodV,
    CallVoidMethodA,


    CallNonvirtualObjectMethod, //調用非虛實例方法,通過class對象指定調用哪個類的非虛函數(cdwang:應該是某對象不調用自己的構造類的方法實現,而是調用父類的方法實現,這個方法節省了子類對象向父類對象的強制轉換)
    CallNonvirtualObjectMethodV,
    CallNonvirtualObjectMethodA,
    CallNonvirtualBooleanMethod,
    CallNonvirtualBooleanMethodV,
    CallNonvirtualBooleanMethodA,
    CallNonvirtualByteMethod,
    CallNonvirtualByteMethodV,
    CallNonvirtualByteMethodA,
    CallNonvirtualCharMethod,
    CallNonvirtualCharMethodV,
    CallNonvirtualCharMethodA,
    CallNonvirtualShortMethod,
    CallNonvirtualShortMethodV,
    CallNonvirtualShortMethodA,
    CallNonvirtualIntMethod,
    CallNonvirtualIntMethodV,
    CallNonvirtualIntMethodA,
    CallNonvirtualLongMethod,
    CallNonvirtualLongMethodV,
    CallNonvirtualLongMethodA,
    CallNonvirtualFloatMethod,
    CallNonvirtualFloatMethodV,
    CallNonvirtualFloatMethodA,
    CallNonvirtualDoubleMethod,
    CallNonvirtualDoubleMethodV,
    CallNonvirtualDoubleMethodA,
    CallNonvirtualVoidMethod,
    CallNonvirtualVoidMethodV,
    CallNonvirtualVoidMethodA,


    GetFieldID,             //獲取成員字段ID


    GetObjectField,         //獲取字段值
    GetBooleanField,
    GetByteField,
    GetCharField,
    GetShortField,
    GetIntField,
    GetLongField,
    GetFloatField,
    GetDoubleField,
    SetObjectField,         //設置字段值
    SetBooleanField,
    SetByteField,
    SetCharField,
    SetShortField,
    SetIntField,
    SetLongField,
    SetFloatField,
    SetDoubleField,


    GetStaticMethodID,     //獲取靜態方法ID


    CallStaticObjectMethod, //調用靜態方法
    CallStaticObjectMethodV,
    CallStaticObjectMethodA,
    CallStaticBooleanMethod,
    CallStaticBooleanMethodV,
    CallStaticBooleanMethodA,
    CallStaticByteMethod,
    CallStaticByteMethodV,
    CallStaticByteMethodA,
    CallStaticCharMethod,
    CallStaticCharMethodV,
    CallStaticCharMethodA,
    CallStaticShortMethod,
    CallStaticShortMethodV,
    CallStaticShortMethodA,
    CallStaticIntMethod,
    CallStaticIntMethodV,
    CallStaticIntMethodA,
    CallStaticLongMethod,
    CallStaticLongMethodV,
    CallStaticLongMethodA,
    CallStaticFloatMethod,
    CallStaticFloatMethodV,
    CallStaticFloatMethodA,
    CallStaticDoubleMethod,
    CallStaticDoubleMethodV,
    CallStaticDoubleMethodA,
    CallStaticVoidMethod,
    CallStaticVoidMethodV,
    CallStaticVoidMethodA,


    GetStaticFieldID,         //獲取靜態字段ID


    GetStaticObjectField,     //獲取字段值
    GetStaticBooleanField,
    GetStaticByteField,
    GetStaticCharField,
    GetStaticShortField,
    GetStaticIntField,
    GetStaticLongField,
    GetStaticFloatField,
    GetStaticDoubleField,


    SetStaticObjectField,     //設置字段值
    SetStaticBooleanField,
    SetStaticByteField,
    SetStaticCharField,
    SetStaticShortField,
    SetStaticIntField,
    SetStaticLongField,
    SetStaticFloatField,
    SetStaticDoubleField,


    NewString,             //通過指定Unicode格式字符數組構造Java String


    GetStringLength,         //獲取Unicode字符的長度
    GetStringChars,         //獲取字符數組
    ReleaseStringChars,     //通知VM可以釋放String和由GetStringChars獲取的字符數組
 
    NewStringUTF,         //通過指定UTF-8格式字符數組構造Java String
    GetStringUTFLength,
    GetStringUTFChars,
    ReleaseStringUTFChars,
 
    GetArrayLength,         //獲取數組長度www.2cto.com
 
    NewObjectArray,         //通過指定的Class類對象和初始元素初始化一個Java對象數組
    GetObjectArrayElement,    //獲取指定索引的對象元素
    SetObjectArrayElement,    //設置指定索引的對象元素
 
    NewBooleanArray,     //構造基本類型的Java數組
    NewByteArray,
    NewCharArray,
    NewShortArray,
    NewIntArray,
    NewLongArray,
    NewFloatArray,
    NewDoubleArray,
 
    GetBooleanArrayElements,    //獲取基本類型元素的C數組
    GetByteArrayElements,
    GetCharArrayElements,
    GetShortArrayElements,
    GetIntArrayElements,
    GetLongArrayElements,
    GetFloatArrayElements,
    GetDoubleArrayElements,
 
    ReleaseBooleanArrayElements,    //通知VM可以釋放Java數組和由前述方法族獲取的C數組
    ReleaseByteArrayElements,
    ReleaseCharArrayElements,
    ReleaseShortArrayElements,
    ReleaseIntArrayElements,
    ReleaseLongArrayElements,
    ReleaseFloatArrayElements,
    ReleaseDoubleArrayElements,
 
    GetBooleanArrayRegion,    //獲取子數組
    GetByteArrayRegion,
    GetCharArrayRegion,
    GetShortArrayRegion,
    GetIntArrayRegion,
    GetLongArrayRegion,
    GetFloatArrayRegion,
    GetDoubleArrayRegion,
    SetBooleanArrayRegion,    //設置子數組
    SetByteArrayRegion,
    SetCharArrayRegion,
    SetShortArrayRegion,
    SetIntArrayRegion,
    SetLongArrayRegion,
    SetFloatArrayRegion,
    SetDoubleArrayRegion,
 
    RegisterNatives,         //映射原生代碼
    UnregisterNatives,
 
    MonitorEnter,
    MonitorExit,
 
    GetJavaVM,             //獲取當前線程關聯的VM


    GetStringRegion,        //獲取子串
    GetStringUTFRegion,


    GetPrimitiveArrayCritical,
    ReleasePrimitiveArrayCritical,


    GetStringCritical,        // Get/ReleaseStringCritical必須成對出現,語義和Get/ReleaseStringChars一樣,但代碼段中不能阻塞當前線程,也不能調用任何其他的JNI函數
    ReleaseStringCritical,   


    NewWeakGlobalRef,
    DeleteWeakGlobalRef,


    ExceptionCheck,        //判斷是否有掛起的異常


    NewDirectByteBuffer,
    GetDirectBufferAddress,
    GetDirectBufferCapacity
}; 

摘自  lknlfy
 

 

android - Jni Environment 函數

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