Android NDK 較完整的總結

NDK項目源碼地址 : 

-- 第一個JNI示例程序下載 : GitHub - https://github.com/han1202012/NDKHelloworld.git 

-- Java傳遞參數給C語言實例程序 : GitHub - https://github.com/han1202012/NDKParameterPassing.git 

--C語言回調Java方法示例程序 : GitHub - https://github.com/han1202012/NDK_Callback.git 

--分析Log框架層JNI源碼所需的Android底層文件 : CSDN - http://download.csdn.net/detail/han1202012/6905507

.

作者 :萬境絕塵 

轉載請註明出處 : http://blog.csdn.net/shulianghan/article/details/18964835

.

開發環境介紹 : 

-- eclipse : adt-bundle-windows-x86-20130917

-- sdk : 版本 2.3.3

-- ndk : android-ndk-r9c-windows-x86.zip

-- cygwin : 所需組件 binutils , gcc , gcc-mingw , gdb , make;

-- javah : jdk6.0自帶工具

-- javap : jdk6.0自帶工具


一. JNI介紹


1. JNI引入


JNI概念 : Java本地接口,Java Native Interface, 它是一個協議, 該協議用來溝通Java代碼和外部的本地C/C++代碼, 通過該協議 Java代碼可以調用外部的本地代碼, 外部的C/C++ 代碼可以調用Java代碼;


C和Java的側重 : 

-- C語言 : C語言中最重要的是 函數 function; 

-- Java語言 : Java中最重要的是 JVM, class類, 以及class中的方法;


C與Java如何交流 : 

-- JNI規範 : C語言與Java語言交流需要一個適配器, 中間件, 即 JNI, JNI提供了一種規範; 

-- C語言中調用Java方法 : 可以讓我們在C代碼中找到Java代碼class中的方法, 並且調用該方法; 

-- Java語言中調用C語言方法 : 同時也可以在Java代碼中, 將一個C語言的方法映射到Java的某個方法上; 

-- JNI橋樑作用 : JNI提供了一個橋樑, 打通了C語言和Java語言之間的障礙;


JNI中的一些概念 : 

-- native : Java語言中修飾本地方法的修飾符, 被該修飾符修飾的方法沒有方法體;

-- Native方法 : 在Java語言中被native關鍵字修飾的方法是Native方法;

-- JNI層 : Java聲明Native方法的部分;

-- JNI函數 : JNIEnv提供的函數, 這些函數在jni.h中進行定義;

-- JNI方法 : Native方法對應的JNI層實現的 C/C++方法, 即在jni目錄中實現的那些C語言代碼;


2. Android中的應用程序框架


正常情況下的Android框架 : 最頂層Android的應用程序代碼, 上層的應用層 和 應用框架層 主要是Java代碼, 中間有一層的Framework框架層代碼是 C/C++代碼, 通過Framework進行系統調用, 調用底層的庫 和linux 內核;



使用JNI時的Android框架 : 繞過Framework提供的調用底層的代碼, 直接調用自己寫的C代碼, 該代碼最終會編譯成爲一個庫, 這個庫通過JNI提供的一個Stable的ABI 調用linux kernel;ABI是二進制程序接口 application binary interface.



紐帶 : JNI是連接框架層 (Framework - C/C++) 和應用框架層(Application Framework - Java)的紐帶;


JNI在Android中作用 : JNI可以調用本地代碼庫(即C/C++代碼), 並通過 Dalvik虛擬機 與應用層 和 應用框架層進行交互, Android中JNI代碼主要位於應用層 和 應用框架層;

-- 應用層 : 該層是由JNI開發, 主要使用標準JNI編程模型;

-- 應用框架層 : 使用的是Android中自定義的一套JNI編程模型, 該自定義的JNI編程模型彌補了標準JNI編程模型的不足;


Android中JNI源碼位置 : 在應用框架層中, 主要的JNI代碼位於 framework/base目錄下, 這些模塊被編譯成共享庫之後放在 /system/lib 目錄下;


NDK與JNI區別 : 

-- NDK: NDK是Google開發的一套開發和編譯工具集, 主要用於Android的JNI開發;

-- JNI : JNI是一套編程接口, 用來實現Java代碼與本地的C/C++代碼進行交互;


JNI編程步驟

-- 聲明native方法 : 在Java代碼中聲明 native method()方法;

-- 實現JNI的C/C++方法 : 在JNI層實現Java中聲明的native方法, 這裏使用javah工具生成帶方法簽名的頭文件, 該JNI層的C/C++代碼將被編譯成動態庫;

-- 加載動態庫 : 在Java代碼中的靜態代碼塊中加載JNI編譯後的動態共享庫;

.


3. JNI作用


JNI作用 : 

-- 擴展: JNI擴展了JVM能力, 驅動開發, 例如開發一個wifi驅動, 可以將手機設置爲無限路由;

-- 高效 : 本地代碼效率高, 遊戲渲染, 音頻視頻處理等方面使用JNI調用本地代碼, C語言可以靈活操作內存;

-- 複用 : 在文件壓縮算法 7zip開源代碼庫, 機器視覺 openCV開放算法庫 等方面可以複用C平臺上的代碼, 不必在開發一套完整的Java體系, 避免重複發明輪子;

-- 特殊 : 產品的核心技術一般也採用JNI開發, 不易破解;


Java語言執行流程 : 

-- 編譯字節碼 : Java編譯器編譯 .java源文件, 獲得.class 字節碼文件;

-- 裝載類庫 : 使用類裝載器裝載平臺上的Java類庫, 並進行字節碼驗證;

-- Java虛擬機 : 將字節碼加入到JVM中, Java解釋器 和 即時編譯器 同時處理字節碼文件, 將處理後的結果放入運行時系統;

-- 調用JVM所在平臺類庫 : JVM處理字節碼後, 轉換成相應平臺的操作, 調用本平臺底層類庫進行相關處理;



Java一次編譯到處執行 : JVM在不同的操作系統都有實現, Java可以一次編譯到處運行, 字節碼文件一旦編譯好了, 可以放在任何平臺的虛擬機上運行;

.


二. NDK詳解


1. 交叉編譯庫文件


C代碼執行 : C代碼被編譯成庫文件之後, 才能執行, 庫文件分爲動態庫 和靜態庫 兩種;

-- 動態庫 : unix環境下.so 後綴的是動態庫, windows環境下.dll 後綴的是動態庫; 動態庫可以依賴靜態庫加載一些可執行的C代碼;

-- 靜態庫 :.a 後綴是靜態庫的擴展名;


庫文件來源 : C代碼 進行 編譯 鏈接操作之後, 纔會生成庫文件, 不同類型的CPU 操作系統 生成的庫文件是不一樣;

-- CPU分類 : arm結構, 嵌入式設備處理器; x86結構, pc 服務器處理器; 不同的CPU指令集不同;

-- 交叉編譯 :windows x86編譯出來的庫文件可以在arm平臺運行的代碼;

-- 交叉編譯工具鏈 : Google提供的 NDK 就是交叉編譯工具鏈, 可以在linux環境下編譯出在arn平臺下執行的二進制庫文件;


NDK作用 : 是Google提供了交叉編譯工具鏈, 能夠在linux平臺編譯出在arm平臺下執行的二進制庫文件;


NDK版本介紹 : android-ndk-windows 是在windows系統中的cygwin使用的, android-ndk-linux 是在linux下使用的;


2. 部署NDK開發環境


(1) 下載Cygwin安裝器


下載地址 : http://cygwin.com/setup-x86.exe , 這是下載器, 可以使用該下載器在線安裝, 也可以將cygwin下載到本地之後, 在進行安裝;


安裝器使用 : Cygwin的下載, 在線安裝, 卸載 等操作都有由該安裝器進行;

-- 本地文件安裝 : 選擇安裝文件所在的目錄, 然後選擇所要安裝的安裝包;

-- 在線安裝 : 選擇在線安裝即可, 然後選擇需要的安裝包;

-- 卸載 : windows上使用其它軟件例如360, 控制面板中是無法卸載Cygwin的, 只能通過安裝器來卸載;


(2) 安裝Cygin


雙擊安裝器 setup-x86.exe 下一步 : 



選擇安裝方式 : 

-- 在線安裝 : 直接下載, 然後安裝;

-- 下載安裝文件 : 將安裝文件下載下來, 可以隨時安裝, 注意安裝文件也需要安裝器來進行安裝;

-- 從本地文件安裝 : 即使用下載的安裝文件進行安裝;



選擇Cygwin安裝位置 : 



選擇下載好安裝文件位置 : 之前我下了一個完全版的Cygwin, 包括了所有的Cygwin組件, 全部加起來有5.23G, 下載速度很快, 使用網易的鏡像, 基本可以全速下載;



選擇需要安裝Cygwin組件 : 這裏我們只需要以下組件 : binutils , gcc , gcc-mingw , gdb , make , 不用下全部的組件;


之後點擊下一步等待完成安裝即可;

.

安裝完之後, 打開bash命令窗口, 可以設置下顯示的字體, 使用 make -version 查看是否安裝成功 : 



(3) Cygwin目錄介紹


以下是Cygwin安裝目錄的情況 : 該安裝目錄就是所模擬的linux 的根目錄;


對應的linux目錄 : 這兩個目錄進行對比發現, 兩個目錄是一樣的, Cygwin的安裝目錄就是 linux根目錄;



cygdrive目錄 : 該目錄是Cygwin模擬出來的windows目錄結構, 進入該目錄後, 會發現windows的盤符目錄, 通過該目錄可以訪問windows中的文件;




(4) 下載NDK工具 


從Google的Android開發者官網上下載該工具, 注意NDK工具分類 : 下載地址 -http://developer.android.com/tools/sdk/ndk/index.html -;

-- windows版本NDK:android-ndk-r9c-windows-x86.zip (32位),android-ndk-r9c-windows-x86_64.zip (64位) 該版本是用在windows上的Cygwin下, 不能直接在windows上直接運行;

-- linux版本NDK :android-ndk-r9c-linux-x86.tar.bz2(32位) , android-ndk-r9c-linux-x86_64.tar.bz2 (64位) , 該版本直接在linux下執行即可;


在這裏下載windows版本的NDK, 運行在Cygwin上;



(4) NDK環境介紹


NDK工具的文件結構 : 



ndk-build腳本 : NDK build 腳本是 gun-make 的簡單封裝, gun-make 是編譯C語言代碼的工具, 該腳本執行的前提是linux環境下必須安裝 make 程序;


NDK安裝在Cygwin中 : 將NDK壓縮文件拷貝到Cygwin的根目錄中, 解壓 : android-ndk-r9c 目錄就是NDK目錄;

執行以下NDK目錄下的 ndk-build 命令 : ./ndk-build ;

執行結果 :

[plain] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <span style="font-family: 'Courier New';">Android NDK: Could not find application project directory !  
  2. Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.  
  3. /android-ndk-r9c/build/core/build-local.mk:148: *** Android NDK: Aborting    。 停止。</span>  



三. 開發第一個NDK程序


1. 開發NDK程序流程


a. 創建Android工程

首選創建一個Android工程, 在這個工程中進行JNI開發;


b. 聲明native方法 : 

注意方法名使用 native 修飾, 沒有方法體 和 參數, eg : public native String helloFromJNI();


c. 創建C文件 : 

在工程根目錄下創建 jni 目錄, 然後創建一個c語言源文件, 在文件中引入 include <jni.h> , C語言方法聲明格式 jstring Java_shuliang.han.ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env) , jstring 是 Java語言中的String類型, 方法名格式爲 : Java_完整包名類名_方法名();

-- JNIEnv參數 : 代表的是Java環境, 通過這個環境可以調用Java裏面的方法;

-- jobject參數 : 調用C語言方法的對象, thiz對象表示當前的對象, 即調用JNI方法所在的類;


d. 編寫Android.mk文件 : 

如何寫 查看文檔, NDK根目錄下有一個 documentation.html 文檔, 點擊該html文件就可以查看文檔, 查看 Android.mk File 文檔, 下面是該文檔給出的 Android.mk示例 : 


[plain] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := hello-jni  
  6. LOCAL_SRC_FILES := hello-jni.c  
  7.   
  8. include $(BUILD_SHARED_LIBRARY)  


-- LOCAL_PATH : 代表mk文件所在的目錄;

-- include $(CLEAR_VARS) : 編譯工具函數, 通過該函數可以進行一些初始化操作;

-- LOCAL_MODULE : 編譯後的 .so 後綴文件叫什麼名字;

-- LOCAL_SRC_FILES: 指定編譯的源文件名稱;

-- include $(BUILD_SHARED_LIBRARY) : 告訴編譯器需要生成動態庫;


e. NDK編譯生成動態庫 : 

進入 cygdrive 找到windows目錄下對應的文件, 編譯完成之後, 會自動生成so文件並放在libs目錄下, 之後就可以在Java中調用C語言方法了;


f. Java中加載動態庫 : 

在Java類中的靜態代碼塊中使用System.LoadLibrary()方法加載編譯好的 .so 動態庫;



NDK平臺版本 : NDK腳本隨着 android-sdk 版本不同, 執行的腳本也是不同的, 不同平臺會引用不同的頭文件, 編譯的時候一定注意 sdk 與 ndk 版本要一致;

so文件在內存中位置 : apk文件安裝到手機上之後, .so動態庫文件存在在 data/安裝目錄/libs 目錄下;


2. 開發實例


按照上面的步驟進行開發


(1) 創建Android工程


Android工程版本 : 創建一個Android工程,minSdk 爲 7 即 android-2.1, 編譯使用的sdk爲 10 即 android-2.3.3 ;
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <uses-sdk  
  2.     android:minSdkVersion="7"  
  3.     android:targetSdkVersion="10" />  


NDK編譯原則 : 編譯NDK動態庫是按照最小版本進行編譯, 選擇編譯的平臺的時候, 會選擇 NDK 7 平臺進行編譯;

      

(2) 聲明native方法


聲明native方法, 注意該方法沒有方法體 和 參數, 如下 :

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. /* 
  2.  * 聲明一個native方法 
  3.  * 這個方法在Java中是沒有實現的, 沒有方法體 
  4.  * 該方法需要使用C語言編寫 
  5.  */  
  6. public native String helloFromJNI();  

.

作者 : 萬境絕塵 

轉載請註明出處 : http://blog.csdn.net/shulianghan/article/details/18964835

.

(3) 創建C文件


引入頭文件: 首先要包含頭文件 jni.h, 該頭文件位置定義在 android-ndk-r9c\platforms\android-5\arch-arm\usr\include目錄下的 jni.h, 下面是該頭文件中定義的一些方法, 包括本項目中使用的 NewString 方法;
  1. jstring     (*NewString)(JNIEnv*, const jchar*, jsize);  
  2. jsize       (*GetStringLength)(JNIEnv*, jstring);  
  3. const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);  
  4. void        (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);  
  5. jstring     (*NewStringUTF)(JNIEnv*, const char*);  
  6. jsize       (*GetStringUTFLength)(JNIEnv*, jstring);  



調用Java類型 : C中調用Java中的String類型爲 jstring;

C語言方法名規則 : Java_完整包名類名_方法名(JNIEnv *env, jobject thiz), 注意完整的類名包名中包名的點要用 _ 代替;

參數介紹 : C語言方法中有兩個重要的參數, JNIEnv *env, jobject thiz ;
-- JNIEnv參數 : 該參數代表Java環境, 通過這個環境可以調用Java中的方法;
-- jobject參數 : 該參數代表調用jni方法的類, 在這裏就是MainActivity;

調用jni.h中的NewStringUTF方法 : 該方法的作用是在C語言中創建一個Java語言中的String類型對象, jni.h中是這樣定義的 jstring (*NewStringUTF)(JNIEnv*, const char*), JNIEnv 結構體中包含了 NewStringUTF 函數指針, 通過 JNIEnv 就可以調用這個方法;

C語言文件源碼 : 
  1. #include <jni.h>  
  2.   
  3. /* 
  4.  * 方法名稱規定 : Java_完整包名類名_方法名() 
  5.  * JNIEnv 指針 
  6.  * 
  7.  * 參數介紹 : 
  8.  * env : 代表Java環境, 通過這個環境可以調用Java中的方法 
  9.  * thiz : 代表調用JNI方法的對象, 即MainActivity對象 
  10.  */  
  11. jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)  
  12. {  
  13.     /* 
  14.      * 調用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法 
  15.      * jni.h 中定義的方法  jstring (*NewStringUTF)(JNIEnv*, const char*);  
  16.      */  
  17.     return (*env)->NewStringUTF(env, "hello world jni");  
  18. }  


(4) 編寫Android.mk文件



查詢NDK文檔 : NDK的文檔在NDK工具根目錄下, 點擊 documentation.html 文件, 就可以在瀏覽器中打開NDK文檔;

上面的開發流程中詳細的介紹了Android.mk 五個參數的詳細用處, 這裏直接給出源碼 : 
[plain] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := hello  
  6. LOCAL_SRC_FILES := hello.c  
  7.   
  8. include $(BUILD_SHARED_LIBRARY)  



(5) 編譯NDK動態庫



進入Cygwin相應目錄 : 從Cygwin中的cygdrive 中進入windows的工程jni目錄 ;


編譯hello.c文件 : 注意Android.mk文件 與 hello.c 文件在同一目錄中;


編譯完成後的情況 : 編譯完之後 會成成一個obj文件, 在obj文件中會生成 libhello.so, 系統會自動將該 so後綴文件放在libs目錄下;



(6) Java中加載動態庫


靜態代碼塊中加載 : Java中在靜態代碼塊中加載庫文件, 調用 System.loadLibrary("hello") 方法,注意 libs中的庫文件名稱爲 libhello.so,我們加載的時候 將 lib 去掉, 只取hello 作爲動態庫名稱, 這是規定的;
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. //靜態代碼塊加載C語言庫文件  
  2. static{  
  3.     System.loadLibrary("hello");  
  4. }  


(7) 其它源碼


MainActivity源碼 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package shuliang.han.ndkhelloworld;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6. import android.widget.Toast;  
  7.   
  8. public class MainActivity extends Activity {  
  9.   
  10.     //靜態代碼塊加載C語言庫文件  
  11.     static{  
  12.         System.loadLibrary("hello");  
  13.     }  
  14.       
  15.     /* 
  16.      * 聲明一個native方法 
  17.      * 這個方法在Java中是沒有實現的, 沒有方法體 
  18.      * 該方法需要使用C語言編寫 
  19.      */  
  20.     public native String helloFromJNI();  
  21.       
  22.     @Override  
  23.     protected void onCreate(Bundle savedInstanceState) {  
  24.         super.onCreate(savedInstanceState);  
  25.         setContentView(R.layout.activity_main);  
  26.         System.out.println(helloFromJNI());  
  27.     }  
  28.   
  29.     public void onClick(View view) {  
  30.         //點擊按鈕顯示從jni調用得到的字符串信息  
  31.         Toast.makeText(getApplicationContext(), helloFromJNI(), 1).show();  
  32.     }  
  33.       
  34. }  

XML佈局文件 : 
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:paddingBottom="@dimen/activity_vertical_margin"  
  6.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  7.     android:paddingRight="@dimen/activity_horizontal_margin"  
  8.     android:paddingTop="@dimen/activity_vertical_margin"  
  9.     tools:context=".MainActivity" >  
  10.   
  11.     <Button  
  12.         android:id="@+id/bt"  
  13.         android:layout_width="wrap_content"  
  14.         android:layout_height="wrap_content"  
  15.         android:onClick="onClick"  
  16.         android:text="顯示JNI返回的字符串" />  
  17.   
  18. </RelativeLayout>  


(8) 將源碼上傳到GitHub中



在上一篇博客 http://blog.csdn.net/shulianghan/article/details/18812279 中對GitHub用法進行了詳解;

在GitHub上創建工程 : 

項目地址 
-- HTTP: https://github.com/han1202012/NDKHelloworld.git 
-- SSH : [email protected]:han1202012/NDKHelloworld.git

生成的命令 : 
[plain] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. touch README.md  
  2. git init  
  3. git add README.md  
  4. git commit -m "first commit"  
  5. git remote add origin [email protected]:han1202012/NDKHelloworld.git  
  6. git push -u origin master  

打開 Git Bash 命令行窗口 : 
-- 從GitHub上克隆項目到本地 : git clone [email protected]:han1202012/NDKHelloworld.git , 注意克隆的時候直接在倉庫根目錄即可, 不用再創建項目根目錄 ;


-- 添加文件 : git add ./* , 將目錄中所有文件添加;

-- 查看狀態 : git status ;

-- 提交緩存 : git commit -m '提交';

-- 提交到遠程GitHub倉庫 : git push -u origin master ;


GitHub項目 : 



3. 項目講解


(1) Android.mk文件講解


Android.mk文件內容 : 

[plain] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := hello  
  6. LOCAL_SRC_FILES := hello.c  
  7.   
  8. include $(BUILD_SHARED_LIBRARY)  

獲取當前文件內容 : $(call my-dir) 是編譯器中的宏方法, 調用該宏方法, 就會返回前的目錄路徑
賦值符號 : " := " 是賦值符號, 第一句話 是 返回當前文件所在的當前目錄, 並將這個目錄路徑賦值給 LOCAL_PATH;
初始化編譯模塊參數 : $(CLEAR_VARS) 作用是將編譯模塊的參數初始化, LOCAL_MODULE LOCAL_SRC_FILES 也是這樣的參數;
指定編譯模塊 : LOCAL_MODULE    := hello , 指定編譯後的 so 文件名稱, 編譯好之後系統會在該名稱前面加上 "lib", 後綴加上 ".so";
指定編譯源文件 : LOCAL_SRC_FILES := hello.c 告訴編譯系統源文件, 如果有多個文件那麼就依次寫在後面即可; 
編譯成靜態庫 : include $(BUILD_SHARED_LIBRARY), 作用是高速系統, 編譯的結果編譯成 .so 後綴的靜態庫;

靜態庫引入 : NDK的platform中有很多 ".a" 結尾的動態庫, 我們編譯動態庫的時候, 可以將一些靜態庫引入進來;


(2) 自動生成方法簽名



使用javah工具 : 在C中實現Java調用的jni方法, 方法的簽名很複雜, 需要將完整的包名類名方法名都要使用 "_" 連接起來, 很麻煩, jdk提供的生成簽名方法的工具;

遺留問題 : 目前查到的方法是 在bin目錄下 執行 javah -jni 包名類名 命令, 但是執行不成功, 暫時沒找到解決方案;
-- Android中會自動生成 .class文件嗎, 沒發現啊, PY人!


解決問題 : 在jni目錄下存在classes目錄, 但是這個目錄在eclipse中不顯示, 這裏我們要注意;


在Cygwin中使用 javah 命令即可 : 

生成的頭文件 : shuliang_han_ndkparameterpassing_DataProvider.h;
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class shuliang_han_ndkparameterpassing_DataProvider */  
  4.   
  5. #ifndef _Included_shuliang_han_ndkparameterpassing_DataProvider  
  6. #define _Included_shuliang_han_ndkparameterpassing_DataProvider  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     shuliang_han_ndkparameterpassing_DataProvider 
  12.  * Method:    add 
  13.  * Signature: (II)I 
  14.  */  
  15. JNIEXPORT jint JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_add  
  16.   (JNIEnv *, jobject, jint, jint);  
  17.   
  18. /* 
  19.  * Class:     shuliang_han_ndkparameterpassing_DataProvider 
  20.  * Method:    sayHelloInc 
  21.  * Signature: (Ljava/lang/String;)Ljava/lang/String; 
  22.  */  
  23. JNIEXPORT jstring JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc  
  24.   (JNIEnv *, jobject, jstring);  
  25.   
  26. /* 
  27.  * Class:     shuliang_han_ndkparameterpassing_DataProvider 
  28.  * Method:    intMethod 
  29.  * Signature: ([I)[I 
  30.  */  
  31. JNIEXPORT jintArray JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod  
  32.   (JNIEnv *, jobject, jintArray);  
  33.   
  34. #ifdef __cplusplus  
  35. }  
  36. #endif  
  37. #endif  

.


(3) NDK開發中亂碼問題


解決亂碼思路 : C語言編譯的時候用的是 ISO-8859-1 碼錶進行編碼, 如果我們使用C語言jni開發, 需要進行轉碼操作;
-- 將ISO-8859-1轉爲UTF-8字符: String string = new String(str.getBytes("iso8859-1"), "UTF-8");


示例 : 

添加中文jni調用 : 將jni中的hello.c 中返回的字符串修改爲中文, 重新編譯 .so 靜態庫文件;
-- 修改後的hello.c文件如下 : 只改變了返回的字符串, 添加了中文;
  1. #include <jni.h>  
  2.   
  3. /* 
  4.  * 方法名稱規定 : Java_完整包名類名_方法名() 
  5.  * JNIEnv 指針 
  6.  * 
  7.  * 參數介紹 : 
  8.  * env : 代表Java環境, 通過這個環境可以調用Java中的方法 
  9.  * thiz : 代表調用JNI方法的對象, 即MainActivity對象 
  10.  */  
  11. jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)  
  12. {  
  13.     /* 
  14.      * 調用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法 
  15.      * jni.h 中定義的方法  jstring (*NewStringUTF)(JNIEnv*, const char*); 
  16.      */  
  17.     return (*env)->NewStringUTF(env, "hello world jni 中文");  
  18. }  

使用NDK重新編譯hello.c文件 : 修改了C源碼之後, 重新將該c文件編譯成so文件;
-- 編譯過程: 打開cygwin, 進入cygdrive/ 下對應windows中源碼項目中的jni目錄, 執行 /android-ndk-r9c/ndk-build 命令;



運行Android代碼報錯 : 因爲jni中c文件有中文, 中文不能被識別;
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. 01-31 14:36:04.803: W/dalvikvm(389): JNI WARNING: illegal continuation byte 0xd0  
  2. 01-31 14:36:04.803: W/dalvikvm(389):              string: 'hello world jni ????'  
  3. 01-31 14:36:04.803: W/dalvikvm(389):              in Lshuliang/han/ndkhelloworld/MainActivity;.helloFromJNI ()Ljava/lang/String; (NewStringUTF)  
  4. 01-31 14:36:04.834: I/dalvikvm(389): "main" prio=5 tid=1 NATIVE  
  5. 01-31 14:36:04.834: I/dalvikvm(389):   | group="main" sCount=0 dsCount=0 obj=0x4001f1a8 self=0xce48  
  6. 01-31 14:36:04.834: I/dalvikvm(389):   | sysTid=389 nice=0 sched=0/0 cgrp=default handle=-1345006528  
  7. 01-31 14:36:04.844: I/dalvikvm(389):   | schedstat=( 257006717 305462830 51 )  
  8. 01-31 14:36:04.844: I/dalvikvm(389):   at shuliang.han.ndkhelloworld.MainActivity.helloFromJNI(Native Method)  
  9. 01-31 14:36:04.844: I/dalvikvm(389):   at shuliang.han.ndkhelloworld.MainActivity.onCreate(MainActivity.java:26)  
  10. 01-31 14:36:04.844: I/dalvikvm(389):   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)  
  11. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)  
  12. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)  
  13. 01-31 14:36:04.853: I/dalvikvm(389):   at android.app.ActivityThread.access$1500(ActivityThread.java:117)  
  14. 01-31 14:36:04.864: I/dalvikvm(389):   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)  
  15. 01-31 14:36:04.864: I/dalvikvm(389):   at android.os.Handler.dispatchMessage(Handler.java:99)  
  16. 01-31 14:36:04.864: I/dalvikvm(389):   at android.os.Looper.loop(Looper.java:123)  
  17. 01-31 14:36:04.864: I/dalvikvm(389):   at android.app.ActivityThread.main(ActivityThread.java:3683)  
  18. 01-31 14:36:04.864: I/dalvikvm(389):   at java.lang.reflect.Method.invokeNative(Native Method)  
  19. 01-31 14:36:04.874: I/dalvikvm(389):   at java.lang.reflect.Method.invoke(Method.java:507)  
  20. 01-31 14:36:04.874: I/dalvikvm(389):   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)  
  21. 01-31 14:36:04.874: I/dalvikvm(389):   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)  
  22. 01-31 14:36:04.874: I/dalvikvm(389):   at dalvik.system.NativeStart.main(Native Method)  
  23. 01-31 14:36:04.884: E/dalvikvm(389): VM aborting  
.


4. JNIEnv 詳解


JNIEnv作用 : JNIEnv 是一個指針,指向了一組JNI函數, 這些函數可以在jni.h中查詢到,通過這些函數可以實現 Java層 與 JNI層的交互 , 通過JNIEnv 調用JNI函數 可以訪問java虛擬機, 操作java對象;

JNI線程相關性 : JNIEnv只在當前的線程有效,JNIEnv不能跨線程傳遞, 相同的Java線程調用本地方法, 所使用的JNIEnv是相同的, 一個Native方法不能被不同的Java線程調用;

JNIEnv結構體系 : JNIEnv指針指向一個線程相關的結構,線程相關結構指向一個指針數組,指針數組中的每個元素最終指向一個JNI函數.

(1) JNIEnv的C/C++聲明


jni.h中聲明JNIEnv : C語言中定義的JNIEnv 是 JNINativeInterface* , C++中定義的JNIEnv 是 _JNIEnv;
  1. struct _JNIEnv;  
  2. struct _JavaVM;  
  3. typedef const struct JNINativeInterface* C_JNIEnv;  
  4.   
  5. #if defined(__cplusplus)    //爲了兼容C 和 C++兩種代碼 使用該 宏加以區分  
  6. typedef _JNIEnv JNIEnv;     //C++ 中的JNIEnv類型  
  7. typedef _JavaVM JavaVM;  
  8. #else  
  9. typedef const struct JNINativeInterface* JNIEnv;//C語言中的JNIEnv類型  
  10. typedef const struct JNIInvokeInterface* JavaVM;  
  11. #endif  

(2) C語言中的JNIEnv


關於JNIEnv指針調用解析 : C中JNIEnv就是 const struct JNINativeInterface*, JNIEnv * env等價於 JNINativeInterface** env, 因此要得到JNINativeInterface結構體中定義的函數指針, 就必須先獲取到 JNINativeInterface的一級指針對象 即 *env , 該一級指針對象就是 JNINativeInterface* env, 然後通過該一級指針對象調用JNI函數 : (*env)->NewStringUTF(env, "hello");

在JNINativeInterface結構體中定義了一系列的關於Java操作的相關方法 : 
  1. /* 
  2.  * Table of interface function pointers. 
  3.  */  
  4. struct JNINativeInterface {  
  5.     void*       reserved0;  
  6.     void*       reserved1;  
  7.       
  8.     ... ...  
  9.       
  10.     jboolean    (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,  
  11.                         va_list);  
  12.     jboolean    (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID,  
  13.                         jvalue*);  
  14.     jbyte       (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);  
  15.     jbyte       (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);  
  16.       
  17.     ... ...  
  18.       
  19.     void*       (*GetDirectBufferAddress)(JNIEnv*, jobject);  
  20.     jlong       (*GetDirectBufferCapacity)(JNIEnv*, jobject);  
  21.   
  22.     /* added in JNI 1.6 */  
  23.     jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);  
  24. };  

(3) C++中的JNIEnv


C++ 中的JNIEnv: C++ 中的JNIEnv 就是 _JNIEnv 結構體, 二者是等同的; 因此在調用 JNI函數的時候, 只需要使用 env->NewStringUTF(env, "hello")方法即可, 不用在進行*運算;

.
  1. /* 
  2.  * C++ object wrapper. 
  3.  * 
  4.  * This is usually overlaid on a C struct whose first element is a 
  5.  * JNINativeInterface*.  We rely somewhat on compiler behavior. 
  6.  */  
  7. struct _JNIEnv {  
  8.     /* do not rename this; it does not seem to be entirely opaque */  
  9.     const struct JNINativeInterface* functions;  
  10.   
  11. #if defined(__cplusplus)  
  12.   
  13.     jint GetVersion()  
  14.     { return functions->GetVersion(this); }  
  15.   
  16.     jlong GetDirectBufferCapacity(jobject buf)  
  17.     { return functions->GetDirectBufferCapacity(this, buf); }  
  18.   
  19.     /* added in JNI 1.6 */  
  20.     jobjectRefType GetObjectRefType(jobject obj)  
  21.     { return functions->GetObjectRefType(this, obj); }  
  22. #endif /*__cplusplus*/  
  23. };  

5. JNI方法命名規則(標準JNI規範)


JNI實現的方法 與 Java中Native方法的映射關係 : 使用方法名進行映射, 可以使用 javah 工具進入 bin/classes 目錄下執行命令, 即可生成頭文件;

JNI方法參數介紹
-- 參數① : 第一個參數是JNI接口指針 JNIEnv;
-- 參數② : 如果Native方法是非靜態的, 那麼第二個參數就是對Java對象的引用, 如果Native方法是靜態的, 那麼第二個參數就是對Java類的Class對象的引用;

JNI方法名規範 : 返回值 + Java前綴 + 全路徑類名 + 方法名 + 參數① JNIEnv + 參數② jobject + 其它參數;
-- 注意分隔符 : Java前綴 與 類名 以及類名之間的包名 和 方法名之間 使用 "_" 進行分割;

聲明 非靜態 方法
-- Native方法 : public int hello (String str, int i); 
-- JNI方法: jint Java_shuliang_han_Hello_hello(JNIEnv * env, jobject obj, jstring str, jint i);

聲明 靜態 方法 : 
-- Native方法 : public static int hello (String str, int i); 
--JNI方法 : jint Java_shuliang_han_Hello_hello(JNIEnv * env, jobject clazz, jstring str, jint i);

兩種規範 : 以上是Java的標準JNI規範, 在Android中還有一套自定義的規範, 該規範是Android應用框架層 和 框架層交互使用的JNI規範, 依靠方法註冊 映射 Native方法 和 JNI方法;

6. JNI方法簽名規則


JNI識別Java方法 : JNI依靠函數名 和 方法簽名 識別方法, 函數名是不能唯一識別一個方法的, 因爲方法可以重載, 類型簽名代表了 參數 和 返回值;
-- 簽名規則 : (參數1類型簽名參數2類型簽名參數3類型簽名參數N類型簽名...)返回值類型簽名, 注意參數列表中沒有任何間隔;

Java類型 與 類型簽名對照表 : 注意 boolean 與 long 不是大寫首字母, 分別是 Z 與 J,  類是L全限定類名, 數組是[元素類型簽名;
-- 類的簽名規則 :L + 全限定名 + ; 三部分, 全限定類名以 / 分割;
Java類型 類型簽名
boolean Z
byte B
char C
short S
int I
long J
float F
double D
L全限定類名
數組 [元素類型簽名


eg. long function(int n, String str, int[] arr);
該方法的簽名 :(ILjava/lang/String;[I)J
.

.

四. Java調用JNI法與日誌打印



1. JNI數據類型



Java數據類型 C數據類型 JNI數據類型對比 : 32位 與 64位機器可能會有出入;

Java數據類型 C本地類型 JNI定義別名
int long jint/jsize
long __int64 jlong
byte signed char jbyte
boolean unsigned char jboolean
char unsigned short jchar
short short jshort
float float jfloat
double doyble jdouble
object'
_jobject jobject

數據類型表示方法 : int數組類型 jintArray , boolean數組 jbooleanArray ...

頭文件定義類型 : 這些基本的數據類型在jni.h 中都有相應的定義 : 
  1. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  2. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);  
  3. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  4. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  5. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);  
  6. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  7. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  8. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  
  9. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  10. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  11. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);  
  12. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  13. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  14. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);  
  15. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  16. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  17. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);  
  18. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  19. jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);  
  20. jbyteArray    (*NewByteArray)(JNIEnv*, jsize);  
  21. jcharArray    (*NewCharArray)(JNIEnv*, jsize);  
  22. jshortArray   (*NewShortArray)(JNIEnv*, jsize);  
  23. jintArray     (*NewIntArray)(JNIEnv*, jsize);  
  24. jlongArray    (*NewLongArray)(JNIEnv*, jsize);  
  25. jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);  
  26. jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);  


2. JNI在Java和C語言之間傳遞int類型



Java中定義的方法 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. //將Java中的兩個int值 傳給C語言, 進行相加後, 返回java語言 shuliang.han.ndkparameterpassing.DataProvider  
  2. public native int add(int x, int y);  

