NDK項目源碼地址 : html
-- 第一個JNI示例程序下載 : GitHub - https://github.com/han1202012/NDKHelloworld.git java
JNI概念 : Java本地接口, Java Native Interface, 它是一個 協議, 該協議用來溝通Java代碼和外部的本地C/C++代碼, 經過該協議 Java代碼能夠調用外部的本地代碼, 外部的C/C++ 代碼能夠調用Java代碼;linux
C和Java的側重 : android
-- C語言 : C語言中最重要的是 函數 function; git
-- Java語言 : Java中最重要的是 JVM, class類, 以及class中的方法;github
C與Java如何交流 : 算法
-- JNI規範 : C語言與Java語言交流須要一個適配器, 中間件, 即 JNI, JNI提供了一種規範; windows
-- C語言中調用Java方法 : 可讓咱們在C代碼中找到Java代碼class中的方法, 而且調用該方法; api
-- Java語言中調用C語言方法 : 同時也能夠在Java代碼中, 將一個C語言的方法映射到Java的某個方法上; 數組
-- JNI橋樑做用 : JNI提供了一個橋樑, 打通了C語言和Java語言之間的障礙;
正常狀況下的Android框架 : 最 頂層是 Android的應用程序代碼, 是純Java代碼, 中間有一層的 Framework框架層代碼是 C/C++代碼, 經過Framework進行系統調用, 調用底層的庫 和 linux 內核;
使用JNI時的Android框架 : 繞過Framework提供的調用底層的代碼, 直接調用本身寫的C代碼, 該代碼最終會編譯成爲一個庫, 這個庫經過JNI提供的一個Stable的ABI 調用linux kernel;ABI是二進制程序接口 application binary interface.
JNI做用 :
-- 擴展: JNI擴展了JVM能力, 驅動開發, 例如開發一個wifi驅動, 能夠將手機設置爲無限路由;
-- 高效 : 本地代碼效率高, 遊戲渲染, 音頻視頻處理等方面使用JNI調用本地代碼, C語言能夠靈活操做內存;
-- 複用 : 在文件壓縮算法 7zip開源代碼庫, 機器視覺 openCV開放算法庫 等方面能夠複用C平臺上的代碼, 沒必要在開發一套完整的Java體系, 避免重複發明輪子;
-- 特殊 : 產品的核心技術通常也採用JNI開發, 不易破解;
Java語言執行流程 :
-- 編譯字節碼 : Java編譯器編譯 .java源文件, 得到.class 字節碼文件;
-- 裝載類庫 : 使用類裝載器裝載平臺上的Java類庫, 並進行字節碼驗證;
-- Java虛擬機 : 將字節碼加入到JVM中, Java解釋器 和 即時編譯器 同時處理字節碼文件, 將處理後的結果放入運行時系統;
-- 調用JVM所在平臺類庫 : JVM處理字節碼後, 轉換成相應平臺的操做, 調用本平臺底層類庫進行相關處理;
Java一次編譯處處執行 : JVM在不一樣的操做系統都有實現, Java能夠一次編譯處處運行, 字節碼文件一旦編譯好了, 能夠放在任何平臺的虛擬機上運行;
.
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下使用的;
下載地址 : http://cygwin.com/setup-x86.exe , 這是下載器, 可使用該下載器在線安裝, 也能夠將cygwin下載到本地以後, 在進行安裝;
安裝器使用 : Cygwin的下載, 在線安裝, 卸載 等操做都有由該安裝器進行;
-- 本地文件安裝 : 選擇安裝文件所在的目錄, 而後選擇所要安裝的安裝包;
-- 在線安裝 : 選擇在線安裝便可, 而後選擇須要的安裝包;
-- 卸載 : windows上使用其它軟件例如360, 控制面板中是沒法卸載Cygwin的, 只能經過安裝器來卸載;
雙擊安裝器 setup-x86.exe 下一步 :
選擇安裝方式 :
-- 在線安裝 : 直接下載, 而後安裝;
-- 下載安裝文件 : 將安裝文件下載下來, 能夠隨時安裝, 注意安裝文件也須要安裝器來進行安裝;
-- 從本地文件安裝 : 即便用下載的安裝文件進行安裝;
選擇Cygwin安裝位置 :
選擇下載好安裝文件位置 : 以前我下了一個徹底版的Cygwin, 包括了全部的Cygwin組件, 所有加起來有5.23G, 下載速度很快, 使用網易的鏡像, 基本能夠全速下載;
選擇須要安裝Cygwin組件 : 這裏咱們只須要如下組件 : binutils , gcc , gcc-mingw , gdb , make , 不用下所有的組件;
以後點擊下一步等待完成安裝便可;
.
安裝完以後, 打開bash命令窗口, 能夠設置下顯示的字體, 使用 make -version 查看是否安裝成功 :
如下是Cygwin安裝目錄的狀況 : 該安裝目錄就是所模擬的 linux 的根目錄;
對應的linux目錄 : 這兩個目錄進行對比發現, 兩個目錄是同樣的, Cygwin的安裝目錄就是 linux根目錄;
cygdrive目錄 : 該目錄是Cygwin模擬出來的windows目錄結構, 進入該目錄後, 會發現windows的盤符目錄, 經過該目錄能夠 訪問windows中的文件;
從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上;
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 ;
執行結果 :
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 。 中止。
首選建立一個Android工程, 在這個工程中進行JNI開發;
注意方法名使用 native 修飾, 沒有方法體 和 參數, eg : public native String helloFromJNI();
在工程根目錄下建立 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方法所在的類;
如何寫 查看文檔, 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) : 告訴編譯器須要生成動態庫;
進入 cygdrive 找到windows目錄下對應的文件, 編譯完成以後, 會 自動生成so文件並放在libs目錄下, 以後就能夠在Java中調用C語言方法了;
在Java類中的靜態代碼塊中使用System.LoadLibrary()方法加載編譯好的 .so 動態庫;
NDK平臺版本 : NDK腳本隨着 android-sdk 版本不一樣, 執行的腳本也是不一樣的, 不一樣平臺會引用不一樣的頭文件, 編譯的時候必定注意 sdk 與 ndk 版本要一致;
so文件在內存中位置 : apk文件安裝到手機上以後, .so動態庫文件存在在 data/安裝目錄/libs 目錄下;
按照上面的步驟進行開發
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 平臺進行編譯;
聲明native方法, 注意該方法沒有方法體 和 參數, 以下 :
/* * 聲明一個native方法 * 這個方法在Java中是沒有實現的, 沒有方法體 * 該方法須要使用C語言編寫 */ public native String helloFromJNI();
引入頭文件: 首先要包含頭文件 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"); }
查詢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)
進入Cygwin相應目錄 : 從Cygwin中的cygdrive 中進入windows的工程jni目錄 ;
編譯hello.c文件 : 注意Android.mk文件 與 hello.c 文件在同一目錄中;
編譯完成後的狀況 : 編譯完以後 會成成一個obj文件, 在obj文件中會生成 libhello.so, 系統會自動將該 so後綴文件放在libs目錄下;
靜態代碼塊中加載 : Java中在靜態代碼塊中加載庫文件, 調用 System.loadLibrary("hello") 方法, 注意 libs中的庫文件名稱爲 libhello.so, 咱們加載的時候 將 lib 去掉, 只取hello 做爲動態庫名稱, 這是規定的;
//靜態代碼塊加載C語言庫文件 static{ System.loadLibrary("hello"); }
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>
在上一篇博客 http://blog.csdn.net/shulianghan/article/details/18812279 中對GitHub用法進行了詳解;
在GitHub上建立工程 :
項目地址
-- HTTP: https://github.com/han1202012/NDKHelloworld.git
-- SSH : git@github.com:han1202012/NDKHelloworld.git
生成的命令 :
touch README.md git init git add README.md git commit -m "first commit" git remote add origin git@github.com:han1202012/NDKHelloworld.git git push -u origin master
打開 Git Bash 命令行窗口 :
-- 從GitHub上克隆項目到本地 : git clone git@github.com:han1202012/NDKHelloworld.git , 注意克隆的時候直接在倉庫根目錄便可, 不用再建立項目根目錄 ;
-- 添加文件 : git add ./* , 將目錄中全部文件添加;
-- 查看狀態 : git status ;
-- 提交緩存 : git commit -m '提交';
-- 提交到遠程GitHub倉庫 : git push -u origin master ;
GitHub項目 :
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" 結尾的動態庫, 咱們編譯動態庫的時候, 能夠將一些靜態庫引入進來;
使用javah工具 : 在C中實現Java調用的jni方法, 方法的簽名很複雜, 須要將完整的包名類名方法名都要使用 "_" 鏈接起來, 很麻煩, jdk提供的生成簽名方法的工具;
遺留問題 : 目前查到的方法是 在bin目錄下 執行 javah -jni 包名類名 命令, 可是執行不成功, 暫時沒找到解決方案;
-- Android中會自動生成 .class文件嗎, 沒發現啊, PY人!
問題解決 : 在bin目錄下有一個classes目錄, 該目錄在eclipse中看不到, 可是實際沒目錄中是存在的;
解決亂碼思路 : 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
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);
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 命令, 便可完成編譯;
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 | 字符串 |
.
.
在該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 ;
根據(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 ;
執行按鈕以後打印的日誌 : 雖然有亂碼, 不過顯示出來了;
Java中的String轉爲C語言中的char字符串 : 下面的工具方法能夠在C程序中解決這個問題;
// java中的jstring, 轉化爲c的一個字符數組 char* Jstring2CStr(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = (*env)->FindClass(env,"java/lang/String"); jstring strencode = (*env)->NewStringUTF(env,"GB2312"); jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312"); jsize alen = (*env)->GetArrayLength(env,barr); jbyte* ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE); if(alen > 0) { rtn = (char*)malloc(alen+1); //new char[alen+1]; "\0" memcpy(rtn,ba,alen); rtn[alen]=0; } (*env)->ReleaseByteArrayElements(env,barr,ba,0); //釋放內存 return rtn; }
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;
編譯以後運行結果 :
注意跨語言字符串轉換: JNI方法中, 要將Java的String字符串轉爲C中的char*字符串;
首先驗證C碼農提供的代碼是否可用 : 驗證該api是否可用, 在一個 int main() 函數中進行測試, 根據該測試代碼查看方法執行相關的狀況;
模塊講解 : 在該模塊中, 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居然啓動失敗, 只能將就着用這個了;
.
建立新項目 : han1202012/NDKParameterPassing ;
-- SSH地址 : git@github.com:han1202012/NDKParameterPassing.git ;
-- HTTP地址 : https://github.com/han1202012/NDKParameterPassing.git ;
.