之前對NDK開發一直是個小白,最近花了幾天時間研究得到的一些理解在此做個記錄分享。結論不足之處拒絕反駁,所有觀點僅單方面宣布,后果自負。*.*,本文出處:http://www.lxweimin.com/p/201046751a7c
一、什么是NDK?
NDK全稱是Native Development Kit(原生開發工具包),NDK提供了一系列的工具,幫助開發者快速開發C(或C++)的動態庫,并能自動將so和java應用一起打包成apk。也就是說它是一個“開發工具包”,就像SDK一樣,區別就在于SDK是面向java開發者的工具集合,而NDK面向的則是C/C++開發者的工具集合(包括對c/c++源碼的打包編譯工具ndk,一些h頭文件等)。附上官方NDK工具包的下載路徑:官網ndk下載,需要翻墻。
二、為什么需要使用NDK?
1.代碼的保護。由于apk的java層代碼很容易被反編譯,而C/C++庫反匯難度較大。
2.可以方便地使用現存的開源庫。大部分現存的開源庫都是用C/C++代碼編寫的。
3.提高程序的執行效率。將要求高性能的應用邏輯使用C開發,從而提高應用程序的執行效率。
4.便于移植。用C/C++寫得庫可以方便在其他的嵌入式平臺上再次使用。
三、JNI、SO介紹
JNI 全稱Java Native Interface,這套技術的機制是用于java訪問c/c++代碼而產生的,說白了NDK開發的核心就是JNI開發,利用java代碼來調用遵循JNI規范的c/c++的方法實現某個功能。
so全稱Shared Object,本地原生庫,先暫時理解為java中的jar包,所有的c/c++的代碼在android(Linux)平臺中最終都會編譯成so庫,然后才能被調用。所以ndk開發所編寫出的c/c++代碼最終的目的都是為了獲得這個so庫,與java方法形成jni的映射關系從而實現調用。
四、開始擼碼
本文使用Android Studio2.0進行演示HelloJni
? 大概步驟:
? ? ?1. java文件中聲明native方法,和java方法聲明一樣,在此基礎上加了natvie修飾。
? ? ?2. 利用javah命令生成與該類對應的頭文件(包含方法信息)
? ? ?3. 根據頭文件的信息編寫c源代碼文件
? ? ?4. 在app\build.gradle文件中配置ndk的編譯信息
? ? ?5. 配置NDK工具包路徑,編譯運行
創建項目:HelloJni
1. 定義一個java類SayHello,并在里面聲明一個靜態無參native方法speak,并且創建jni文件夾
2. 利用javah命令生成與該類對應的jni頭文件,生成的頭文件的目的主要是用來編寫c/c++源文件
3.根據.h頭文件的信息編寫c源代碼文件 : 創建SayHello.c文件,把頭文件里的方法copy到該文件中,并修改成實體方法,下面則是返回一段字符串。如果熟悉了jni方法名稱命名規范,完全可自己手寫,生成頭文件的步驟也可跳過。親測發現如果包名帶有數字的命名規則不好把握,所以建議用javah生成。
4.在build.gradle中配置ndk的編譯信息,配置完成保存同步之后可能出現錯誤,添加 android.useDeprecatedNdk=true 到gradle.properties 文件中即可解決。
5.配置下載好的NDK工具包:File->Project Structure->SDK Location(文件路徑\android-ndk-r14b目錄配置到系統環境變量中,以備后面使用)
然后回到在java文件中,加載buil.gradle中配置的moduleName的類庫名稱,這里配置為:SayHello
最后在MainActivity中測試該方法。
運行。
至此,體驗了一把基本的ndk開發過程。不過洗腦還沒有結束:
在運行完成之后,我們并沒有發現工程目錄中有so庫文件,其實這個so庫文件是在運行之后直接打包到了apk文件中的lib目錄下了
由于我們在build.gradle配置了abiFilters打包時只打包x86的文件夾中的so庫。所以我們在apk中只看到x86的文件夾,里面存放的就是so庫,如果不配置abiFilters,那么將會出現android支持的7種abi,可參見該文章理解ABI。
因為我們可以調用so這個庫,顯然這個so庫是根據我們在jni文件夾下編寫的源文件編譯生成的,如果我們沒有配置,gradle默認就會去編譯jni的文件夾下的c/c++的代碼生成so庫,這個路勁就是src/main/jni,如果這個文件夾沒有文件即使配置了ndk{...}信息也不會生成so庫,當然gradle還提供自定義配置,下面就看看如何配置:
sourceSets{ ?main{ ? ?jin.srcDirs=["src/mian/jni"] ?//默認路徑,jin.srcDirs指的是需要加入編譯的jni的路徑,可以自己修改路徑的 ?} }
這個apk安裝到x86 abi手機上之后,so庫會安裝在data\app\包名-數值\ib目錄下(可通過Device Monitor工具查看),所以由此可判斷System.loadLibrary()加載的庫默認是這個路徑下的庫,也可以調用System.load(data\app\包名-數值\ib\abi\libxx.so)加載絕對路徑的so庫。
所以java代碼能不能正確的執行so庫里的內容取決于so庫能否被正確的安裝。如果未能正確安裝,當虛擬機去System.loadLibrary時就會報錯java.lang.UnsatisfiedLinkError。
上面的做法只有在打包時才能得到so庫,下面就介紹通過ndk開發工具包里的ndk-build單獨來編譯出so庫,這種方式就無需在build.gradle中配置ndk{...}了。
1. 在jni文件家中新建android.mk編譯配置文件,參見Android.mk詳細配置
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE ? ?:= SayHello
LOCAL_SRC_FILES := SayHello.c
include $(BUILD_SHARED_LIBRARY)
2.在jni文件家中新建application.mk配置需要生成支持的abi so庫,參見Application.mk詳細配置
APP_CFLAGS += -Wno-error=format-security
APP_ABI := all
這里配置支持所有的abi。
3.在terminal中調用ndk-build工具生成so庫
我們可以看到在main文件夾下生成了libs目錄,并且生成了支持所有abi的so庫,到此生成so完畢;現在任務就是要讓這些so庫打包到apk文件中的libs目錄下,在build.gradle中配置sourceSets的另一個屬性jniLibs.srcDirs,配置的路徑下的so庫文件都會打包到apk文件中,其默認值為app/libs,所以也可以把這些so文件拷到app/libs中而不配置這個屬性用其默認值。
上圖中不配置jni.srcDirs的路徑的作用是為了打包時不讓編譯系統再去編譯得到so庫(因為我們已經單獨生成),雖然上面ndk沒有被配置,但是只要的配置這個路徑下有c文件就會生成so庫,并且名字為libapp.so,這樣一來就造成了相同的包存在兩個增加app的體積。
最后build apk看看apk里有沒有so庫:
運行,大功告成。
下一篇文章介紹:第三方so庫的調用