C語言中定義的方法 : 
  1. #include <jni.h>  
  2.   
  3. //方法簽名, Java環境 和 調用native方法的類 必不可少, 後面的參數就是native方法的參數  
  4. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)  
  5. {  
  6.     return x + y;  
  7. }  

使用NDK工具變異該c類庫 : 
在cygwin中進入cygdrive, 然後進入windows中相應的目錄, 執行 /android-ndk-r9c/ndk-build 命令, 即可完成編譯;



3. NDK中C代碼使用LogCat



(1) 引入頭文件


NDK中斷點調試 : 斷點調試在NDK中實現極其困難, 因此在這裏我們一般都是打印日誌;

引入頭文件 : 在C代碼中引入下面的頭文件;
  1. #include <android/log.h>  
  2. #define LOG_TAG "System.out"  
  3. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  4. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  

頭文件介紹 : log.h 是關於調用 LogCat日誌文件;
-- log.h頭文件路徑 : android-ndk-r9c\platforms\android-9\arch-arm\usr\include\android\log.h;
-- 主要方法 :  __android_log_write, 下面有該方法的解析, 傳入參數 日誌等級 日誌標籤 日誌內容;
-- 宏定義 : __android_log_write 方法太麻煩, 這裏做出一個映射, LOGD(...) 輸出debug級別的日誌, LOGI(...) 輸出Info級別的日誌;
--LogCat日誌級別 : verbose < debug < info < warn < error < assert;

使用到的log.h文件內容解析 : __android_log_write 方法中的日誌等級參數就使用 枚舉中的內容 
  1. /* 
  2.  * Android log priority values, in ascending priority order. 日誌等級 
  3.  */  
  4. typedef enum android_LogPriority {  
  5.     ANDROID_LOG_UNKNOWN = 0,  
  6.     ANDROID_LOG_DEFAULT,    /* only for SetMinPriority() */  
  7.     ANDROID_LOG_VERBOSE,  
  8.     ANDROID_LOG_DEBUG,  
  9.     ANDROID_LOG_INFO,  
  10.     ANDROID_LOG_WARN,  
  11.     ANDROID_LOG_ERROR,  
  12.     ANDROID_LOG_FATAL,  
  13.     ANDROID_LOG_SILENT,     /* only for SetMinPriority(); must be last */  
  14. } android_LogPriority;  
  15.   
  16. /* 
  17.  * Send a simple string to the log. 向LogCat中輸出日誌  
  18.     參數介紹: 日誌優先級 , 日誌標籤 , 日誌內容 
  19.  */  
  20. int __android_log_write(int prio, const char *tag, const char *text);  

C語言中輸入輸出函數佔位符介紹 : 
佔位符 數據類型
%d int
%ld long int
%c char
%f float
&lf double
%x 十六進制
%O 八進制
%s 字符串

.
.

(2) Android.mk增加liblog.so動態庫


在該make配置文件中, 增加一行 : LOCAL_LDLIBS += -llog , 該語句添加在 LOCAL_SRC_FILES 語句下面一行;

完整的Android.mk文件 : 
[plain] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := DataProvider  
  6. LOCAL_SRC_FILES := DataProvider.c  
  7. #增加log函數對應的函數庫 liblog.so  libthread_db.a  
  8. LOCAL_LDLIBS += -llog -lthread_db   
  9. include $(BUILD_SHARED_LIBRARY)  

函數庫位置 : android-ndk-r9c\platforms\android-9\arch-arm\usr\lib;
函數庫截圖 : 從該目錄下的 liglog.so可以看出, 存在該庫;

引入函數庫方法 : 使用 LOCAL_LDLIBS += -l函數庫名, 注意函數庫名不帶lib前綴 和.so 後綴, 同時可以添加多個庫, 使用 -l庫1 -l庫2 -庫3 ;


(3) 編譯執行


根據(1) 中的佔位符, 編寫打印日誌代碼
  1. //Java中的int對應的是C語言中的long類型, 對應JNI中的jint類型, C語言中  
  2. LOGI("JNI_日誌 : x = %ld , y = %ld" , x , y);  

最終的包含打印日誌的完整代碼 : 注意, 這裏有一處可能錯誤, 如果是32位機器, int類型佔位符使用 %d 即可;
  1. #include <jni.h>  
  2. #include <android/log.h>  
  3. #define LOG_TAG "System.out"  
  4. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  5. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
  6.   
  7.   
  8. //方法簽名, Java環境 和 調用native方法的類 必不可少, 後面的參數就是native方法的參數  
  9. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)  
  10. {  
  11.     //Java中的int對應的是C語言中的long類型, 對應JNI中的jint類型, C語言中  
  12.     LOGI("JNI_日誌 : x = %ld , y = %ld" , x , y);  
  13.     return x + y;  
  14. }  

重新編譯C文件 : 執行 /android-ndk-r9c/ndk-build命令;
-- 第一次編譯 : 出現警告, long int佔位符行不通, 注意區分機器位長, 64位 與 32位不同, 這樣編譯出現的結果就不會打印日誌;

-- 第二次編譯 : 將佔位符改爲 %d ;


執行按鈕之後打印的日誌 : 雖然有亂碼, 不過顯示出來了;





4. 字符串處理

.

Java中的String轉爲C語言中的char字符串 : 下面的工具方法可以在C程序中解決這個問題;
  1. // java中的jstring, 轉化爲c的一個字符數組  
  2. char* Jstring2CStr(JNIEnv* env, jstring jstr) {  
  3. <span style="white-space:pre">  </span>//聲明瞭一個字符串變量 rtn  
  4. <span style="white-space:pre">  </span>char* rtn = NULL;  
  5. <span style="white-space:pre">  </span>//找到Java中的String的Class對象  
  6. <span style="white-space:pre">  </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");  
  7. <span style="white-space:pre">  </span>//創建一個Java中的字符串 "GB2312"  
  8. <span style="white-space:pre">  </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");  
  9. <span style="white-space:pre">  </span>/* 
  10. <span style="white-space:pre">  </span> * 獲取String中定義的方法 getBytes(), 該方法的參數是 String類型的, 返回值是 byte[]數組 
  11. <span style="white-space:pre">  </span> * "(Ljava/lang/String;)[B" 方法前面解析 : 
  12. <span style="white-space:pre">  </span> * -- Ljava/lang/String; 表示參數是String字符串 
  13. <span style="white-space:pre">  </span> * -- [B : 中括號表示這是一個數組, B代表byte類型, 返回值是一個byte數組 
  14. <span style="white-space:pre">  </span> */  
  15. <span style="white-space:pre">  </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
  16. <span style="white-space:pre">          </span>"(Ljava/lang/String;)[B");  
  17. <span style="white-space:pre">  </span>//調用Java中的getBytes方法, 傳入參數介紹 參數②表示調用該方法的對象, 參數③表示方法id , 參數④表示方法參數  
  18. <span style="white-space:pre">  </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,  
  19. <span style="white-space:pre">          </span>strencode); // String .getByte("GB2312");  
  20. <span style="white-space:pre">  </span>//獲取數組的長度  
  21. <span style="white-space:pre">  </span>jsize alen = (*env)->GetArrayLength(env, barr);  
  22. <span style="white-space:pre">  </span>//獲取數組中的所有的元素 , 存放在 jbyte*數組中  
  23. <span style="white-space:pre">  </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
  24. <span style="white-space:pre">  </span>//將Java數組中所有元素拷貝到C的char*數組中, 注意C語言數組結尾要加一個 '\0'  
  25. <span style="white-space:pre">  </span>if (alen > 0) {  
  26. <span style="white-space:pre">      </span>rtn = (char*) malloc(alen + 1); //new   char[alen+1]; "\0"  
  27. <span style="white-space:pre">      </span>memcpy(rtn, ba, alen);  
  28. <span style="white-space:pre">      </span>rtn[alen] = 0;  
  29. <span style="white-space:pre">  </span>}  
  30. <span style="white-space:pre">  </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //釋放內存  
  31.   
  32.   
  33. <span style="white-space:pre">  </span>return rtn;  
  34. }  

Jstring2CStr方法講解 : 
a. 獲取Java中String類型的class對象 : 參數 : 上下文環境 env, String類完整路徑 ;
  1. jclass clsstring = (*env)->FindClass(env, "java/lang/String");  
b.創建Java字符串 : 使用 NewStringUTF 方法;
  1. jstring strencode = (*env)->NewStringUTF(env, "GB2312");  
c.獲取String中的getBytes()方法 : 參數介紹 ① env 上下文環境 ② 完整的類路徑 ③ 方法名 ④ 方法簽名, 方法簽名 Ljava/lang/String; 代表參數是String字符串, [B  中括號表示這是一個數組, B代表byte類型, 返回值是一個byte數組;
  1. jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
  2.         "(Ljava/lang/String;)[B");  
d. 獲取數組的長度 : 
  1. jsize alen = (*env)->GetArrayLength(env, barr);  
e. 獲取數組元素 : 獲取數組中的所有的元素 , 存放在 jbyte*數組中;
  1. jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
f.數組拷貝: 將Java數組中所有元素拷貝到C的char*數組中, 注意C語言數組結尾要加一個 '\0';
  1. if (alen > 0) {  
  2.     rtn = (char*) malloc(alen + 1); //new   char[alen+1]; "\0"  
  3.     memcpy(rtn, ba, alen);  
  4.     rtn[alen] = 0;  
  5. }  
g.釋放內存 : 
  1. (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //釋放內存  

.

作者 : 萬境絕塵 

轉載請註明出處 : http://blog.csdn.net/shulianghan/article/details/18964835

.



C語言方法 : 注意調用Jstring2CStr方法之後要強轉, 否則會出錯, Jstring2CStr方法要定義在該方法的前面, C語言中的方法要先聲明才能使用;
  1. jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)    
  2. {    
  3.     char *p = (char*)Jstring2CStr(env, str);    
  4.     //打印Java傳遞過來的數據    
  5.     LOGI("Java JNI string parameter is : %s", p);    
  6.         
  7.     char *append = "append";    
  8.         
  9.     //strcat(dest, source) 函數可以將source字符串 添加到dest字符串後面    
  10.     return (*env)->NewStringUTF(env, strcat(p, append));    
  11. }  

-- 如果沒有強轉會出現下面的錯誤 : char *p = Jstring2CStr(env, str);


-- 將Jstring2CStr方法定義在主方法下面會出現下面錯誤 : 


Java源碼 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. case R.id.sayHelloInc:    
  2.     Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();    
  3.     break;    

編譯之後運行結果 : 





5. 開發JNI程序流程


a. C語言類庫接口 : 存在C語言類庫, 調用接口爲login_server(char* address, char* username, char* password);
b. 
Java定義本地方法 : public native void LoginServer(String address, String user, String pwd);
c. C語言JNI代碼 : Java_包名_類名_LoginServer(JNIEnv* env, jobject obj, jstring address, jstring user, jstring pwd){...調C接口};


注意跨語言字符串轉換: JNI方法中, 要將Java的String字符串轉爲C中的char*字符串;


首先驗證C碼農提供的代碼是否可用 : 驗證該api是否可用, 在一個 int main() 函數中進行測試, 根據該測試代碼查看方法執行相關的情況;

6. 數組參數處理


模塊講解 : 在該模塊中, Java語言傳遞一個int數組參數給C語言, C語言將這一組參數讀取出來, 並且輸出到Android的LogCat中, 這裏涉及到了兩個重要的JNI方法, 一個數獲取數組長度方法, 一個是獲取數組中每個元素的方法;


獲取數組長度方法 : jni中定義 - jsize (*GetArrayLength)(JNIEnv*, jarray);
創建數組相關方法 : 
  1. jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);    
  2. jbyteArray    (*NewByteArray)(JNIEnv*, jsize);    
  3. jcharArray    (*NewCharArray)(JNIEnv*, jsize);    
  4. jshortArray   (*NewShortArray)(JNIEnv*, jsize);    
  5. jintArray     (*NewIntArray)(JNIEnv*, jsize);    
  6. jlongArray    (*NewLongArray)(JNIEnv*, jsize);    
  7. jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);    
  8. jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);    

獲取數組元素相關方法 : 
  1. jboolean*   (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);    
  2. jbyte*      (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);    
  3. jchar*      (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);    
  4. jshort*     (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);    
  5. jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);    
  6. jlong*      (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);    
  7. jfloat*     (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);    
  8. jdouble*    (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);   

