1. 程式人生 > >Android NDK開發從0到1

Android NDK開發從0到1

本文的開發環境為 Windows,其他平臺操作類似

其實說到 NDK 就不得不提 JNI ( Java Native Interface ) ,JNI 是專門用來與原生代碼進行互動而提供的一個介面。通過 JNI 就可以呼叫 C/C++ 所編寫的原生代碼。

而 NDK ( Native Development Kit) 是 Android 所提供的一個工具集合,通過 NDK 就可以很方便的在 Android 中通過 JNI 來訪問原生代碼。

那麼使用 NDK 有什麼好處呢?簡單總結幾點如下,更多的歡迎補充

  • 可以使用 C/C++ 的開源庫 ( 比如 OpenCV 等)
  • 安全性。因為 so 庫很難進行反編譯,所以在安全性上有一定的保證
  • 可移植。通過編譯生成的 so 庫可以在其他平臺上使用

那麼廢話不多說,下面開始吧~

開發流程

首先先大致明確NDK開發從0到1的幾個步驟:

  • 環境變數配置
  • 在 Java 類中編寫 native 程式碼
  • 生成與 native 方法對應的標頭檔案
  • 利用生成的標頭檔案,編寫對應的 C/C++ 程式碼
  • 生成 so 庫
  • 使用 so 庫

嗯,大致的步驟就是這幾個,但是具體實現起來時有很多坑,那麼接下來就按照這幾個步驟開始講解

環境變數配置

首先就是下載 NDK,那麼這裡有幾個方法

  • 從 Android Studio 中下載 (最好可以掛載 映象代理 )

當NDK下載完過後,經過解壓等一些列操作,就可以配置環境了

新建系統變數,將變數名( NDK_HOME )和變數值 (自己NDK檔案路徑)如下輸入

因為我是用的第二種方式,所以 NDK 直接下在了 android-sdk 中,具體情況請根據自己的檔案位置而定

然後新增到 path 中 %NDK_HOME%\ ,如果是新增在最後要記得在前面加個分號

配置完成過後,在命令視窗輸入 ndk-build

若能出現如上資訊,則說明配置成功

最後就是對Android Studio 進行環境配置,路徑根據自己的情況而定

在 Java 類中編寫 native 程式碼

新建 Android 專案,現在先見一個 JniUtil 類,專門用來存放 native 方法

12345 public class JniUtil { public
native int add(int a, int b)
;}

這裡將利用 NDK 計算兩數之和

生成與 native 方法對應的標頭檔案

在 Android NDK 開發中,C/C++ 中對應於 Java 方法的函式名應該叫什麼是很有講究的,大致是形式是

Java_包名_類名_方法名

所以 C/C++ 中的函式名不能隨便取,必須按照規則來。因為這個函式名很繁瑣,手動書寫十分容易出錯,所以這裡需要利用 javah 的命令來生成對應於函式的標頭檔案在標頭檔案中會有對應的 C/C++ 函式名,所以直接複製函式名就可以編寫自己的邏輯了

那麼接下來就利用 javah 生成自己的標頭檔案,命令格式如下

javah -classpath (搜尋類目錄) -d (輸出目錄) (類名)

這裡需要強調一下的是這裡的搜尋類目錄,搜尋的是這個 Java 類的 .class 檔案 ,所以在在執行生成標頭檔案的命令時,需要使用快捷鍵 ctrl+F9 或者 Android Studio 頂部選單欄 build -> Make Project,手動生成這個類的 class 檔案

生成完 class 檔案過後,就可以利用標頭檔案生成對應的標頭檔案了,命令如下:

javah -classpath app\build\intermediates\classes\debug -d ./app/src/main/jni com.innofang.ndkdemo.JniUtil

這裡只需要將上面括號類的內容按照自己專案的情況進行修改就可以了。但是,這麼長的命令,難道每次新增 native 方法都要自己手動輸入生成標頭檔案嗎?

在這裡可以利用一個小技巧,就是利用 Android Studio 的 External Tools 工具 ,一件生成標頭檔案

File -> Settings 或者 Ctrl+Alt+S 開啟設定介面,接著點選 Tools ,找到 External Tool

點選 +新建一個 Exteranl Tools

輸入如下資訊

在 Name 區域輸入 Generate Header File

Program : $JDKPath$\bin\javah

Parameters : -classpath $OutputPath$ -d ./app/src/main/jni $FileClass$

Working directory : $ProjectFileDir$

其餘的勾選項對照上圖即可

然後點選 OK 就完成了

使用時,只需要右擊java檔案,找到 External Tool ,點選 Generate Header File 即可

切換到 Project 檢視,就可以看到在 main 包下多了一個 jni 的檔案,裡面就是剛剛生成的標頭檔案

Q: 出現類似 錯誤: 找不到 'com.innofang.ndkdemo.jnituil' 的類檔案。 的情況

A:在生成標頭檔案之前要先生成類檔案,使用快捷鍵 ctrl+F9 或者 Android Studio 頂部選單欄 build -> Make Project

利用生成的標頭檔案,編寫對應的 C/C++ 程式碼

點選進入標頭檔案,檢視標頭檔案內部的內容

自動生成的標頭檔案內容

123456789101112131415161718192021 /* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_innofang_ndkdemo_JniUtil */#ifndef _Included_com_innofang_ndkdemo_JniUtil#define _Included_com_innofang_ndkdemo_JniUtil#ifdef __cplusplusextern "C" {#endif/* * Class: com_innofang_ndkdemo_JniUtil * Method: add * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_innofang_ndkdemo_JniUtil_add (JNIEnv *, jobject, jint, jint);#ifdef __cplusplus}#endif#endif

可以看到,在標頭檔案內部已經寫好了對應函式的宣告,這裡有一點需要強調一下

123 #ifdef __cplusplusextern "C" {#endif

這裡的巨集定義指定了 extern "C" 內部的函式將採用 C 語言的命名風格來編譯。如果你想使用 C++ 那麼這裡最好改成 extern "C++" {。否則當採用 C++ 時,可能會因為 C 和 C++ 比編譯過程中對函式的命名風格不同,導致編譯錯誤。

接下來就是編寫 C 程式碼 就將整個聲明覆制下來,在 jni 目錄下,新建一個 C 檔案並取名為取名為 jnituil.c ,這個名字可以隨意,可以不和標頭檔案名相同

建議根據標頭檔案中規定的是採用 C 還是 C++ 來決定新建什麼型別的檔案( .c 或 .cpp ),雖然不會報錯,但是牽扯到語言風格,規範一點總沒錯

1234567 #include "com_innofang_ndkdemo_JniUtil.h"JNIEXPORT jint JNICALL Java_com_innofang_ndkdemo_JniUtil_add (JNIEnv *env, jobject obj, jint a, jint b) { return a + b;}

因為函式功能是兩數相加求和,所以這裡直接返回兩數之和即可,這一步還是比較簡單的

簡單介紹一下上面出現的幾個陌生的東西,JNIEXPORT, JNICALL, JNIEnv 和 jobject,這些都是 JNI 標準中所定義的型別或者巨集,含義如下:

  • JNIEnv * : 表示一個指向 JNI 環境的指標,可以通過它來訪問 JNI 提供的介面方法
  • jobject : 表示 Java 物件中的 this
  • JNIEXPORT 和 JNICALL : 都是 JNI 中所定義的巨集,可以在 jni.h 這個標頭檔案中查詢到 ( 上面的程式碼中雖然沒有包含這個標頭檔案,但是如果你細心的話會發現在之前生成的標頭檔案中已經包含這個標頭檔案了,所以這裡就只包含自己生成的標頭檔案。當然還需要哪些標頭檔案需要根據具體情況而定)

這裡涉及到 JNI 的知識,暫時不是本文章範圍內,以後有時間再詳細介紹

生成 so 庫

這一步很關鍵,操作也比較繁瑣,簡單羅列一下操作步驟

  • 配置 Gradle
  • 編寫 Android.mk 檔案
  • 編寫 Application.mk 檔案

配置 Gradle

開啟 build.gradle 檔案 (記住這是在 Module:app 下的)

  1. 在 defaultConfig 標籤下,加入如下內容

    123 ndk { moduleName "app"}

    這個 app 就是等會生成的 so 庫的名字,也就是等會要使用的 so 庫的名字

  2. 然後再在 android 標籤下,新增如下內容

    123 sourceSets.main { jni.srcDirs = ['libs']}

    表示生成的 so 庫會在 src/main/libs 目錄下

這兩個步驟缺一不可

為了便於對照檢視,下面貼出這部分 Gradle 內容

12345678910111213141516171819202122232425262728293031 android { compileSdkVersion 25 buildToolsVersion "25.0.2" defaultConfig { applicationId "com.innofang.ndkdemo" minSdkVersion 21 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" ndk{ moduleName "app" // 這個 app 就是等會生成的 so 庫的名字 } } buildTypes { release { ... } } sourceSets.main { jni.srcDirs = ['libs'] // 表示生成的 so 庫會在 src/main/libs 目錄下 }}dependencies { ...}

編寫 Android.mk 檔案

在 jni 檔案下 ,新建一個檔案取名為 Android.mk

編輯內容如下

1234567 LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := appLOCAL_SRC_FILES := jniutil.cinclude $(BUILD_SHARED_LIBRARY)

這裡要關注的是 LOCAL_MODULE 和 LOCAL_SRC_FILES 這兩個東西

  • LOCAL_MODULE 表示模組的名稱。後面的 app 就是上一步驟配置的 moduleName
  • LOCAL_SRC_FILES 表示需要參與編譯的原檔案。後面的檔名就是剛才編寫的 C/C++ 程式碼的檔案

請根據具體情況來更改,這兩項必須對應正確

編寫 Application.mk 檔案

同樣 在 jni 檔案下 ,新建一個檔案取名為 Application.mk

編輯內容如下

1 APP_ABI := all
  • APP_ABI : 表示 CPU 的架構平臺的型別。all 表示編譯所有 CPU 平臺的 so 庫。

目前常見的平臺有 armeabi、x86 和 mips ,其中在移動裝置中佔據主要地位的是 armeabi ,所以有很多 apk 只包含 armeabi 型別的 so 庫的原因。預設情況下 NDK 會編譯產生各個 CPU 平臺的 so 庫,通過 APP_ABI 選項即可指定 so 庫的 CPU 平臺的型別。像上面寫 all 表示編譯所有 CPU 平臺的 so 庫, 若改成 armeabi 則只會編譯 armeabi 平臺下的 so 庫,如果需要編譯多個 so 庫,可以用 , 分割,如:APP_ABI := armeabi, x86

請根據實際情況來修改,若編譯所有 CPU 平臺的 so 庫,不可避免 apk 的體積也會隨之變大

上面的三個步驟都僅僅只是生成 so 庫的準備工作,下面才開始真正生成 so 庫

找到 Android Studio 底部操作欄的 Terminal 並點選,在 Terminal 內分別輸入如下命令

12 cd app/src/main/jni # 將位置切換到 jni目錄下ndk-build

這裡需要強調一點的是,如果目錄名字不是 jni,那麼 ndk-build 則無法編譯成功。因為在上一步生成標頭檔案的時候已經通過命令列自動生成了一個 jni 資料夾並在裡面生成了標頭檔案,所以這一步沒有強調讓建立 jni 資料夾。

如果終端資訊出現如下內容則說明 so 庫生成成功,否則請檢查上面的步驟是否正確操作

經過上面一系列步驟,你會發現 main 目錄下多了兩個資料夾 libs 和 obj,這兩個檔案下存的都是新生成的 so 庫。至此,生成 so 庫這個任務就算完成了。

但是發現每次生成 so 庫時都要手動切換路徑然後生成檔案,雖然說沒有之前生成標頭檔案那麼複雜,但是這種重複的操作還是有點麻煩,那麼為了簡化這個步驟,下面就要利用到上面提到過的 External Tool 了

還是開啟設定介面,找到 Tools 這個目錄,找到 External Tool,點選 + 新建一個 External Tool

在 Name : 區域輸入 Generate SO

Program : F:\android-sdk\ndk-bundle\ndk-build.cmd ( 這裡請改成自己的 NDK 目錄下的 ndk-build.cmd 的位置 )

Parameters : -C ./app/src/main/jni ( -C 後面接 project 路徑 )

Working directory : $ProjectFileDir$

最後點選 OK 就完成了。使用時,右擊 jni 檔案 找到並點選 External Tool 下的 Generate SO 即可

結果跟手動生成的一樣

使用 so 庫

上面進行了很多操作,都是為了使用生成的 so 庫

要使用 so 庫,首先就是先修改 JniUtil.java 檔案,新增一個靜態塊

123456789 public class JniUtil { static { System.loadLibrary("app"); } public native int add (int a, int b);}

然後在 main 目錄下新建一個資料夾,取名為 jniLibs

將剛才生成的 libs 目錄下的所有檔案拷貝到這個 jniLibs 目錄下!!!
將剛才生成的 libs 目錄下的所有檔案拷貝到這個 jniLibs 目錄下!!!
將剛才生成的 libs 目錄下的所有檔案拷貝到這個 jniLibs 目錄下!!!

重要的事情說三遍,如果缺失這一步驟將會出現找不到 so 庫的錯誤

確定經過上面這步後,就可以正式使用 so 庫了

回到我們熟悉的 MainActivity 中,為了使用 JniUtil 中的 add 方法,首先在佈局檔案中新增兩個 EditText 和一個 Button,然後在 MainActivity 中編寫如下程式碼

123456789101112131415161718192021222324252627282930 @Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /* 含有本地方法的Java類 */ final JniUtil jniUtil = new JniUtil(); /* 控制元件初始化 */ final EditText numberA = (EditText) findViewById(R.id.number_a); final EditText numberB = (EditText) findViewById(R.id.number_b); /* Button 點選事件 */ findViewById(R.id.result).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String a = numberA.getText().toString(); String b = numberB.getText().toString(); /* 判空 */ if (!TextUtils.isEmpty(a) && !TextUtils.isEmpty(b)) { /* 使用本地方法 */ int result = jniUtil.add(Integer.parseInt(a), Integer.parseInt(b)); Toast.makeText(MainActivity.this, "" + result, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "cannot be empty", Toast.LENGTH_SHORT).show(); } } });}

