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 ;
執行結果 :
- <span style="font-family: 'Courier New';">Android NDK: Could not find application project directory !
- Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
- /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示例 :
- LOCAL_PATH := $(call my-dir)
-
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := hello-jni
- LOCAL_SRC_FILES := hello-jni.c
-
- 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 ;
- <uses-sdk
- android:minSdkVersion="7"
- android:targetSdkVersion="10" />
NDK編譯原則 : 編譯NDK動態庫是按照最小版本進行編譯, 選擇編譯的平臺的時候, 會選擇 NDK 7 平臺進行編譯;
(2) 聲明native方法
聲明native方法, 注意該方法沒有方法體 和 參數, 如下 :
(3) 創建C文件
引入頭文件: 首先要包含頭文件 jni.h, 該頭文件位置定義在 android-ndk-r9c\platforms\android-5\arch-arm\usr\include目錄下的 jni.h, 下面是該頭文件中定義的一些方法, 包括本項目中使用的 NewString 方法;
- jstring (*NewString)(JNIEnv*, const jchar*, jsize);
- jsize (*GetStringLength)(JNIEnv*, jstring);
- const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
- void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
- jstring (*NewStringUTF)(JNIEnv*, const char*);
- 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語言文件源碼 :
- #include <jni.h>
-
- /*
- * 方法名稱規定 : Java_完整包名類名_方法名()
- * JNIEnv 指針
- *
- * 參數介紹 :
- * env : 代表Java環境, 通過這個環境可以調用Java中的方法
- * thiz : 代表調用JNI方法的對象, 即MainActivity對象
- */
- jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)
- {
- /*
- * 調用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法
- * jni.h 中定義的方法 jstring (*NewStringUTF)(JNIEnv*, const char*);
- */
- return (*env)->NewStringUTF(env, "hello world jni");
- }
(4) 編寫Android.mk文件
查詢NDK文檔 : NDK的文檔在NDK工具根目錄下, 點擊 documentation.html 文件, 就可以在瀏覽器中打開NDK文檔;
上面的開發流程中詳細的介紹了Android.mk 五個參數的詳細用處, 這裏直接給出源碼 :
- LOCAL_PATH := $(call my-dir)
-
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := hello
- LOCAL_SRC_FILES := hello.c
-
- 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 作爲動態庫名稱, 這是規定的;
- //靜態代碼塊加載C語言庫文件
- static{
- System.loadLibrary("hello");
- }
(7) 其它源碼
MainActivity源碼 :
- package shuliang.han.ndkhelloworld;
-
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Toast;
-
- public class MainActivity extends Activity {
-
- //靜態代碼塊加載C語言庫文件
- static{
- System.loadLibrary("hello");
- }
-
- /*
- * 聲明一個native方法
- * 這個方法在Java中是沒有實現的, 沒有方法體
- * 該方法需要使用C語言編寫
- */
- public native String helloFromJNI();
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- System.out.println(helloFromJNI());
- }
-
- public void onClick(View view) {
- //點擊按鈕顯示從jni調用得到的字符串信息
- Toast.makeText(getApplicationContext(), helloFromJNI(), 1).show();
- }
-
- }
XML佈局文件 :
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context=".MainActivity" >
-
- <Button
- android:id="@+id/bt"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:onClick="onClick"
- android:text="顯示JNI返回的字符串" />
-
- </RelativeLayout>
(8) 將源碼上傳到GitHub中
在GitHub上創建工程 :
項目地址
-- HTTP: https://github.com/han1202012/NDKHelloworld.git
生成的命令 :
- touch README.md
- git init
- git add README.md
- git commit -m "first commit"
- git remote add origin [email protected]:han1202012/NDKHelloworld.git
- 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文件內容 :
- LOCAL_PATH := $(call my-dir)
-
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := hello
- LOCAL_SRC_FILES := hello.c
-
- 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;
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class shuliang_han_ndkparameterpassing_DataProvider */
-
- #ifndef _Included_shuliang_han_ndkparameterpassing_DataProvider
- #define _Included_shuliang_han_ndkparameterpassing_DataProvider
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: shuliang_han_ndkparameterpassing_DataProvider
- * Method: add
- * Signature: (II)I
- */
- JNIEXPORT jint JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_add
- (JNIEnv *, jobject, jint, jint);
-
- /*
- * Class: shuliang_han_ndkparameterpassing_DataProvider
- * Method: sayHelloInc
- * Signature: (Ljava/lang/String;)Ljava/lang/String;
- */
- JNIEXPORT jstring JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc
- (JNIEnv *, jobject, jstring);
-
- /*
- * Class: shuliang_han_ndkparameterpassing_DataProvider
- * Method: intMethod
- * Signature: ([I)[I
- */
- JNIEXPORT jintArray JNICALL Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod
- (JNIEnv *, jobject, jintArray);
-
- #ifdef __cplusplus
- }
- #endif
- #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文件如下 : 只改變了返回的字符串, 添加了中文;
- #include <jni.h>
-
- /*
- * 方法名稱規定 : Java_完整包名類名_方法名()
- * JNIEnv 指針
- *
- * 參數介紹 :
- * env : 代表Java環境, 通過這個環境可以調用Java中的方法
- * thiz : 代表調用JNI方法的對象, 即MainActivity對象
- */
- jstring Java_shuliang_han_ndkhelloworld_MainActivity_helloFromJNI(JNIEnv *env, jobject thiz)
- {
- /*
- * 調用 android-ndk-r9c\platforms\android-8\arch-arm\usr\include 中jni.h中的方法
- * jni.h 中定義的方法 jstring (*NewStringUTF)(JNIEnv*, const char*);
- */
- return (*env)->NewStringUTF(env, "hello world jni 中文");
- }
使用NDK重新編譯hello.c文件 : 修改了C源碼之後, 重新將該c文件編譯成so文件;
-- 編譯過程: 打開cygwin, 進入cygdrive/ 下對應windows中源碼項目中的jni目錄, 執行 /android-ndk-r9c/ndk-build 命令;
運行Android代碼報錯 : 因爲jni中c文件有中文, 中文不能被識別;
- 01-31 14:36:04.803: W/dalvikvm(389): JNI WARNING: illegal continuation byte 0xd0
- 01-31 14:36:04.803: W/dalvikvm(389): string: 'hello world jni ????'
- 01-31 14:36:04.803: W/dalvikvm(389): in Lshuliang/han/ndkhelloworld/MainActivity;.helloFromJNI ()Ljava/lang/String; (NewStringUTF)
- 01-31 14:36:04.834: I/dalvikvm(389): "main" prio=5 tid=1 NATIVE
- 01-31 14:36:04.834: I/dalvikvm(389): | group="main" sCount=0 dsCount=0 obj=0x4001f1a8 self=0xce48
- 01-31 14:36:04.834: I/dalvikvm(389): | sysTid=389 nice=0 sched=0/0 cgrp=default handle=-1345006528
- 01-31 14:36:04.844: I/dalvikvm(389): | schedstat=( 257006717 305462830 51 )
- 01-31 14:36:04.844: I/dalvikvm(389): at shuliang.han.ndkhelloworld.MainActivity.helloFromJNI(Native Method)
- 01-31 14:36:04.844: I/dalvikvm(389): at shuliang.han.ndkhelloworld.MainActivity.onCreate(MainActivity.java:26)
- 01-31 14:36:04.844: I/dalvikvm(389): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
- 01-31 14:36:04.853: I/dalvikvm(389): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)
- 01-31 14:36:04.853: I/dalvikvm(389): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
- 01-31 14:36:04.853: I/dalvikvm(389): at android.app.ActivityThread.access$1500(ActivityThread.java:117)
- 01-31 14:36:04.864: I/dalvikvm(389): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
- 01-31 14:36:04.864: I/dalvikvm(389): at android.os.Handler.dispatchMessage(Handler.java:99)
- 01-31 14:36:04.864: I/dalvikvm(389): at android.os.Looper.loop(Looper.java:123)
- 01-31 14:36:04.864: I/dalvikvm(389): at android.app.ActivityThread.main(ActivityThread.java:3683)
- 01-31 14:36:04.864: I/dalvikvm(389): at java.lang.reflect.Method.invokeNative(Native Method)
- 01-31 14:36:04.874: I/dalvikvm(389): at java.lang.reflect.Method.invoke(Method.java:507)
- 01-31 14:36:04.874: I/dalvikvm(389): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
- 01-31 14:36:04.874: I/dalvikvm(389): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
- 01-31 14:36:04.874: I/dalvikvm(389): at dalvik.system.NativeStart.main(Native Method)
- 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;
- struct _JNIEnv;
- struct _JavaVM;
- typedef const struct JNINativeInterface* C_JNIEnv;
-
- #if defined(__cplusplus) //爲了兼容C 和 C++兩種代碼 使用該 宏加以區分
- typedef _JNIEnv JNIEnv; //C++ 中的JNIEnv類型
- typedef _JavaVM JavaVM;
- #else
- typedef const struct JNINativeInterface* JNIEnv;//C語言中的JNIEnv類型
- typedef const struct JNIInvokeInterface* JavaVM;
- #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操作的相關方法 :
- /*
- * Table of interface function pointers.
- */
- struct JNINativeInterface {
- void* reserved0;
- void* reserved1;
-
- ... ...
-
- jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,
- va_list);
- jboolean (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID,
- jvalue*);
- jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);
- jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);
-
- ... ...
-
- void* (*GetDirectBufferAddress)(JNIEnv*, jobject);
- jlong (*GetDirectBufferCapacity)(JNIEnv*, jobject);
-
- /* added in JNI 1.6 */
- jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
- };
(3) C++中的JNIEnv
C++ 中的JNIEnv: C++ 中的JNIEnv 就是 _JNIEnv 結構體, 二者是等同的; 因此在調用 JNI函數的時候, 只需要使用 env->NewStringUTF(env, "hello")方法即可, 不用在進行*運算;
.
- /*
- * C++ object wrapper.
- *
- * This is usually overlaid on a C struct whose first element is a
- * JNINativeInterface*. We rely somewhat on compiler behavior.
- */
- struct _JNIEnv {
- /* do not rename this; it does not seem to be entirely opaque */
- const struct JNINativeInterface* functions;
-
- #if defined(__cplusplus)
-
- jint GetVersion()
- { return functions->GetVersion(this); }
-
- jlong GetDirectBufferCapacity(jobject buf)
- { return functions->GetDirectBufferCapacity(this, buf); }
-
- /* added in JNI 1.6 */
- jobjectRefType GetObjectRefType(jobject obj)
- { return functions->GetObjectRefType(this, obj); }
- #endif /*__cplusplus*/
- };
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 中都有相應的定義 :
- jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
- jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
- jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
- jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
- jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
- jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
- jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);
- jbyteArray (*NewByteArray)(JNIEnv*, jsize);
- jcharArray (*NewCharArray)(JNIEnv*, jsize);
- jshortArray (*NewShortArray)(JNIEnv*, jsize);
- jintArray (*NewIntArray)(JNIEnv*, jsize);
- jlongArray (*NewLongArray)(JNIEnv*, jsize);
- jfloatArray (*NewFloatArray)(JNIEnv*, jsize);
- jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize);
2. JNI在Java和C語言之間傳遞int類型
Java中定義的方法 :
- //將Java中的兩個int值 傳給C語言, 進行相加後, 返回java語言 shuliang.han.ndkparameterpassing.DataProvider
- public native int add(int x, int y);
C語言中定義的方法 :
- #include <jni.h>
-
- //方法簽名, Java環境 和 調用native方法的類 必不可少, 後面的參數就是native方法的參數
- jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
- {
- return x + y;
- }
使用NDK工具變異該c類庫 :
在cygwin中進入cygdrive, 然後進入windows中相應的目錄, 執行 /android-ndk-r9c/ndk-build 命令, 即可完成編譯;
3. NDK中C代碼使用LogCat
(1) 引入頭文件
NDK中斷點調試 : 斷點調試在NDK中實現極其困難, 因此在這裏我們一般都是打印日誌;
引入頭文件 : 在C代碼中引入下面的頭文件;
- #include <android/log.h>
- #define LOG_TAG "System.out"
- #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
- #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 方法中的日誌等級參數就使用 枚舉中的內容
- /*
- * Android log priority values, in ascending priority order. 日誌等級
- */
- typedef enum android_LogPriority {
- ANDROID_LOG_UNKNOWN = 0,
- ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
- ANDROID_LOG_VERBOSE,
- ANDROID_LOG_DEBUG,
- ANDROID_LOG_INFO,
- ANDROID_LOG_WARN,
- ANDROID_LOG_ERROR,
- ANDROID_LOG_FATAL,
- ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
- } android_LogPriority;
-
- /*
- * Send a simple string to the log. 向LogCat中輸出日誌
- 參數介紹: 日誌優先級 , 日誌標籤 , 日誌內容
- */
- 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文件 :
- LOCAL_PATH := $(call my-dir)
-
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := DataProvider
- LOCAL_SRC_FILES := DataProvider.c
- #增加log函數對應的函數庫 liblog.so libthread_db.a
- LOCAL_LDLIBS += -llog -lthread_db
- 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) 中的佔位符, 編寫打印日誌代碼:
- //Java中的int對應的是C語言中的long類型, 對應JNI中的jint類型, C語言中
- LOGI("JNI_日誌 : x = %ld , y = %ld" , x , y);
最終的包含打印日誌的完整代碼 : 注意, 這裏有一處可能錯誤, 如果是32位機器, int類型佔位符使用 %d 即可;
- #include <jni.h>
- #include <android/log.h>
- #define LOG_TAG "System.out"
- #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
- #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-
-
- //方法簽名, Java環境 和 調用native方法的類 必不可少, 後面的參數就是native方法的參數
- jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
- {
- //Java中的int對應的是C語言中的long類型, 對應JNI中的jint類型, C語言中
- LOGI("JNI_日誌 : x = %ld , y = %ld" , x , y);
- return x + y;
- }
重新編譯C文件 : 執行
/android-ndk-r9c/ndk-build命令;
-- 第一次編譯 : 出現警告, long int佔位符行不通, 注意區分機器位長, 64位 與 32位不同, 這樣編譯出現的結果就不會打印日誌;
-- 第二次編譯 : 將佔位符改爲 %d ;
執行按鈕之後打印的日誌 : 雖然有亂碼, 不過顯示出來了;
4. 字符串處理
.
Java中的String轉爲C語言中的char字符串 : 下面的工具方法可以在C程序中解決這個問題;
- // java中的jstring, 轉化爲c的一個字符數組
- char* Jstring2CStr(JNIEnv* env, jstring jstr) {
- <span style="white-space:pre"> </span>//聲明瞭一個字符串變量 rtn
- <span style="white-space:pre"> </span>char* rtn = NULL;
- <span style="white-space:pre"> </span>//找到Java中的String的Class對象
- <span style="white-space:pre"> </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");
- <span style="white-space:pre"> </span>//創建一個Java中的字符串 "GB2312"
- <span style="white-space:pre"> </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");
- <span style="white-space:pre"> </span>/*
- <span style="white-space:pre"> </span> * 獲取String中定義的方法 getBytes(), 該方法的參數是 String類型的, 返回值是 byte[]數組
- <span style="white-space:pre"> </span> * "(Ljava/lang/String;)[B" 方法前面解析 :
- <span style="white-space:pre"> </span> * -- Ljava/lang/String; 表示參數是String字符串
- <span style="white-space:pre"> </span> * -- [B : 中括號表示這是一個數組, B代表byte類型, 返回值是一個byte數組
- <span style="white-space:pre"> </span> */
- <span style="white-space:pre"> </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
- <span style="white-space:pre"> </span>"(Ljava/lang/String;)[B");
- <span style="white-space:pre"> </span>//調用Java中的getBytes方法, 傳入參數介紹 參數②表示調用該方法的對象, 參數③表示方法id , 參數④表示方法參數
- <span style="white-space:pre"> </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
- <span style="white-space:pre"> </span>strencode); // String .getByte("GB2312");
- <span style="white-space:pre"> </span>//獲取數組的長度
- <span style="white-space:pre"> </span>jsize alen = (*env)->GetArrayLength(env, barr);
- <span style="white-space:pre"> </span>//獲取數組中的所有的元素 , 存放在 jbyte*數組中
- <span style="white-space:pre"> </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
- <span style="white-space:pre"> </span>//將Java數組中所有元素拷貝到C的char*數組中, 注意C語言數組結尾要加一個 '\0'
- <span style="white-space:pre"> </span>if (alen > 0) {
- <span style="white-space:pre"> </span>rtn = (char*) malloc(alen + 1); //new char[alen+1]; "\0"
- <span style="white-space:pre"> </span>memcpy(rtn, ba, alen);
- <span style="white-space:pre"> </span>rtn[alen] = 0;
- <span style="white-space:pre"> </span>}
- <span style="white-space:pre"> </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //釋放內存
-
-
- <span style="white-space:pre"> </span>return rtn;
- }
Jstring2CStr方法講解 :
a. 獲取Java中String類型的class對象 : 參數 : 上下文環境 env, String類完整路徑 ;
- jclass clsstring = (*env)->FindClass(env, "java/lang/String");
b.創建Java字符串 : 使用 NewStringUTF 方法;
- jstring strencode = (*env)->NewStringUTF(env, "GB2312");
c.
獲取String中的getBytes()方法 : 參數介紹 ① env 上下文環境 ② 完整的類路徑 ③ 方法名 ④ 方法簽名, 方法簽名 Ljava/lang/String; 代表參數是String字符串, [B 中括號表示這是一個數組, B代表byte類型, 返回值是一個byte數組;
- jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
- "(Ljava/lang/String;)[B");
d.
獲取數組的長度 :
- jsize alen = (*env)->GetArrayLength(env, barr);
e. 獲取數組元素 : 獲取數組中的所有的元素 , 存放在 jbyte*數組中;
- jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
f.
數組拷貝: 將Java數組中所有元素拷貝到C的char*數組中, 注意C語言數組結尾要加一個 '\0';
- if (alen > 0) {
- rtn = (char*) malloc(alen + 1); //new char[alen+1]; "\0"
- memcpy(rtn, ba, alen);
- rtn[alen] = 0;
- }
g.
釋放內存 :
- (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //釋放內存
C語言方法 : 注意調用Jstring2CStr方法之後要強轉, 否則會出錯, Jstring2CStr方法要定義在該方法的前面, C語言中的方法要先聲明才能使用;
- jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)
- {
- char *p = (char*)Jstring2CStr(env, str);
- //打印Java傳遞過來的數據
- LOGI("Java JNI string parameter is : %s", p);
-
- char *append = "append";
-
- //strcat(dest, source) 函數可以將source字符串 添加到dest字符串後面
- return (*env)->NewStringUTF(env, strcat(p, append));
- }
--
如果沒有強轉會出現下面的錯誤 : char *p = Jstring2CStr(env, str);
--
將Jstring2CStr方法定義在主方法下面會出現下面錯誤 :
Java源碼 :
- case R.id.sayHelloInc:
- Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();
- 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);
創建數組相關方法 :
- jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);
- jbyteArray (*NewByteArray)(JNIEnv*, jsize);
- jcharArray (*NewCharArray)(JNIEnv*, jsize);
- jshortArray (*NewShortArray)(JNIEnv*, jsize);
- jintArray (*NewIntArray)(JNIEnv*, jsize);
- jlongArray (*NewLongArray)(JNIEnv*, jsize);
- jfloatArray (*NewFloatArray)(JNIEnv*, jsize);
- jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize);
獲取數組元素相關方法 :
- jboolean* (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);
- jbyte* (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);
- jchar* (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);
- jshort* (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);
- jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
- jlong* (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);
- jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);
- jdouble* (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);
C語言代碼 :
- jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)
- {
- //獲取arr大小
- int len = (*env)->GetArrayLength(env, arr);
-
- //在LogCat中打印出arr的大小
- LOGI("the length of array is %d", len);
-
- //如果長度爲0, 返回arr
- if(len == 0)
- return arr;
-
- //如果長度大於0, 那麼獲取數組中的每個元素
- jint* p = (*env)->GetIntArrayElements(env, arr, 0);
-
- //打印出數組中每個元素的值
- int i = 0;
- for(; i < len; i ++)
- {
- LOGI("arr[%d] = %d", i, *(p + i));
- }
-
- return arr;
-
- }
Java代碼 :
- case R.id.intMethod:
- int[] array = {1, 2, 3, 4, 5};
- dataProvider.intMethod(array);
- break;
執行結果 : 上面的那種LogCat竟然啓動失敗, 只能將就着用這個了;
7. 本程序源碼
XML佈局文件 :
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <Button
- android:id="@+id/add"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="調用 add 本地 方法"
- android:onClick="onClick"/>
-
- <Button
- android:id="@+id/sayHelloInc"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="調用 sayHelloInc 本地 方法"
- android:onClick="onClick"/>
-
- <Button
- android:id="@+id/intMethod"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="調用 intMethod 本地 方法"
- android:onClick="onClick"/>
-
- </LinearLayout>
Java源碼 :
-- MainActivity源碼 :
- package shuliang.han.ndkparameterpassing;
-
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Toast;
-
- public class MainActivity extends Activity {
-
- static{
- System.loadLibrary("DataProvider");
- }
-
- DataProvider dataProvider;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- dataProvider = new DataProvider();
- }
-
- public void onClick(View view) {
-
- int id = view.getId();
-
- switch (id) {
- case R.id.add:
- int result = dataProvider.add(1, 2);
- Toast.makeText(getApplicationContext(), "the add result : " + result, Toast.LENGTH_LONG).show();
- break;
-
- case R.id.sayHelloInc:
- Toast.makeText(getApplicationContext(), dataProvider.sayHelloInc("Hello"), Toast.LENGTH_LONG).show();
- break;
-
- case R.id.intMethod:
- int[] array = {1, 2, 3, 4, 5};
- dataProvider.intMethod(array);
- break;
-
- default:
- break;
- }
- }
-
- }
--
DataProvider源碼 :
- package shuliang.han.ndkparameterpassing;
-
- public class DataProvider {
-
- //將Java中的兩個int值 傳給C語言, 進行相加後, 返回java語言 shuliang.han.ndkparameterpassing.DataProvider
- public native int add(int x, int y);
-
- //將Java字符串傳遞給C語言, C語言處理字符串之後, 將處理結果返回給java
- public native String sayHelloInc(String s);
-
- //將java中的int數組傳遞給C語言, C語言爲每個元素加10, 返回給Java
- public native int[] intMethod(int[] nums);
-
- }
JNI相關源碼 :
-- Android.mk源碼 :
- LOCAL_PATH := $(call my-dir)
-
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := DataProvider
- LOCAL_SRC_FILES := DataProvider.c
- #增加log函數對應的log庫
- LOCAL_LDLIBS += -llog
-
- include $(BUILD_SHARED_LIBRARY)
--
DataProvider.c 主程序源碼 :
- #include <jni.h>
- #include <string.h>
- #include <android/log.h>
- #define LOG_TAG "System.out"
- #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
- #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-
- // java中的jstring, 轉化爲c的一個字符數組
- char* Jstring2CStr(JNIEnv* env, jstring jstr) {
- <span style="white-space:pre"> </span>//聲明瞭一個字符串變量 rtn
- <span style="white-space:pre"> </span>char* rtn = NULL;
- <span style="white-space:pre"> </span>//找到Java中的String的Class對象
- <span style="white-space:pre"> </span>jclass clsstring = (*env)->FindClass(env, "java/lang/String");
- <span style="white-space:pre"> </span>//創建一個Java中的字符串 "GB2312"
- <span style="white-space:pre"> </span>jstring strencode = (*env)->NewStringUTF(env, "GB2312");
- <span style="white-space:pre"> </span>/*
- <span style="white-space:pre"> </span> * 獲取String中定義的方法 getBytes(), 該方法的參數是 String類型的, 返回值是 byte[]數組
- <span style="white-space:pre"> </span> * "(Ljava/lang/String;)[B" 方法前面解析 :
- <span style="white-space:pre"> </span> * -- Ljava/lang/String; 表示參數是String字符串
- <span style="white-space:pre"> </span> * -- [B : 中括號表示這是一個數組, B代表byte類型, 返回值是一個byte數組
- <span style="white-space:pre"> </span> */
- <span style="white-space:pre"> </span>jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
- <span style="white-space:pre"> </span>"(Ljava/lang/String;)[B");
- <span style="white-space:pre"> </span>//調用Java中的getBytes方法, 傳入參數介紹 參數②表示調用該方法的對象, 參數③表示方法id , 參數④表示方法參數
- <span style="white-space:pre"> </span>jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,
- <span style="white-space:pre"> </span>strencode); // String .getByte("GB2312");
- <span style="white-space:pre"> </span>//獲取數組的長度
- <span style="white-space:pre"> </span>jsize alen = (*env)->GetArrayLength(env, barr);
- <span style="white-space:pre"> </span>//獲取數組中的所有的元素 , 存放在 jbyte*數組中
- <span style="white-space:pre"> </span>jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
- <span style="white-space:pre"> </span>//將Java數組中所有元素拷貝到C的char*數組中, 注意C語言數組結尾要加一個 '\0'
- <span style="white-space:pre"> </span>if (alen > 0) {
- <span style="white-space:pre"> </span>rtn = (char*) malloc(alen + 1); //new char[alen+1]; "\0"
- <span style="white-space:pre"> </span>memcpy(rtn, ba, alen);
- <span style="white-space:pre"> </span>rtn[alen] = 0;
- <span style="white-space:pre"> </span>}
- <span style="white-space:pre"> </span>(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //釋放內存
-
-
- <span style="white-space:pre"> </span>return rtn;
- }
-
- //方法簽名, Java環境 和 調用native方法的類 必不可少, 後面的參數就是native方法的參數
- jint Java_shuliang_han_ndkparameterpassing_DataProvider_add(JNIEnv * env, jobject obj, jint x, jint y)
- {
- //Java中的int對應的是C語言中的long類型, 對應JNI中的jint類型, C語言中
- LOGI("JNI_log : x = %d , y = %d" , x , y);
- return x + y;
- }
-
- jstring Java_shuliang_han_ndkparameterpassing_DataProvider_sayHelloInc(JNIEnv *env, jobject obj, jstring str)
- {
- char *p = (char*)Jstring2CStr(env, str);
- //打印Java傳遞過來的數據
- LOGI("Java JNI string parameter is : %s", p);
-
- char *append = "append";
-
- //strcat(dest, source) 函數可以將source字符串 添加到dest字符串後面
- return (*env)->NewStringUTF(env, strcat(p, append));
- }
-
- jintArray Java_shuliang_han_ndkparameterpassing_DataProvider_intMethod(JNIEnv *env, jobject obj, jintArray arr)
- {
- //獲取arr大小
- int len = (*env)->GetArrayLength(env, arr);
-
- //在LogCat中打印出arr的大小
- LOGI("the length of array is %d", len);
-
- //如果長度爲0, 返回arr
- if(len == 0)
- return arr;
-
- //如果長度大於0, 那麼獲取數組中的每個元素
- jint* p = (*env)->GetIntArrayElements(env, arr, 0);
-
- //打印出數組中每個元素的值
- int i = 0;
- for(; i < len; i ++)
- {
- LOGI("arr[%d] = %d", i, *(p + i));
- }
-
- return arr;
-
- }
.
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返回值;
- //DataProvider完整類名 shulaing.han.ndk_callback.DataProvider
- char* classname = "shulaing/han/ndk_callback/DataProvider";
-
-
- jclass dpclazz = (*env)->FindClass(env, classname);
(2) 找到要調用的方法的methodID
使用jni.h中提供的GetMethodID方法, 獲取jmethodID, 傳入參數 ①JNIEnv指針 ②Class對象 ③ 方法名 ④方法簽名, 在這裏方法名和方法簽名確定一個方法, 方法簽名就是方法的返回值 與 參數的唯一標示;
- //參數介紹 : 第二個參數是Class對象, 第三個參數是方法名,第四個參數是方法的簽名, 獲取到調用的method
- jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
找到靜態方法 : 如果方法是靜態的, 就使用GetStaticMethod方法獲取
- jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
- { return functions->GetStaticMethodID(this, clazz, name, sig); }
(3) 在C語言中調用相應方法
普通方法 : CallTypeMethod , 其中的Type隨着返回值類型的不同而改變;
參數介紹 : ① JNIEnv指針 ②調用該native方法的對象 ③方法的methodID ④⑤... 後面是可變參數, 這些參數是
- jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
-
- jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
- jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
- jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
- jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
- jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
- jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
- jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
- jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
- jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
- jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
- jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
- jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
- jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
- void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
- void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
靜態方法 : CallStaticTypeMethod, 其中的Type隨着返回值類型不同而改變;
.
2. 一些基本代碼編寫
Java代碼 : 定義一個callCcode本地方法, 以及三個Java方法, 在jni中使用本地方法調用Java中的方法;
- package shulaing.han.ndk_callback;
-
- public class DataProvider {
-
- public native void callCcode();
-
- //C調用java中空方法 shulaing.han.ndk_callback.DataProvider
-
- public void helloFromJava(){
- System.out.println("hello from java");
- }
-
- //C調用java中的帶兩個int參數的方法
- public int Add(int x,int y){
- return x + y;
- }
-
- //C調用java中參數爲string的方法
- public void printString(String s){
- System.out.println(s);
- }
-
- }
生成頭文件 : 進入 bin/classed目錄, 使用
javah shulaing.han.ndk_callback.DataProvider 命令, 可以在bin/classes下生成頭文件;
頭文件內容 : 文件名 : shulaing_han_ndk_callback_DataProvider.h ;
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class shulaing_han_ndk_callback_DataProvider */
-
- #ifndef _Included_shulaing_han_ndk_callback_DataProvider
- #define _Included_shulaing_han_ndk_callback_DataProvider
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: shulaing_han_ndk_callback_DataProvider
- * Method: callCcode
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
- (JNIEnv *, jobject);
-
- #ifdef __cplusplus
- }
- #endif
- #endif
編寫Android.mk文件 : 注意將LogCat日誌輸出系統動態庫加入;
- LOCAL_PATH := $(call my-dir)
-
- include $(CLEAR_VARS)
-
- LOCAL_MODULE := jni
- LOCAL_SRC_FILES := jni.c
- #增加log函數對應的log庫
- LOCAL_LDLIBS += -llog
-
- include $(BUILD_SHARED_LIBRARY)
編寫jni的C代碼 : 注意加入LogCat相關導入的包;
- #include "shulaing_han_ndk_callback_DataProvider.h"
- #include <string.h>
- #include <android/log.h>
- #define LOG_TAG "System.out"
- #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
- #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 命令, 即可顯示出每個方法的簽名;
- $ javap -s shulaing.han.ndk_callback.DataProvider
- Compiled from "DataProvider.java"
- public class shulaing.han.ndk_callback.DataProvider extends java.lang.Object{
- public shulaing.han.ndk_callback.DataProvider();
- Signature: ()V
- public native void callCcode();
- Signature: ()V
- public void helloFromJava();
- Signature: ()V
- public int Add(int, int);
- Signature: (II)I
- public void printString(java.lang.String);
- Signature: (Ljava/lang/String;)V
- }
截圖 :
方法簽名介紹 :
-- 返回值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方法的相關函數 :
- jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
-
- jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
- jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
- jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
- jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
- jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
- jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
- jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
- jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
- jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
- jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
- jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
- jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;
- jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;
- jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;
- void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
- void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
- void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
C語言代碼 :
- #include "shulaing_han_ndk_callback_DataProvider.h"
- #include <string.h>
- #include <android/log.h>
- #define LOG_TAG "System.out"
- #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
- #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-
- JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
- (JNIEnv * env, jobject obj)
- {
- //調用DataProvider對象中的helloFromJava()方法
- //獲取到某個對象, 獲取對象中的方法, 調用獲取到的方法
- LOGI("in code");
- //DataProvider完整類名 shulaing.han.ndk_callback.DataProvider
- char* classname = "shulaing/han/ndk_callback/DataProvider";
-
-
- jclass dpclazz = (*env)->FindClass(env, classname);
- if(dpclazz == 0)
- LOGI("class not find !!!");
- else
- LOGI("class find !!!");
-
- //參數介紹 : 第二個參數是Class對象, 第三個參數是方法名,第四個參數是方法的簽名, 獲取到調用的method
- jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "helloFromJava", "()V");
- if(methodID == 0)
- LOGI("method not find !!!");
- else
- LOGI("method find !!!");
-
- /*
- * 調用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
- * 參數介紹 : 後面的 ... 是可變參數, 如果該返回值void的方法有參數, 就將參數按照次序排列
- */
- LOGI("before call method");
- (*env)->CallVoidMethod(env, obj, methodID);
- LOGI("after call method");
-
- }
Java代碼 :
--XML佈局文件代碼 :
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context=".MainActivity" >
-
- <Button
- android:id="@+id/call_void_method"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:onClick="onClick"
- android:text="C語言回調Java中的空方法" />
-
- </LinearLayout>
--
MainActivity代碼 :
- package shulaing.han.ndk_callback;
-
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
-
- public class MainActivity extends Activity {
-
- static{
- System.loadLibrary("jni");
- }
- DataProvider dp;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- dp = new DataProvider();
- }
-
- public void onClick(View view) {
- int id = view.getId();
- switch (id) {
- case R.id.call_void_method:
- dp.callCcode();
- break;
-
- default:
- break;
- }
- }
-
- }
執行結果 :
.
4. C代碼回調Java中帶String參數的方法
在DataProvider中添加兩個native方法 :
- public native void callCcode();
- public native void callCcode1();
- public native void callCcode2();
進入bin/classes目錄, 使用 javah -jni shulaing.han.ndk_callback.DataProvider 命令生成頭文件 :
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class shulaing_han_ndk_callback_DataProvider */
-
- #ifndef _Included_shulaing_han_ndk_callback_DataProvider
- #define _Included_shulaing_han_ndk_callback_DataProvider
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: shulaing_han_ndk_callback_DataProvider
- * Method: callCcode
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode
- (JNIEnv *, jobject);
-
- /*
- * Class: shulaing_han_ndk_callback_DataProvider
- * Method: callCcode1
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
- (JNIEnv *, jobject);
-
- /*
- * Class: shulaing_han_ndk_callback_DataProvider
- * Method: callCcode2
- * Signature: ()V
- */
- JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
- (JNIEnv *, jobject);
-
- #ifdef __cplusplus
- }
- #endif
- #endif
jni C語言代碼 : 這裏只需要修改兩處, 方法名, 獲取方法id中的參數, 調用方法中最後加上一個Java參數;
- JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode1
- (JNIEnv *env, jobject obj)
- {
- //調用DataProvider對象中的helloFromJava()方法
- //獲取到某個對象, 獲取對象中的方法, 調用獲取到的方法
- LOGI("in code");
- //DataProvider完整類名 shulaing.han.ndk_callback.DataProvider
- char* classname = "shulaing/han/ndk_callback/DataProvider";
-
-
- jclass dpclazz = (*env)->FindClass(env, classname);
- if(dpclazz == 0)
- LOGI("class not find !!!");
- else
- LOGI("class find !!!");
-
- //參數介紹 : 第二個參數是Class對象, 第三個參數是方法名,第四個參數是方法的簽名, 獲取到調用的method
- jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "printString", "(Ljava/lang/String;)V");
- if(methodID == 0)
- LOGI("method not find !!!");
- else
- LOGI("method find !!!");
-
- /*
- * 調用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
- * 參數介紹 : 後面的 ... 是可變參數, 如果該返回值void的方法有參數, 就將參數按照次序排列
- */
- LOGI("before call method");
- (*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "printString method callback success!!"));
- LOGI("after call method");
- }
執行後的結果 :
5. C代碼中回調帶兩個int類型的參數的方法
按照上面的流程, 不同之處就是jni中獲取方法 和 方法id , 調用方法的jni函數不同 :
- JNIEXPORT void JNICALL Java_shulaing_han_ndk_1callback_DataProvider_callCcode2
- (JNIEnv *env, jobject obj)
- {
- //調用DataProvider對象中的helloFromJava()方法
- //獲取到某個對象, 獲取對象中的方法, 調用獲取到的方法
- LOGI("in code");
- //DataProvider完整類名 shulaing.han.ndk_callback.DataProvider
- char* classname = "shulaing/han/ndk_callback/DataProvider";
-
-
- jclass dpclazz = (*env)->FindClass(env, classname);
- if(dpclazz == 0)
- LOGI("class not find !!!");
- else
- LOGI("class find !!!");
-
- //參數介紹 : 第二個參數是Class對象, 第三個參數是方法名,第四個參數是方法的簽名, 獲取到調用的method
- jmethodID methodID = (*env)->GetMethodID(env, dpclazz, "Add", "(II)I");
- if(methodID == 0)
- LOGI("method not find !!!");
- else
- LOGI("method find !!!");
-
- /*
- * 調用方法 void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
- * 參數介紹 : 後面的 ... 是可變參數, 如果該返回值void的方法有參數, 就將參數按照次序排列
- */
- LOGI("before call method");
- (*env)->CallIntMethod(env, obj, methodID, 3, 5);
- LOGI("after call method");
-
- }
Java代碼 :
- case R.id.call_int_parameter_method:
- dp.callCcode2();
- break;
執行結果 :
6. 完整源碼
Java源碼 :
-- DataProvider源碼 :
- package shulaing.han.ndk_callback;
-
-
- public class DataProvider {
-
- public native void callCcode();
- public native void callCcode1();
- public native void callCcode2();
-
- //C調用java中空方法 shulaing.han.ndk_callback.DataProvider
-
- public void helloFromJava(){
- System.out.println("hello from java");
- }
-
- //C調用java中的帶兩個int參數的方法
- public int Add(int x,int y){
- System.out.println("the add result is : " + (x + y));
- return x + y;
- }
-
- //C調用java中參數爲string的方法
- public void printString(String s){
- System.out.println("in java code :" + s);
- }
-
- }
-- MainActivity源碼 :
- package shulaing.han.ndk_callback;
-
- import android.app.Activity;
- import android.os.Bundle;
- import android.view.View;
-
- public class MainActivity extends Activity {
-
- static{
- System.loadLibrary("jni");
- }
- DataProvider dp;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- dp = new DataProvider();
- }
-
- public void onClick(View view) {
- int id = view.getId();
- switch (id) {
- case R.id.call_void_method:
- dp.callCcode();
- break;
-
- case R.id.call_string_parameter_method:
- dp.callCcode1();
- break;
-
- case R.id.call_int_parameter_method:
- dp.callCcode2();
- break;
-
- default:
- break;
- }
- }
-
- }
XML佈局文件源碼 :
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context=".MainActivity" >
-
- <Button
- android:id="@+id/call_void_method"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:onClick="onClick"
- android:text="C語言回調Java中的空方法" />
-
- <Button
- android:id="@+id/call_string_parameter_method"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:onClick="onClick"
- android:text="C語言回調Java中的String參數方法" />
-
- <Button
- android:id="@+id/call_int_parameter_method"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:onClick="onClick"