C語言代碼 : 
  1. jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)    
  2. {    
  3.     //獲取arr大小    
  4.     int len = (*env)->GetArrayLength(env, arr);    
  5.         
  6.     //在LogCat中打印出arr的大小    
  7.     LOGI("the length of array is %d", len);    
  8.         
  9.     //如果長度爲0, 返回arr    
  10.     if(len == 0)    
  11.         return arr;    
  12.             
  13.     //如果長度大於0, 那麼獲取數組中的每個元素    
  14.     jint* p = (*env)->GetIntArrayElements(env, arr, 0);    
  15.         
  16.     //打印出數組中每個元素的值    
  17.     int i = 0;    
  18.     for(; i < len; i ++)    
  19.     {    
  20.         LOGI("arr[%d] = %d", i, *(p + i));    
  21.     }    
  22.         
  23.     return arr;    
  24.         
  25. }    


Java代碼 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. case R.id.intMethod:    
  2.     int[] array = {12345};    
  3.     dataProvider.intMethod(array);    
  4.     break;    

執行結果 : 上面的那種LogCat竟然啓動失敗, 只能將就着用這個了;


7. 本程序源碼


XML佈局文件 : 
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:orientation="vertical"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent" >  
  6.   
  7.     <Button   
  8.         android:id="@+id/add"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:text="調用 add 本地 方法"  
  12.         android:onClick="onClick"/>  
  13.       
  14.     <Button   
  15.         android:id="@+id/sayHelloInc"  
  16.         android:layout_width="wrap_content"  
  17.         android:layout_height="wrap_content"  
  18.         android:text="調用 sayHelloInc 本地 方法"  
  19.         android:onClick="onClick"/>  
  20.       
  21.     <Button   
  22.         android:id="@+id/intMethod"  
  23.         android:layout_width="wrap_content"  
  24.         android:layout_height="wrap_content"  
  25.         android:text="調用 intMethod 本地 方法"  
  26.         android:onClick="onClick"/>  
  27.   
  28. </LinearLayout>  

Java源碼 : 
-- MainActivity源碼 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package shuliang.han.ndkparameterpassing;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6. import android.widget.Toast;  
  7.   
  8. public class MainActivity extends Activity {  
  9.   
  10.     static{  
  11.         System.loadLibrary("DataProvider");  
  12.     }  
  13.       
  14.     DataProvider dataProvider;  
  15.     @Override  
  16.     public void onCreate(Bundle savedInstanceState) {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.activity_main);  
  19.         dataProvider = new DataProvider();  
  20.     }  
  21.   
  22.     public void onClick(View view) {  
  23.           
  24.         int id = view.getId();  
  25.           
  26.         switch (id) {  
  27.             case R.id.add:  
  28.                 int result = dataProvider.add(12);  
  29.                 Toast.makeText(getApplicationContext(), "the add result : " + result, Toast.LENGTH_LONG).show();  
  30.                 break;  
  31.                   
  32.             case R.id.sayHelloInc:  
  33.                 Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();  
  34.                 break;  
  35.                   
  36.             case R.id.intMethod:  
  37.                 int[] array = {12345};  
  38.                 dataProvider.intMethod(array);  
  39.                 break;  
  40.       
  41.             default:  
  42.                 break;  
  43.         }  
  44.     }  
  45.       
  46. }  
--DataProvider源碼 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package shuliang.han.ndkparameterpassing;  
  2.   
  3. public class DataProvider {  
  4.   
  5.     //將Java中的兩個int值 傳給C語言, 進行相加後, 返回java語言 shuliang.han.ndkparameterpassing.DataProvider  
  6.     public native int add(int x, int y);  
  7.       
  8.     //將Java字符串傳遞給C語言, C語言處理字符串之後, 將處理結果返回給java  
  9.     public native String sayHelloInc(String s);  
  10.       
  11.     //將java中的int數組傳遞給C語言, C語言爲每個元素加10, 返回給Java  
  12.     public native int[] intMethod(int[] nums);   
  13.       
  14. }  

JNI相關源碼 : 
-- Android.mk源碼 : 
[plain] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := DataProvider  
  6. LOCAL_SRC_FILES := DataProvider.c  
  7. #增加log函數對應的log庫  
  8. LOCAL_LDLIBS += -llog   
  9.   
  10. include $(BUILD_SHARED_LIBRARY)  
--DataProvider.c 主程序源碼 : 
  1. #include <jni.h>  
  2. #include <string.h>  
  3. #include <android/log.h>  
  4. #define LOG_TAG "System.out"  
  5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
  7.   
  8. // java中的jstring, 轉化爲c的一個字符數組  
  9. char* Jstring2CStr(JNIEnv* env, jstring jstr) {  
  10. <span style="white-space:pre">  </span>//聲明瞭一個字符串變量 rtn  
  11. <span style="white-space:pre">  </span>char* rtn = NULL;  
  12. <span style="white-space:pre">  </span>//找到Java中的String的Class對象  
  13. <span style="white-space:pre">  </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");  
  14. <span style="white-space:pre">  </span>//創建一個Java中的字符串 "GB2312"  
  15. <span style="white-space:pre">  </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");  
  16. <span style="white-space:pre">  </span>/* 
  17. <span style="white-space:pre">  </span> * 獲取String中定義的方法 getBytes(), 該方法的參數是 String類型的, 返回值是 byte[]數組 
  18. <span style="white-space:pre">  </span> * "(Ljava/lang/String;)[B" 方法前面解析 : 
  19. <span style="white-space:pre">  </span> * -- Ljava/lang/String; 表示參數是String字符串 
  20. <span style="white-space:pre">  </span> * -- [B : 中括號表示這是一個數組, B代表byte類型, 返回值是一個byte數組 
  21. <span style="white-space:pre">  </span> */  
  22. <span style="white-space:pre">  </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
  23. <span style="white-space:pre">          </span>"(Ljava/lang/String;)[B");  
  24. <span style="white-space:pre">  </span>//調用Java中的getBytes方法, 傳入參數介紹 參數②表示調用該方法的對象, 參數③表示方法id , 參數④表示方法參數  
  25. <span style="white-space:pre">  </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,  
  26. <span style="white-space:pre">          </span>strencode); // String .getByte("GB2312");  
  27. <span style="white-space:pre">  </span>//獲取數組的長度  
  28. <span style="white-space:pre">  </span>jsize alen = (*env)->GetArrayLength(env, barr);  
  29. <span style="white-space:pre">  </span>//獲取數組中的所有的元素 , 存放在 jbyte*數組中  
  30. <span style="white-space:pre">  </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
  31. <span style="white-space:pre">  </span>//將Java數組中所有元素拷貝到C的char*數組中, 注意C語言數組結尾要加一個 '\0'  
  32. <span style="white-space:pre">  </span>if (alen > 0) {  
  33. <span style="white-space:pre">      </span>rtn = (char*) malloc(alen + 1); //new   char[alen+1]; "\0"  
  34. <span style="white-space:pre">      </span>memcpy(rtn, ba, alen);  
  35. <span style="white-space:pre">      </span>rtn[alen] = 0;  
  36. <span style="white-space:pre">  </span>}  
  37. <span style="white-space:pre">  </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //釋放內存  
  38.   
  39.   
  40. <span style="white-space:pre">  </span>return rtn;  
  41. }  
  42.   
  43. //方法簽名, Java環境 和 調用native方法的類 必不可少, 後面的參數就是native方法的參數  
  44. jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)  
  45. {  
  46.     //Java中的int對應的是C語言中的long類型, 對應JNI中的jint類型, C語言中  
  47.     LOGI("JNI_log : x = %d , y = %d" , x , y);  
  48.     return x + y;  
  49. }  
  50.   
  51. jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)  
  52. {  
  53.     char *p = (char*)Jstring2CStr(env, str);  
  54.     //打印Java傳遞過來的數據  
  55.     LOGI("Java JNI string parameter is : %s", p);  
  56.       
  57.     char *append = "append";  
  58.       
  59.     //strcat(dest, source) 函數可以將source字符串 添加到dest字符串後面  
  60.     return (*env)->NewStringUTF(env, strcat(p, append));  
  61. }  
  62.   
  63. jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)  
  64. {  
  65.     //獲取arr大小  
  66.     int len = (*env)->GetArrayLength(env, arr);  
  67.       
  68.     //在LogCat中打印出arr的大小  
  69.     LOGI("the length of array is %d", len);  
  70.       
  71.     //如果長度爲0, 返回arr  
  72.     if(len == 0)  
  73.         return arr;  
  74.           
  75.     //如果長度大於0, 那麼獲取數組中的每個元素  
  76.     jint* p = (*env)->GetIntArrayElements(env, arr, 0);  
  77.       
  78.     //打印出數組中每個元素的值  
  79.     int i = 0;  
  80.     for(; i < len; i ++)  
  81.     {  
  82.         LOGI("arr[%d] = %d", i, *(p + i));  
  83.     }  
  84.       
  85.     return arr;  
  86.       
  87. }  


.



8. 上傳代碼到GitHub



創建新項目 : han1202012/NDKParameterPassing ;
-- SSH地址 : [email protected]:han1202012/NDKParameterPassing.git ;
-- HTTP地址 : https://github.com/han1202012/NDKParameterPassing.git ;


五. C語言代碼回調Java方法

.

C語言回調Java方法場景 : 
-- 複用方法 : 使用Java對象, 複用Java中的方法;
-- 激活Java : C程序後臺運行, 該後臺程序一直運行, 某個時間出發後需要啓動Java服務, 激活Android中的某個界面, 例如使用Intent啓動一個Activity;


1. C代碼回調Java方法的流程


(1) 找到java對應的Class


創建一個char*數組, 然後使用jni.h中提供的FindClass方法獲取jclass返回值;
  1. //DataProvider完整類名 shulaing.han.ndk_callback.DataProvider  
  2. char* classname = "shulaing/han/ndk_callback/DataProvider";  
  3.   
  4.   
  5. jclass dpclazz = (*env)->FindClass(env, classname);  

(2) 找到要調用的方法的methodID


使用jni.h中提供的GetMethodID方法, 獲取jmethodID, 傳入參數 ①JNIEnv指針 ②Class對象 ③ 方法名 ④方法簽名, 在這裏方法名和方法簽名確定一個方法, 方法簽名就是方法的返回值 與 參數的唯一標示;
  1. //參數介紹 : 第二個參數是Class對象, 第三個參數是方法名,第四個參數是方法的簽名, 獲取到調用的method  
  2. jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add""(II)I");  

找到靜態方法 : 如果方法是靜態的, 就使用GetStaticMethod方法獲取 
  1. jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)  
  2.     { return functions->GetStaticMethodID(this, clazz, name, sig); }  



(3) 在C語言中調用相應方法


普通方法 : CallTypeMethod , 其中的Type隨着返回值類型的不同而改變;
參數介紹 : ① JNIEnv指針 ②調用該native方法的對象 ③方法的methodID ④⑤... 後面是可變參數, 這些參數是
  1. jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);  
  2.   
  3. jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);  
  4. jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  5. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  6. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);  
  7. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  8. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  9. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);  
  10. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  11. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  12. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  
  13. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  14. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  15. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);  
  16. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  17. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  18. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);  
  19. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  20. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  21. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);  
  22. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  23. jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  24. jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
  25. jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
  26. jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
  27. jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
  28. jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
  29. jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
  30. void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);  
  31. void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  32. void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  

靜態方法 : CallStaticTypeMethod, 其中的Type隨着返回值類型不同而改變;



.

2. 一些基本代碼編寫


Java代碼 : 定義一個callCcode本地方法, 以及三個Java方法, 在jni中使用本地方法調用Java中的方法;
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package shulaing.han.ndk_callback;  
  2.   
  3. public class DataProvider {  
  4.   
  5.     public native void callCcode();  
  6.       
  7.     //C調用java中空方法 shulaing.han.ndk_callback.DataProvider  
  8.   
  9.     public void helloFromJava(){  
  10.         System.out.println("hello from java");  
  11.     }  
  12.       
  13.     //C調用java中的帶兩個int參數的方法  
  14.     public int Add(int x,int y){  
  15.         return x + y;  
  16.     }  
  17.       
  18.     //C調用java中參數爲string的方法  
  19.     public void printString(String s){  
  20.         System.out.println(s);  
  21.     }  
  22.       
  23. }  