在這裡,獲取兩個 EditText 中的數字,然後呼叫 JniUtil 中的 add 方法,將返回資料 Toast 顯示出來

到此為止,Android NDK開發的第一步已經成功邁出!!!

總結

上面知識添加了一個 native 方法,如果根據又需要要新增多個 native 方法 ,那麼步驟基本相似,這裡將步驟重溫一遍當作總結

  • 初次使用 NDK 首先配置環境
  • 在 Java 類中新增 native 本地方法
    • 若初次編寫就新建一個 Java 類,若有則完全可以在同一個 Java 類中繼續新增 native 方法
  • 生成對應的標頭檔案
    • 手動輸入 javah 命令或使用 External Tool 工具
  • 利用生成的標頭檔案編寫 C/C++ 程式碼
    • 在jni目錄下新建C/CPP檔案,複製標頭檔案中的函式名進行編寫以防出錯。記住要包含對應的標頭檔案 #include "*.h"
  • 生成 so 庫
    • 保證配置好了 Gradle
    • 這裡必須有對應的 Android.mk 和 Application.mk ,否則無法成功
  • 將生成的 so 庫全部複製到 jniLibs 目錄下( 若有檔案就覆蓋掉原來 )
    • 使用前保證已經添加了靜態程式碼塊,其中的 System.loadLibrary("");中的字串是之前定義的 moduleName 也是 so 庫的名字(去掉前面的 lib 和 後面的 .so ),這裡務必對應正確
  • 使用 native 方法

相關推薦

Android NDK開發0到1

