Android 開發 之 JNI入門 - 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 - https://download.csdn.net/detail/han1202012/6905507
.
作者 :萬境絕塵
轉載請注明出處 : https://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安裝器
下載地址 : https://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工具分類 : 下載地址 -https://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 >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)
-- 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工程


(2) 聲明native方法
- /*
- * 聲明一個native方法
- * 這個方法在Java中是沒有實現的, 沒有方法體
- * 該方法需要使用C語言編寫
- */
- public native String helloFromJNI();
.
作者 : 萬境絕塵
轉載請注明出處 : https://blog.csdn.net/shulianghan/article/details/18964835
.
(3) 創建C文件
- 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);
- #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文件
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE := hello
- LOCAL_SRC_FILES := hello.c
- include $(BUILD_SHARED_LIBRARY)
(5) 編譯NDK動態庫



(6) Java中加載動態庫
- 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="https://schemas.android.com/apk/res/android"
- xmlns:tools="https://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中
- 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 命令行窗口 :

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

-- 查看狀態 : git status ;


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


3. 項目講解
(1) Android.mk文件講解
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE := hello
- LOCAL_SRC_FILES := hello.c
- include $(BUILD_SHARED_LIBRARY)
(2) 自動生成方法簽名



- /* 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開發中亂碼問題
- #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文件;

- 01-31 14:36:04.803: W/dalvikvm(389): JNI WARNING: illegal continuation byte 0xd0
-
01-31 14:最後更新:2017-04-03 12:55:22
上一篇:
Eclipse打包工具 Fatjar clipse插件安裝方法 如何使用fat打包運行
下一篇:
Java ???????????? ??????????????? ??? ?????????Adapter?????? (???)-??????-????????????-?????????