生成頭文件 : 進入 bin/classed目錄, 使用 javah shulaing.han.ndk_callback.DataProvider 命令, 可以在bin/classes下生成頭文件;


頭文件內容 : 文件名 : shulaing_han_ndk_callback_DataProvider.h ;
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class shulaing_han_ndk_callback_DataProvider */  
  4.   
  5. #ifndef _Included_shulaing_han_ndk_callback_DataProvider  
  6. #define _Included_shulaing_han_ndk_callback_DataProvider  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     shulaing_han_ndk_callback_DataProvider 
  12.  * Method:    callCcode 
  13.  * Signature: ()V 
  14.  */  
  15. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
  16.   (JNIEnv *, jobject);  
  17.   
  18. #ifdef __cplusplus  
  19. }  
  20. #endif  
  21. #endif  

編寫Android.mk文件 : 注意將LogCat日誌輸出系統動態庫加入;
[plain] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := jni  
  6. LOCAL_SRC_FILES := jni.c  
  7. #增加log函數對應的log庫  
  8. LOCAL_LDLIBS += -llog   
  9.   
  10. include $(BUILD_SHARED_LIBRARY)  

編寫jni的C代碼 : 注意加入LogCat相關導入的包;
  1. #include "shulaing_han_ndk_callback_DataProvider.h"  
  2. #include <string.h>  
  3. #include <android/log.h>  
  4. #define LOG_TAG "System.out"  
  5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  


3. C中回調Java的void返回值方法


使用JNIEnv指針獲取Class對象 : 在jni.h文件中找到 - jclass (*FindClass)(JNIEnv*, const char*);
-- 參數介紹 : 第二個參數是類的路徑字符串, 如 "/shuliang/han/ndk_callback/DataProvider" ;

獲取Java類中定義的method方法 : 在jni.h中找到方法 - jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
-- 參數介紹 : 第二個參數是 Java類的Class對象, 第三個參數是方法名, 第四個參數是Java方法的簽名;

方法簽名生成工具 : javap , 使用javap -s 命令即可生成方法簽名;


進入bin/classed目錄下 : 執行 javap -s shulaing.han.ndk_callback.DataProvider 命令, 即可顯示出每個方法的簽名;
[plain] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. $ javap -s shulaing.han.ndk_callback.DataProvider  
  2. Compiled from "DataProvider.java"  
  3. public class shulaing.han.ndk_callback.DataProvider extends java.lang.Object{  
  4. public shulaing.han.ndk_callback.DataProvider();  
  5.   Signature: ()V  
  6. public native void callCcode();  
  7.   Signature: ()V  
  8. public void helloFromJava();  
  9.   Signature: ()V  
  10. public int Add(int, int);  
  11.   Signature: (II)I  
  12. public void printString(java.lang.String);  
  13.   Signature: (Ljava/lang/String;)V  
  14. }  
截圖 : 



方法簽名介紹 : 
-- 返回值null, 參數null : void helloFromJava() 方法的簽名是 "()V", 括號裏什麼都沒有代表參數爲null, V代表返回值是void;
-- 返回值int, 參數兩個int : int Add(int x,int y) 方法的簽名是 "(II)I", 括號中II表示兩個int類型參數, 右邊括號外的I代表返回值是int類型;
-- 返回值null, 參數String : void printString(String s) 方法簽名是 "(Ljava/lang/String;)V", 括號中的Ljava/lang/String; 表示參數是String類型, V表示返回值是void;

jni.h中定義的回調Java方法的相關函數 : 
  1. jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);  
  2.   
  3. jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);  
  4. jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  5. jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  6. jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);  
  7. jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  8. jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  9. jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);  
  10. jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  11. jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  12. jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  
  13. jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  14. jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  15. jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);  
  16. jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  17. jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  18. jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);  
  19. jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  20. jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  21. jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);  
  22. jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  23. jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
  24. jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
  25. jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
  26. jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
  27. jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;  
  28. jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;  
  29. jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;  
  30. void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);  
  31. void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
  32. void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  

C語言代碼 : 
  1. #include "shulaing_han_ndk_callback_DataProvider.h"  
  2. #include <string.h>  
  3. #include <android/log.h>  
  4. #define LOG_TAG "System.out"  
  5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  6. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
  7.   
  8. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
  9.   (JNIEnv * env, jobject obj)  
  10. {  
  11.     //調用DataProvider對象中的helloFromJava()方法  
  12.     //獲取到某個對象, 獲取對象中的方法, 調用獲取到的方法  
  13.     LOGI("in code");  
  14.     //DataProvider完整類名 shulaing.han.ndk_callback.DataProvider  
  15.     char* classname = "shulaing/han/ndk_callback/DataProvider";  
  16.   
  17.   
  18.     jclass dpclazz = (*env)->FindClass(env, classname);  
  19.     if(dpclazz == 0)  
  20.         LOGI("class not find !!!");  
  21.     else  
  22.         LOGI("class find !!!");  
  23.   
  24.     //參數介紹 : 第二個參數是Class對象, 第三個參數是方法名,第四個參數是方法的簽名, 獲取到調用的method  
  25.     jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava""()V");  
  26.     if(methodID == 0)  
  27.             LOGI("method not find !!!");  
  28.         else  
  29.             LOGI("method find !!!");  
  30.   
  31.     /* 
  32.      * 調用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  33.      * 參數介紹 : 後面的 ... 是可變參數, 如果該返回值void的方法有參數, 就將參數按照次序排列 
  34.      */  
  35.     LOGI("before call method");  
  36.     (*env)->CallVoidMethod(env, obj, methodID);  
  37.     LOGI("after call method");  
  38.   
  39. }  

Java代碼 : 
--XML佈局文件代碼 : 
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical"  
  6.     android:paddingBottom="@dimen/activity_vertical_margin"  
  7.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  8.     android:paddingRight="@dimen/activity_horizontal_margin"  
  9.     android:paddingTop="@dimen/activity_vertical_margin"  
  10.     tools:context=".MainActivity" >  
  11.   
  12.     <Button  
  13.         android:id="@+id/call_void_method"  
  14.         android:layout_width="wrap_content"  
  15.         android:layout_height="wrap_content"  
  16.         android:onClick="onClick"  
  17.         android:text="C語言回調Java中的空方法" />  
  18.   
  19. </LinearLayout>  
--MainActivity代碼 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package shulaing.han.ndk_callback;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6.   
  7. public class MainActivity extends Activity {  
  8.   
  9.     static{  
  10.         System.loadLibrary("jni");  
  11.     }  
  12.     DataProvider dp;  
  13.       
  14.     @Override  
  15.     protected void onCreate(Bundle savedInstanceState) {  
  16.         super.onCreate(savedInstanceState);  
  17.         setContentView(R.layout.activity_main);  
  18.         dp = new DataProvider();  
  19.     }  
  20.   
  21.     public void onClick(View view) {  
  22.         int id = view.getId();  
  23.         switch (id) {  
  24.             case R.id.call_void_method:  
  25.                 dp.callCcode();  
  26.                 break;  
  27.       
  28.             default:  
  29.                 break;  
  30.         }  
  31.     }  
  32.   
  33. }  

執行結果 : 


.

4. C代碼回調Java中帶String參數的方法


在DataProvider中添加兩個native方法 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public native void callCcode();  
  2. public native void callCcode1();  
  3. public native void callCcode2();  

進入bin/classes目錄, 使用 javah -jni shulaing.han.ndk_callback.DataProvider 命令生成頭文件 : 
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class shulaing_han_ndk_callback_DataProvider */  
  4.   
  5. #ifndef _Included_shulaing_han_ndk_callback_DataProvider  
  6. #define _Included_shulaing_han_ndk_callback_DataProvider  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     shulaing_han_ndk_callback_DataProvider 
  12.  * Method:    callCcode 
  13.  * Signature: ()V 
  14.  */  
  15. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
  16.   (JNIEnv *, jobject);  
  17.   
  18. /* 
  19.  * Class:     shulaing_han_ndk_callback_DataProvider 
  20.  * Method:    callCcode1 
  21.  * Signature: ()V 
  22.  */  
  23. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
  24.   (JNIEnv *, jobject);  
  25.   
  26. /* 
  27.  * Class:     shulaing_han_ndk_callback_DataProvider 
  28.  * Method:    callCcode2 
  29.  * Signature: ()V 
  30.  */  
  31. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
  32.   (JNIEnv *, jobject);  
  33.   
  34. #ifdef __cplusplus  
  35. }  
  36. #endif  
  37. #endif  

jni C語言代碼 : 這裏只需要修改兩處, 方法名, 獲取方法id中的參數, 調用方法中最後加上一個Java參數;
  1. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
  2.   (JNIEnv *env, jobject obj)  
  3. {  
  4.     //調用DataProvider對象中的helloFromJava()方法  
  5.         //獲取到某個對象, 獲取對象中的方法, 調用獲取到的方法  
  6.         LOGI("in code");  
  7.         //DataProvider完整類名 shulaing.han.ndk_callback.DataProvider  
  8.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
  9.   
  10.   
  11.         jclass dpclazz = (*env)->FindClass(env, classname);  
  12.         if(dpclazz == 0)  
  13.             LOGI("class not find !!!");  
  14.         else  
  15.             LOGI("class find !!!");  
  16.   
  17.         //參數介紹 : 第二個參數是Class對象, 第三個參數是方法名,第四個參數是方法的簽名, 獲取到調用的method  
  18.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString""(Ljava/lang/String;)V");  
  19.         if(methodID == 0)  
  20.                 LOGI("method not find !!!");  
  21.             else  
  22.                 LOGI("method find !!!");  
  23.   
  24.         /* 
  25.          * 調用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  26.          * 參數介紹 : 後面的 ... 是可變參數, 如果該返回值void的方法有參數, 就將參數按照次序排列 
  27.          */  
  28.         LOGI("before call method");  
  29.         (*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));  
  30.         LOGI("after call method");  
  31. }  

執行後的結果 : 


5. C代碼中回調帶兩個int類型的參數的方法


按照上面的流程, 不同之處就是jni中獲取方法 和 方法id , 調用方法的jni函數不同 : 
  1. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
  2.   (JNIEnv *env, jobject obj)  
  3. {  
  4.     //調用DataProvider對象中的helloFromJava()方法  
  5.         //獲取到某個對象, 獲取對象中的方法, 調用獲取到的方法  
  6.         LOGI("in code");  
  7.         //DataProvider完整類名 shulaing.han.ndk_callback.DataProvider  
  8.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
  9.   
  10.   
  11.         jclass dpclazz = (*env)->FindClass(env, classname);  
  12.         if(dpclazz == 0)  
  13.             LOGI("class not find !!!");  
  14.         else  
  15.             LOGI("class find !!!");  
  16.   
  17.         //參數介紹 : 第二個參數是Class對象, 第三個參數是方法名,第四個參數是方法的簽名, 獲取到調用的method  
  18.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add""(II)I");  
  19.         if(methodID == 0)  
  20.                 LOGI("method not find !!!");  
  21.             else  
  22.                 LOGI("method find !!!");  
  23.   
  24.         /* 
  25.          * 調用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  26.          * 參數介紹 : 後面的 ... 是可變參數, 如果該返回值void的方法有參數, 就將參數按照次序排列 
  27.          */  
  28.         LOGI("before call method");  
  29.         (*env)->CallIntMethod(env, obj, methodID, 3, 5);  
  30.         LOGI("after call method");  
  31.   
  32. }  

Java代碼 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. case R.id.call_int_parameter_method:  
  2.     dp.callCcode2();  
  3.     break;  
執行結果 : 

.

作者 : 萬境絕塵 

轉載請註明出處 : http://blog.csdn.net/shulianghan/article/details/18964835

.


6. 完整源碼 


Java源碼 : 
-- DataProvider源碼 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package shulaing.han.ndk_callback;  
  2.   
  3.   
  4. public class DataProvider {  
  5.   
  6.     public native void callCcode();  
  7.     public native void callCcode1();  
  8.     public native void callCcode2();  
  9.       
  10.     //C調用java中空方法 shulaing.han.ndk_callback.DataProvider  
  11.   
  12.     public void helloFromJava(){  
  13.         System.out.println("hello from java");  
  14.     }  
  15.       
  16.     //C調用java中的帶兩個int參數的方法  
  17.     public int Add(int x,int y){  
  18.         System.out.println("the add result is : " + (x + y));  
  19.         return x + y;  
  20.     }  
  21.       
  22.     //C調用java中參數爲string的方法  
  23.     public void printString(String s){  
  24.         System.out.println("in java code :" + s);  
  25.     }  
  26.       
  27. }  
-- MainActivity源碼 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package shulaing.han.ndk_callback;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.View;  
  6.   
  7. public class MainActivity extends Activity {  
  8.   
  9.     static{  
  10.         System.loadLibrary("jni");  
  11.     }  
  12.     DataProvider dp;  
  13.       
  14.     @Override  
  15.     protected void onCreate(Bundle savedInstanceState) {  
  16.         super.onCreate(savedInstanceState);  
  17.         setContentView(R.layout.activity_main);  
  18.         dp = new DataProvider();  
  19.     }  
  20.   
  21.     public void onClick(View view) {  
  22.         int id = view.getId();  
  23.         switch (id) {  
  24.             case R.id.call_void_method:  
  25.                 dp.callCcode();  
  26.                 break;  
  27.               
  28.             case R.id.call_string_parameter_method:  
  29.                 dp.callCcode1();  
  30.                 break;  
  31.       
  32.             case R.id.call_int_parameter_method:  
  33.                 dp.callCcode2();  
  34.                 break;  
  35.                   
  36.             default:  
  37.                 break;  
  38.         }  
  39.     }  
  40.   
  41. }  

XML佈局文件源碼 : 
[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical"  
  6.     android:paddingBottom="@dimen/activity_vertical_margin"  
  7.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  8.     android:paddingRight="@dimen/activity_horizontal_margin"  
  9.     android:paddingTop="@dimen/activity_vertical_margin"  
  10.     tools:context=".MainActivity" >  
  11.   
  12.     <Button  
  13.         android:id="@+id/call_void_method"  
  14.         android:layout_width="wrap_content"  
  15.         android:layout_height="wrap_content"  
  16.         android:onClick="onClick"  
  17.         android:text="C語言回調Java中的空方法" />  
  18.       
  19.     <Button  
  20.         android:id="@+id/call_string_parameter_method"  
  21.         android:layout_width="wrap_content"  
  22.         android:layout_height="wrap_content"  
  23.         android:onClick="onClick"  
  24.         android:text="C語言回調Java中的String參數方法" />  
  25.       
  26.     <Button  
  27.         android:id="@+id/call_int_parameter_method"  
  28.         android:layout_width="wrap_content"  
  29.         android:layout_height="wrap_content"  
  30.         android:onClick="onClick"  
  31.         android:text="C語言回調Java中的int參數方法" />  
  32.   
  33. </LinearLayout>  


jni源碼 : 
-- 頭文件源碼 : 
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class shulaing_han_ndk_callback_DataProvider */  
  4.   
  5. #ifndef _Included_shulaing_han_ndk_callback_DataProvider  
  6. #define _Included_shulaing_han_ndk_callback_DataProvider  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     shulaing_han_ndk_callback_DataProvider 
  12.  * Method:    callCcode 
  13.  * Signature: ()V 
  14.  */  
  15. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
  16.   (JNIEnv *, jobject);  
  17.   
  18. /* 
  19.  * Class:     shulaing_han_ndk_callback_DataProvider 
  20.  * Method:    callCcode1 
  21.  * Signature: ()V 
  22.  */  
  23. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
  24.   (JNIEnv *, jobject);  
  25.   
  26. /* 
  27.  * Class:     shulaing_han_ndk_callback_DataProvider 
  28.  * Method:    callCcode2 
  29.  * Signature: ()V 
  30.  */  
  31. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
  32.   (JNIEnv *, jobject);  
  33.   
  34. #ifdef __cplusplus  
  35. }  
  36. #endif  
  37. #endif  
-- Android.mk源碼 :
[plain] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE    := jni  
  6. LOCAL_SRC_FILES := jni.c  
  7. #增加log函數對應的log庫  
  8. LOCAL_LDLIBS += -llog   
  9.   
  10. include $(BUILD_SHARED_LIBRARY)  
-- jni主程序源碼 :  
  1. #include "shulaing_han_ndk_callback_DataProvider.h"  
  2. #include "first.h"  
  3. #include <string.h>  
  4. #include <android/log.h>  
  5. #define LOG_TAG "System.out"  
  6. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  7. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
  8.   
  9. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode  
  10.   (JNIEnv * env, jobject obj)  
  11. {  
  12.     //調用DataProvider對象中的helloFromJava()方法  
  13.     //獲取到某個對象, 獲取對象中的方法, 調用獲取到的方法  
  14.     LOGI("in code");  
  15.     //DataProvider完整類名 shulaing.han.ndk_callback.DataProvider  
  16.     char* classname = "shulaing/han/ndk_callback/DataProvider";  
  17.   
  18.   
  19.     jclass dpclazz = (*env)->FindClass(env, classname);  
  20.     if(dpclazz == 0)  
  21.         LOGI("class not find !!!");  
  22.     else  
  23.         LOGI("class find !!!");  
  24.   
  25.     //參數介紹 : 第二個參數是Class對象, 第三個參數是方法名,第四個參數是方法的簽名, 獲取到調用的method  
  26.     jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava""()V");  
  27.     if(methodID == 0)  
  28.             LOGI("method not find !!!");  
  29.         else  
  30.             LOGI("method find !!!");  
  31.   
  32.     /* 
  33.      * 調用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  34.      * 參數介紹 : 後面的 ... 是可變參數, 如果該返回值void的方法有參數, 就將參數按照次序排列 
  35.      */  
  36.     LOGI("before call method");  
  37.     (*env)->CallVoidMethod(env, obj, methodID);  
  38.     LOGI("after call method");  
  39.   
  40. }  
  41.   
  42. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1  
  43.   (JNIEnv *env, jobject obj)  
  44. {  
  45.     //調用DataProvider對象中的helloFromJava()方法  
  46.         //獲取到某個對象, 獲取對象中的方法, 調用獲取到的方法  
  47.         LOGI("in code");  
  48.         //DataProvider完整類名 shulaing.han.ndk_callback.DataProvider  
  49.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
  50.   
  51.   
  52.         jclass dpclazz = (*env)->FindClass(env, classname);  
  53.         if(dpclazz == 0)  
  54.             LOGI("class not find !!!");  
  55.         else  
  56.             LOGI("class find !!!");  
  57.   
  58.         //參數介紹 : 第二個參數是Class對象, 第三個參數是方法名,第四個參數是方法的簽名, 獲取到調用的method  
  59.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString""(Ljava/lang/String;)V");  
  60.         if(methodID == 0)  
  61.                 LOGI("method not find !!!");  
  62.             else  
  63.                 LOGI("method find !!!");  
  64.   
  65.         /* 
  66.          * 調用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  67.          * 參數介紹 : 後面的 ... 是可變參數, 如果該返回值void的方法有參數, 就將參數按照次序排列 
  68.          */  
  69.         LOGI("before call method");  
  70.         (*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));  
  71.         LOGI("after call method");  
  72. }  
  73.   
  74. /* 
  75.  * 實際開發的情況 
  76.  * C代碼工程師給我們 first.h first.c , 我們只需要將first.h引入, 然後就可以使用其中的方法了 
  77.  */  
  78. JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2  
  79.   (JNIEnv *env, jobject obj)  
  80. {  
  81.     //調用DataProvider對象中的helloFromJava()方法  
  82.         //獲取到某個對象, 獲取對象中的方法, 調用獲取到的方法  
  83.         LOGI("in code");  
  84.         //DataProvider完整類名 shulaing.han.ndk_callback.DataProvider  
  85.         char* classname = "shulaing/han/ndk_callback/DataProvider";  
  86.   
  87.   
  88.         jclass dpclazz = (*env)->FindClass(env, classname);  
  89.         if(dpclazz == 0)  
  90.             LOGI("class not find !!!");  
  91.         else  
  92.             LOGI("class find !!!");  
  93.   
  94.         //參數介紹 : 第二個參數是Class對象, 第三個參數是方法名,第四個參數是方法的簽名, 獲取到調用的method  
  95.         jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add""(II)I");  
  96.         if(methodID == 0)  
  97.                 LOGI("method not find !!!");  
  98.             else  
  99.                 LOGI("method find !!!");  
  100.   
  101.         /* 
  102.          * 調用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 
  103.          * 參數介紹 : 後面的 ... 是可變參數, 如果該返回值void的方法有參數, 就將參數按照次序排列 
  104.          */  
  105.         LOGI("before call method");  
  106.         (*env)->CallIntMethod(env, obj, methodID, 3, 5);  
  107.         LOGI("after call method");  
  108.   
  109. }  

7. 將程序上傳到GitHub中

GitHub地址 : 
-- SSH : [email protected]:han1202012/NDK_Callback.git
-- HTTP : https://github.com/han1202012/NDK_Callback.git
.


.

六. 實際開發中的環境


這裏舉一個簡單的小例子 : 
-- 在實際開發中, C工程師會給我們c文件如下 : first.h first.c, 一個C主程序, 一個頭文件, 我們只需要將這個頭文件引入到jni中的C代碼中即可, 在我們自定義生成的簽名函數中調用 first.h中的方法;

first.h源碼 : 
  1. #ifndef FIRST_H  
  2. #define FIRST_H  
  3.   
  4. extern int first(int  x, int  y);  
  5.   
  6. #endif /* FIRST_H */  

first.c源碼 : 
  1. #include "first.h"  
  2.   
  3. int  first(int  x, int  y)  
  4. {  
  5.     return x + y;  
  6. }  

在簽名函數中, 直接調用 first()方法即可
;

.

七 分析Log日誌系統框架的JNI代碼



在這裏分析日誌輸出函數 : Log.i(TAG, "log"), 分析該日誌系統的JNI層源碼結構;

這裏使用下載的Android2.3.3源碼進行分析 : 在 http://blog.csdn.net/shulianghan/article/details/17350401 中介紹瞭如何使用repo 和 git 下載Android源碼 和 kernel 源碼;

LogJNI調用層次 : android.util.Log.java 中的接口 是通過JNI調用 本地庫 並最終調用內核驅動程序 Logger 將日誌信息寫到 內核空間中.

分析的源碼文件 : "\" 代表Android源代碼的本目錄;
-- Java代碼 : \frameworks\base\core\java\android\util\Log.java
-- JNI層實現代碼 : \frameworks\base\core\jni\android_util_Log.cpp
下面的是Android自定義的JNI規範相關的源碼 : 
-- JNI規範頭文件 : \dalvik\libnativehelper\include\nativehelper\jni.h
-- JNI幫助文件 : ① \dalvik\libnativehelper\include\nativehelper\JNIHelp.h  ② \dalvik\libnativehelper\JNIHelp.c
-- JNI運行時文件 :  \frameworks\base\core\jni\AndroidRuntime.cpp

這裏將上面幾個文件上傳到CSDN資源中, 便於查看 : http://download.csdn.net/detail/han1202012/6905507 ;

1. 分析Log.java源碼


Log.java分析 : 在Log.java文件中,定義了 isLoggable 和 println_native 兩個Native方法, 在Java方法中, 只需要事先聲明native方法, 不用實現方法體, 可以直接調用;
Log.java在Android源碼中的位置 : \frameworks\base\core\java\android\util\Log.java

Log.java內容 : 
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. package android.util;  
  2.   
  3. import com.android.internal.os.RuntimeInit;  
  4.   
  5. import java.io.PrintWriter;  
  6. import java.io.StringWriter;  
  7. public final class Log {  
  8.   
  9.     ... ...  
  10.       
  11.     //打印日誌  
  12.     public static int d(String tag, String msg) {  
  13.         return println_native(LOG_ID_MAIN, DEBUG, tag, msg);  
  14.     }  
  15.   
  16.     //打印日誌和異常  
  17.     public static int d(String tag, String msg, Throwable tr) {  
  18.         return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));  
  19.     }  
  20.   
  21.     //打印日誌  
  22.     public static int i(String tag, String msg) {  
  23.         return println_native(LOG_ID_MAIN, INFO, tag, msg);  
  24.     }  
  25.   
  26.     ... ...  
  27.       
  28.     //聲明native方法  
  29.     public static native boolean isLoggable(String tag, int level);  
  30.   
  31.     ... ...  
  32.     
  33.     /** @hide */ public static final int LOG_ID_MAIN = 0;  
  34.     /** @hide */ public static final int LOG_ID_RADIO = 1;  
  35.     /** @hide */ public static final int LOG_ID_EVENTS = 2;  
  36.     /** @hide */ public static final int LOG_ID_SYSTEM = 3;  
  37.   
  38.     //聲明native方法  
  39.     /** @hide */ public static native int println_native(int bufID,  
  40.             int priority, String tag, String msg);  
  41. }  