本文的開發環境為 Windows,其他平臺操作類似 其實說到 NDK 就不得不提 JNI ( Java Native Interface ) ,JNI 是專門用來與原生代碼進行互動而提供的一個介面。通過 JNI 就可以呼叫 C/C++ 所編寫的原生代碼。 而 NDK ( Native Developme

Android NDK開發環境搭建到Demo級十步流

寫在正文之前: 幾個月沒有更新部落格,感覺有點生疏了,所以說不能斷,一斷人就懶。 其實這幾個月也並不是什麼事也沒有做,俺可是時刻想著今年的任務呢,10本書,30篇博文…,這幾個月間斷性的也是在學習中,學H5,學設計模式,以及NDK JNI開發等等。 學習J

Android studio NDK開發 入門到實踐二

這一節開始將,怎麼將公司扔給你的c程式碼封裝成android的so庫呢. 1.給自己的c程式碼新增log c程式碼中的log,在ndk開發中,他是不能直接列印到我們android studio中. https://www.cnblogs.com/0616--ataozhij

Android NDK 開發總結

設置 .text nbsp def runt 編寫 abi 文件的 targe 一.安裝配置環境 1.安裝Android Studio,下載路徑https://developer.android.com/studio/index.html?hl=zh-cn。我下載的是Win

android NDK開發中,用Cygwin調試本地代碼時報錯“Another debug session running,Use --force to kill it”原因及解決的方法

能夠 att cati kill 時報 andro 使用 deb gdb調試 在使用ndk-gdb調試的時候。運行$NDK/ndk-gdb --verbose報錯“Another debug session running,Use --force to kil

android應用開發-設計到實現 3-4 靜態原型的狀態欄

不同的 討論 group 手把手教你 copy lac csdn article statusbar 靜態原型的狀態欄 狀態欄Symbol 狀態欄似乎非常復雜,有wifi信號、手機信號、時間、電量等信息,幸好Sketch原生就自帶的現成組件,你能

Android NDK開發及OpenCV初步學習筆記

-a cep cto strip 鏈接 jni 加載 idt jniexport https://www.jianshu.com/p/c29bb20908da Android NDK開發及OpenCV初步學習筆記 Super_聖代 關註 2017.08.19 00:

Android NDK開發 Android JNI專案建立

本篇文章只介紹android ndk在windows系統的編譯環境配置方法 更新於2015年1月11日 將更加詳細的介紹一個基本的Android Jni專案的建立。 步驟一:下圖是必須的,配置好這一步驟就可以進行Android JNI專案的建立了。 步驟二:新建一個Andr

Android NDK 開發教程三 Hello JNI 示例

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

我的新書《Android App開發入門到精通》終於出版啦

前言 經過了兩年多終於完成了這本書,2016年9月份開始寫的,到今天為止2年零2個月,本書的內容大部分是去年完成的,看過我去年總結的讀者可能知道,去年事情很多太忙了,導致本命年這本書沒有上架(有點小小的遺憾)。 工作之餘喜歡寫寫技術文章,對自己的技術做一個總結同時也讓後來者站在我的肩膀上,之前一直在CSD

Android NDK開發(一)CMake構建工具使用

  一、Android studio中需要的外掛:     CMake     LLDB     NDK   二、專案配置      ①build.gardle的配置  :多了兩個externalNativeBuild :def

ubuntu14.04搭建Android-NDK開發環境

1.建立Android平臺工作空間 mkdir AndroidWorkSpace 2.進入Android平臺工作空間,建立NDK工具目錄 cd AndroidWorkSpace mkdir NDK_Tools 3.獲取android-ndk-r10b 下載:wget ht

Android NDK開發之引入第三方庫

在Android開發中我們經常要把一些比較看重安全或者計算效率的東西通過JNI呼叫C/C++程式碼來實現,如果需要實現的功能簡單或者你的C/C++程式碼能力比較強,但是目前還是有很多功能強大的第三方庫的,比如openssl、FFmpeg等,呼叫這些第三方實現顯然比重複造輪子實際的多。 本教程適合將原始的動態

Android NDK開發掃盲及最新CMake的編譯使用

本篇文章旨在簡介 Android 中 NDK 是什麼以及重點講解最新 Android Studio 編譯工具 CMake 的使用 1 NDK 簡介 在介紹 NDK 之前還是首推 Android 官方 NDK 文件。傳送門 官方文件分別從以下幾個方面介紹了 NDK ND

android ndk開發crash崩潰定位:

android使用ndk開發crash崩潰定位: 1、法一:使用ndk-stack輸出呼叫堆疊    cd /home/hk/Android-Develop/android-ndk-r12b 將log.txt放在這個目錄    ./ndk-stack -sym

NDK開發 入門到放棄(一:基本流程入門瞭解)

一、前言 ● NDK Native Development Kit(NDK)是一系列工具的集合。它提供了一系列的工具,幫助開發者快速開發C/C++的動態庫,並能自動將so和java一起打包成apk。 ● JNI Java Native Interface(JNI)標準是java平臺的

android NDK 開發(2)

java向native層傳遞引數 1.傳遞基本型別引數,可以直接使用 //Java層定義介面 public native static int sum(int a, int b); //.c檔案native層實現 JNIEXPORT jint JNICALL Java_c

android NDK 開發(3)

1.native層呼叫java層static方法 //java層定義方法 public native static void request(); //native層執行完request後回撥此方法 public static void nativeCallBack()

Android NDK開發之旅(6):JNI函式完全解析與專案實戰

對於基本型別而言,JNI與Java之間的對映是一對一的,比如Java中的int型別直接對應於C/C++中的jint;而對引用型別的處理卻是不同的,JNI把Java中的物件當作一個C指標傳遞到本地函式中,這個指標指向JVM中的內部資料結構,而內部資料結構在記憶體

Android NDK開發之CMake

知之為知之,不知為不知 哇!(先來個王者之哇助助興),最近的專案一直用到Android NDK,簡直頭皮發麻,每次底層出現問題,都要找同事幫忙,甚是尷尬,於是看一些帖子,稍微整理了一下,做個小筆記,同時也分享一下前人之經驗.不說了,開始進入正題. Android開發環境 工具:And