以前學了一點NDK相關的知識,這篇文章主要總結回顧一下。NDK是一種幫助我們開發C/C++代碼的工具,NDK的開發包里包含很多交叉編譯的工具。一般情況下我們開發Android應用,只需要用到Java代碼,調用Android API即可,因此NDK在我們應用開發中很少接觸,下面從幾個方面詳細介紹一下NDK。
一、NDK使用場景
NDK到底在什么情況下使用呢?一般用于以下三種情形:
- 提高效率:C/C++編寫的代碼較Java編寫的代碼效率高,例如需要對大量數據進行排序時
- 使用現有第三方C/C++庫:大部分開源庫都是用C/C++編寫的,例如:人臉識別、音視頻處理開源庫、圖形圖像處理開源庫,如OpenCV等。
- 底層程序設計,便于移植:用C/C++代碼編寫的庫可以方便地在其他嵌入式平臺上再次使用。例如想開發一個在Android和IOS平臺都能使用的庫,就需要用到C/C++來編寫。
二、什么是交叉編譯
一般我們編寫的程序很容易就在電腦上編譯執行,這是因為我們的電腦的內存及CPU都能提供充足的空間和處理能力,但是在一般的嵌入式設備中開發就沒那么簡單了。由于嵌入式設備只有有限的空間和處理資源,因此很多情況下我們無法在嵌入式設備上編譯程序,由此就有了交叉編譯的概念。
交叉編譯就是在一個平臺上生成另一個平臺上可執行的代碼,例如我們的電腦是X86平臺,手機是arm平臺,那么我們可以在電腦的x86平臺上編譯出來我們的應用程序,安裝到我們手機的arm平臺來運行,ndk開發包中就提供了這樣的交叉編譯工具。
三、什么是JNI
JNI(Java Native Interface,本地調用)提供了若干的API實現了Java和其他語言的通信(主要是C&C++),JNI標準已經成為了Java平臺的一部分。
JNI的實現流程如下:
四、什么是鏈接庫
“庫”是成熟的、可復用的代碼,我們可以將“庫”理解為一個文件,這個文件可以在編譯時由編譯器直接鏈接到可執行程序中,也可以在運行時由操作系統的runtime enviroment根據需要動態加載到內存中。
很多時候我們程序員寫代碼都會依賴很多底層的庫,庫的存在不僅節約了很多時間,也保證了我們開發的穩定性,很多時候我們沒有那么多的精力和能力來將所有的代碼從頭開始寫,因此庫的存在是很有意義的。
本質上來說庫是一種可執行代碼的二進制形式,可以被操作系統載入內存執行。
庫有兩種:靜態庫(.a、*.lib)和動態庫(.so、.dll)。windows上對應的是.lib和.dll。linux上對應的是.a和.so。
我們知道,將一個程序編譯成可執行程序有如下幾個步驟:
所謂靜態、動態是指鏈接。靜態庫與動態庫的區別在于鏈接階段如何處理庫:
- 靜態鏈接庫:將匯編生成的目標文件.o與引用到的庫一起鏈接打包到可執行文件中。它的特點是體積大、可移植性好,對庫的鏈接是編譯時完成的。
- 動態鏈接庫:在編譯時只編譯自己的資源,運行時動態查找所需資源。它的特點是體積小、可共享,但可能出現所需文件找不到的情況。
五、動手試試吧
首先下載ndk開發包,可以去官網上下載相應的版本:下載地址,下載好以后配置環境,這里就不贅述了。
1.eclipse下ndk的使用##
以前用NDK都是在eclipse下,主要有以下個步驟:
(1)新建Android項目,在MainActivity中添加native方法,例如:
public static native String getStringFromC();
(2)在項目工程下創建jni目錄,并借助javah命令生成C頭文件到jni目錄下,在cmd中敲如下指令即可生成對應頭文件:
生成的頭文件可以看見其中與native函數有關的函數定義:
/*
* Class: com_example_hellondk_MainActivity
* Method: getStringFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_hellondk_MainActivity_getStringFromC
(JNIEnv *, jclass);
(3)在jni目錄下寫.c文件來寫具體的實現代碼,并在該C文件中引入(2)中生成的頭文件
#include<stdio.h>
#include<stdlib.h>
#include "com_example_hellondk_MainActivity.h"http://引入(2)中生成的頭文件
JNIEXPORT jstring JNICALL Java_com_example_hellondk_MainActivity_getStringFromC
(JNIEnv * env, jclass jclass){ // 函數的具體實現
return(*env)->NewStringUTF(env,"Get Hello string from C!");
}
(4)在jni下創建Android.mk文件,指定編譯哪個C文件
LOCAL_MODULE : = hello
LOCAL_SRC_FILES : = hello.c
(5)執行ndk-build命令,生成obj目錄并生成libhello.so(在obj目錄下)
(6)在MainActivity中寫靜態代碼塊:
static{
System.loadLibrary("hello");//導入生成的庫libhello.so
}
最終我們的MainActivity.java如下,調用名為“hello”的動態庫里的函數getStringFromC(),獲得其返回字符串顯示在頁面的TextView中:
最后我們運行一下我們的應用程序可以看到,我們的java層成功地調用了我們的動態庫中的獲取字符串函數并將我們設置的字符串顯示了出來。
注:我們可以編譯不同平臺的.so文件,通過在jni目錄下建立Application.mk文件來控制。
當Application.mk文件中加入:APP_ABI := all時,生成所有四個版本的so文件,如下圖所示。
若只想生成arm平臺下的so文件,可將Application.mk文件中APP_ABI := all注釋,如下圖:
2.Android Studio下ndk的使用##
用AndroidStudio也來試試吧!我使用的Android Studio版本是1.5,gradle版本是2.8
(1)首先新建一個工程,配置好NDK路徑:File->Project Structure
這樣設置ndk路徑的效果跟在工程的local.properties中添加如下配置語句是一樣的,這樣設置成功后會自動在該文件下生成這句配置語句。
ndk.dir=E\:\\androidStudio\\android-ndk-r13
(2)在app模塊的build.gradle文件中配置ndk相關信息,如下圖,此處配置了生成的庫的名字,也可以在這里進行更多的配置,比如編譯平臺的選擇、log功能的配置等等。
(3)在工程目錄下的 gradl.properties 文件里加上android.useDeprecatedNdk=true
(4)與在eclipse下的MainActivity代碼一樣,調用native的getHelloStringFromC()方法,這時候這個方法會報錯,因為還沒有與這個方法對應的本地方法
解決辦法很簡單,在app>src>main目錄下新建jni目錄
在報錯的本地方法調用函數上按Alt+Enter,便會在jni目錄下自動生成對應的.c文件
修改hello.c中的代碼即可,所有的都是自動生成的,是不是很方便?
若想生成對應的頭文件,也很簡單,借助javah命令,在Terminal中輸入如下語句即可:
javah -d [生成頭文件的存放路徑] -jni [調用對應native方法的源文件路徑]
3.ndk的使用小結##
從上面eclipse和Android Studio的使用可以看出,二者原理上并無差別,只是Android Studio的自動化程度更高,用起來更加方便,但是目前對NDK的支持仍處于試驗階段,可能不同版本的使用過程中會出現這樣或者那樣的錯誤。工具只是一種輔助,自己可以根據自己的使用習慣選擇合適的工具。有關NDK使用的進階學習可以查看我的另一篇博客:Anroid NDK初體驗(下)