2. 分析Log系統JNI層源碼


JNI層方法: JNI層方法根據一定規則與Java層聲明的Native方法進行映射, 然後可以通過JNIEnv指針提供的JNI函數對Java層進行操作;
Log系統的JNI層文件是 : android_util_Log.cpp, 該文件路徑 :\frameworks\base\core\jni\android_util_Log.cpp 代碼如下 :
  1. #define LOG_NAMESPACE "log.tag."  
  2. #define LOG_TAG "Log_println"  
  3.   
  4. #include <assert.h>  
  5. #include <cutils/properties.h>  
  6. #include <utils/Log.h>  
  7. #include <utils/String8.h>  
  8.   
  9. #include "jni.h"  
  10. #include "utils/misc.h"  
  11. #include "android_runtime/AndroidRuntime.h"  
  12.   
  13. ... ...  
  14.   
  15. namespace android {  
  16.   
  17. struct levels_t {  
  18.     jint verbose;  
  19.     jint debug;  
  20.     jint info;  
  21.     jint warn;  
  22.     jint error;  
  23.     jint assert;  
  24. };  
  25. static levels_t levels;  
  26.   
  27. static int toLevel(const char* value)   
  28. {  
  29.     switch (value[0]) {  
  30.         case 'V'return levels.verbose;  
  31.         case 'D'return levels.debug;  
  32.         case 'I'return levels.info;  
  33.         case 'W'return levels.warn;  
  34.         case 'E'return levels.error;  
  35.         case 'A'return levels.assert;  
  36.         case 'S'return -1; // SUPPRESS  
  37.     }  
  38.     return levels.info;  
  39. }  
  40.   
  41. /* 
  42.     實現Java層聲明的 isLoggable 方法, 注意方法名不符合標準JNI規範 
  43.     標準的JNI規範方法名應該是 Java_包名_類名_方法名 
  44.     其中傳入了JNIEnv 和 jobject 參數, JNIEnv參數是Java運行環境, 可以與JVM進行交互 
  45.     jobject參數是包含Native方法的Java類對象 
  46.     該方法中可以通過JNIEnv調用本地庫進行函數處理, 最後返回給Java層函數 
  47. */  
  48. static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)  
  49. {  
  50. #ifndef HAVE_ANDROID_OS  
  51.     return false;  
  52. #else /* HAVE_ANDROID_OS */  
  53.     int len;  
  54.     char key[PROPERTY_KEY_MAX];  
  55.     char buf[PROPERTY_VALUE_MAX];  
  56.   
  57.     if (tag == NULL) {  
  58.         return false;  
  59.     }  
  60.       
  61.     jboolean result = false;  
  62.       
  63.     //調用了JNI函數  
  64.     const char* chars = env->GetStringUTFChars(tag, NULL);  
  65.   
  66.     if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {  
  67.         jclass clazz = env->FindClass("java/lang/IllegalArgumentException");  
  68.         char buf2[200];  
  69.         snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %d characters\n",  
  70.                 chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));  
  71.   
  72.         // release the chars!  
  73.         env->ReleaseStringUTFChars(tag, chars);  
  74.   
  75.         env->ThrowNew(clazz, buf2);  
  76.         return false;  
  77.     } else {  
  78.         strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);  
  79.         strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);  
  80.     }  
  81.       
  82.     env->ReleaseStringUTFChars(tag, chars);  
  83.   
  84.     len = property_get(key, buf, "");  
  85.     int logLevel = toLevel(buf);  
  86.     return (logLevel >= 0 && level >= logLevel) ? true : false;  
  87. #endif /* HAVE_ANDROID_OS */  
  88. }  
  89.   
  90. /* 
  91.  * In class android.util.Log: 
  92.  *  public static native int println_native(int buffer, int priority, String tag, String msg) 
  93.  */  
  94. static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,  
  95.         jint bufID, jint priority, jstring tagObj, jstring msgObj)  
  96. {  
  97.     const char* tag = NULL;  
  98.     const char* msg = NULL;  
  99.   
  100.     if (msgObj == NULL) {  
  101.         jclass npeClazz;  
  102.   
  103.         npeClazz = env->FindClass("java/lang/NullPointerException");  
  104.         assert(npeClazz != NULL);  
  105.   
  106.         env->ThrowNew(npeClazz, "println needs a message");  
  107.         return -1;  
  108.     }  
  109.   
  110.     if (bufID < 0 || bufID >= LOG_ID_MAX) {  
  111.         jclass npeClazz;  
  112.   
  113.         npeClazz = env->FindClass("java/lang/NullPointerException");  
  114.         assert(npeClazz != NULL);  
  115.   
  116.         env->ThrowNew(npeClazz, "bad bufID");  
  117.         return -1;  
  118.     }  
  119.   
  120.     if (tagObj != NULL)  
  121.         tag = env->GetStringUTFChars(tagObj, NULL);  //調用JNI函數  
  122.     msg = env->GetStringUTFChars(msgObj, NULL);  
  123.   
  124.     int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);  
  125.   
  126.     if (tag != NULL)  
  127.         env->ReleaseStringUTFChars(tagObj, tag); //調用JNI函數釋放資源  
  128.     env->ReleaseStringUTFChars(msgObj, msg); //調用JNI函數釋放資源  
  129.   
  130.     return res;  
  131. }  
  132.   
  133. /* 
  134.  * JNI registration. JNI方法註冊 
  135.  */  
  136. static JNINativeMethod gMethods[] = {  
  137.     /* name, signature, funcPtr */  
  138.     { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },  
  139.     { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },  
  140. };  
  141.   
  142. int register_android_util_Log(JNIEnv* env)  
  143. {  
  144.     jclass clazz = env->FindClass("android/util/Log");  
  145.   
  146.     if (clazz == NULL) {  
  147.         LOGE("Can't find android/util/Log");  
  148.         return -1;  
  149.     }  
  150.       
  151.     levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE""I"));  
  152.     levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG""I"));  
  153.     levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO""I"));  
  154.     levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN""I"));  
  155.     levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR""I"));  
  156.     levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT""I"));  
  157.                   
  158.     return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));  
  159. }  
  160.   
  161. }; // namespace android  

3. 聲明JNI 與 Native 方法的映射關係


標準JNI規範 : 在標準的JNI規範中, Java中的Native方法 與 JNI層方法 是通過方法名的對應關係進行映射的, 我們通過 javah 工具生成JNI層頭文件, 頭文件中定義了規範的JNI層方法名, 這個方法名就與Java Native方法對應;

Android自定義規範 : 在 \dalvik\libnativehelper\include\nativehelper\jni.h 中定義了這樣的映射關係 : 
  1. typedef struct {  
  2.     const char* name;       //Java層Native函數方法名  
  3.     const char* signature;  //Java層Native函數的簽名  
  4.     void*       fnPtr;      //JNI層實現的方法  
  5. } JNINativeMethod;  
.
JNINativeMethod類型數據 : 在android_util_Log.cpp 中定義了一個該類型的數組 :
  1. /* 
  2.  * JNI registration. 
  3.  */  
  4. static JNINativeMethod gMethods[] = {  
  5.     /* name, signature, funcPtr */  
  6.     { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },  
  7.     { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },  
  8. };  

JNINativeMethod結構體作用 : JNINativeMethod是一個結構體類型, 聲明瞭Native方法 與 JNI方法 的映射關係;
-- 解析上面的數組中的元素 : 
 --- Native方法 : "isLoggable" 是Java中聲明的Native方法; 
 --- 方法簽名 : "(Ljava/lang/String;I)Z" 表示該方法的簽名, 參數是String類型 和 int類型, Z 表示 boolean類型;
 --- JNI方法 : (void*) android_util_Log_isLoggable 表示JNI層實現的方法指針;

4. 註冊JNI方法到虛擬機中

映射關係體現到虛擬機中 :  在android_util_Log.cpp 中存在這樣的方法 : 
  1. int register_android_util_Log(JNIEnv* env)  
  2. {  
  3.     jclass clazz = env->FindClass("android/util/Log");  
  4.   
  5.     if (clazz == NULL) {  
  6.         LOGE("Can't find android/util/Log");  
  7.         return -1;  
  8.     }  
  9.       
  10.     levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE""I"));  
  11.     levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG""I"));  
  12.     levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO""I"));  
  13.     levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN""I"));  
  14.     levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR""I"));  
  15.     levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT""I"));  
  16.                   
  17.     return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));  
  18. }  
  19.   
  20. }; // namespace android  

核心方法 : 該函數調用了 AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods)) 方法註冊JNI方法;

register_android_util_Log調用時機 : 該函數是在Android系統啓動的時候, 通過AndroidRuntime.cpp中的register_jni_proocs方法執行, 執行該方法的時候會將 Native方法 與 JNI方法 的函數映射關係註冊給 Dalvik 虛擬機;


5. 解析registerNativeMethod函數


該函數定義在AndroidRuntime.cpp中 : 該文件的路徑在 \frameworks\base\core\jni\AndroidRuntime.cpp ;
  1. /* 
  2.  * Register native methods using JNI. 
  3.  */  
  4. /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,  
  5.     const char* className, const JNINativeMethod* gMethods, int numMethods)  
  6. {  
  7.     return jniRegisterNativeMethods(env, className, gMethods, numMethods);  
  8. }  

registerNativeMethods 方法只是對 jniRegisterNativeMethods 方法的封裝, 在JNIHelp.h中找到該方法的聲明
  1. /* 
  2.  * Register one or more native methods with a particular class. 
  3.  */  
  4. int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,  
  5.     const JNINativeMethod* gMethods, int numMethods);  

在JNIHelp.c 中找到該方法的實現 : 最終方法中調用了 JNIEnv 的RegisterNatives 函數, 將gMethods中存放的JNINativeMethod結構體(存放Native方法 與 JNI方法關聯信息) 傳遞到java虛擬機;
  1. /* 
  2.  * Register native JNI-callable methods. 
  3.  * 
  4.  * "className" looks like "java/lang/String". 
  5.  */  
  6. int jniRegisterNativeMethods(JNIEnv* env, const char* className,  
  7.     const JNINativeMethod* gMethods, int numMethods)  
  8. {  
  9.     jclass clazz;  
  10.   
  11.     LOGV("Registering %s natives\n", className);  
  12.     clazz = (*env)->FindClass(env, className);  
  13.     if (clazz == NULL) {  
  14.         LOGE("Native registration unable to find class '%s'\n", className);  
  15.         return -1;  
  16.     }  
  17.   
  18.     int result = 0;  
  19.     if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {  
  20.         LOGE("RegisterNatives failed for '%s'\n", className);  
  21.         result = -1;  
  22.     }  
  23.   
  24.     (*env)->DeleteLocalRef(env, clazz);  
  25.     return result;  
  26. }  


6. JNI的規範


Android中JNI存在兩種規範 : 一種是標準的JNI規範, 多在應用層使用; 另一種是Android中自定義的規範, 多使用在應用框架層;
-- JNI標準規範: 遵守JNI標準規函數命名方式, JNI中方法命名爲 Java_包名_類名_方法名 , 可以使用javah生成簽名頭文件, 靠這種方式實現 Native方法 與 JNI方法之間的映射關係, 即應用直接與框架層進行交互, 這種規範常用與應用開發;
-- 函數註冊規範 : 這是Android自定義的一種規範, 應用框架層採用該規範, 即應用框架層 與 框架層 進行交互, 底層源碼開發多使用該規範;

.

作者 : 萬境絕塵 

轉載請註明出處 : http://blog.csdn.net/shulianghan/article/details/18964835

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