2025-05-25

一、概述
      對於大部分應用開發者來說可能都不怎麼接觸到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)傳輸的數據都正確。

 

摘自  lknlfy
 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *