0 前言
同NDK
技術的淵源始於3年前,使用so
文件的時候了解到NDK
技術,並且C
語言一直是強項,就鼓搗起NDK
開發。在AndroidStduio
還沒推廣的年代,基於eclipse
搭建NDK
開發環境需要安全依賴開發工具,並且調試起來具備難度。隨后AndroidStudio
也先后支持nkd-build
和cmake
使用NDK
開發。
參見之前的博客:
1 准備工作
1.1 下載 NDK
當前 NDK 穩定版已經 發布到 r15c
。附上各個平台的下載地址:
1.2 添加NDK依賴
解壓下載好的文件在本地,在 AndroidStudio 工程配置(注意不是 AndroidStudio 工具配置)中指定 NDK 路徑。

或者在local.properties
文件中指定NDK路徑。

1.3 添加cmake支持
在 AndroidStudio 工具配置中,選擇 Android SDK -> SDK Tools 中,勾選CMake並安裝。

2 新建支持NDk工程
現在的AndroidStduio
更支持一種極簡方式集成NDK
開發支持,即在下圖中勾選include C++ support
。然后選擇C++
標准。如C++ 11
。建選默認的ToolChain Default
。


之后正常 run 即可將 C 語言部分生成出 so 文件並打包到 apk 文件中。
3 給工程添加NDK支持
上述方式適合在新的工程中添加 NDK 支持。如何要在現有的項目中添加 NDK 支持,現提供 cmake
和 ndk-build
兩種方式。
由於在同一個工程中,同時支持 cmake
和 ndk-build
兩種方式編譯 so 文件,因此將 C 源碼單獨放在 cpp-src
目錄。且將 cmake
、ndk-build
區分不同的module
進行編譯。

3.1 cmake
這是目前最受歡迎的集成方式,AndroidStduio 在創建新工程時默認使用該方式添加 NDK 支持。但在現有的工程中添加 NDK支持,需要手動配置。

創建 cmake module 添加個三個文件。
- CMakeLists.txt cmake編譯配置文件
cmake_minimum_required(VERSION 3.4.1)
add_library(
hello-jni # so 庫的名稱 libhello-jni.so
SHARED # 設置為分享庫
# 指定C源文件的路徑,指向公共cpp-src目錄
../../../../cpp-src/hello-jni.c
)
find_library(
log-lib # 設置路徑變量名稱
log # 指定CMake需要加載的NDK庫
)
# 鏈接hello-jni庫依賴的庫,注意下面變量名的配置 target_link_libraries(hello-jni ${log-lib} ) 復制代碼
- AndroidManifest.xml 每個module必須的配置文件,指定packageName。
<?xml version="1.0" encoding="UTF-8" ?> <manifest package="com.flueky.cmake"> </manifest> 復制代碼
- Build.gradle 每個module必須的配置文件,用於構建項目。
apply plugin: 'com.android.library' android { compileSdkVersion 28 defaultConfig{ externalNativeBuild { cmake { // 指定配置參數,更多參數設置見 https://developer.android.google.cn/ndk/guides/cmake arguments "-DCMAKE_BUILD_TYPE=DEBUG" // 添加CPP標准 // cppFlags "-std=c++11" } } } externalNativeBuild { cmake { // 指定CMake編譯配置文件路徑 path "src/main/cpp/CMakeLists.txt" } } } 復制代碼
關於 CMake 編譯參數的設置,更多內容請閱讀官方資料。
眼尖的小伙伴已經發現兩處配置了 externalNativeBuild
。其中第二處的externalNativeBuild
配置是生成Gradle Task
可以不運行工程,直接在 ndk-cmake -> Tasks -> other 找到編譯 so 文件有關的四個任務。


雙擊 exeternalNativeBuildDebug
執行任務,如圖:

根據路徑即可找到生成的so文件。
3.2 ndk-build
這是最傳統的 ndk 編譯方式。在配置得當的情況下,可以在不打開 AndroidStudio 情況下完成so文件的編譯和輸出。

創建 ndk-build module ,添加4個文件。
# 講真,這個參數我看不懂。從 官方demo 抄來的。用於指定源文件的時候使用 abspath_wa = $(join $(filter %:,$(subst :,: ,$1)),$(abspath $(filter-out %:,$(subst :,: ,$1)))) # 指定當前路徑 LOCAL_PATH := $(call my-dir) # 指定源文件路徑 JNI_SRC_PATH := $(call abspath_wa, $(LOCAL_PATH)/../../../../cpp-src) # 聲明 clear 變量 include $(CLEAR_VARS) # 指定 so 庫的名稱 libhello-jni.so LOCAL_MODULE := hello-jni # 指定 c 源文件 LOCAL_SRC_FILES := $(JNI_SRC_PATH)/hello-jni.c # 添加需要依賴的NDK庫 LOCAL_LDLIBS := -llog -landroid # 指定為分享庫 include $(BUILD_SHARED_LIBRARY) 復制代碼
關於 Android.mk 編譯參數的設置,更多內容請閱讀官方資料
# 指定編譯的的so版本 APP_ABI := all # 指定 APP 平台版本。比 android:minSdkVersion 值大時,會有警告 APP_PLATFORM := android-28 復制代碼
關於 Application.mk 編譯參數的設置,更多內容請閱讀官方資料
- AndroidManifext.xml
<?xml version="1.0" encoding="UTF-8" ?> <manifest package="com.flueky.ndk"> </manifest> 復制代碼
- build.gradle
apply plugin: 'com.android.library' android { compileSdkVersion 28 externalNativeBuild { ndkBuild { // 指定mk文件路徑 path 'src/main/jni/Android.mk' } } defaultConfig { } } 復制代碼
上面的externalNativeBuild
作用同 CMake
方式的一樣,用於編譯生成 so 文件。 但是 ndk-build 還支持使用命令ndk-build
編譯 so 文件。 需要將 NDK 路徑添加至環境變量。
需要在jni
目錄下執行該命令:

最后生成的so文件路徑如圖;

4 實踐
4.1 生成頭文件
在主 module 中的 MainActivity中添加 native 方法 。使用 javah 編譯出頭文件。 使用 -d
參數指定頭文件的輸出目錄。
public class MainActivity extends Activity { static { // 加載 JNI 庫 System.loadLibrary("hello-jni"); } ...... // 聲明 Native 方法 private native String hello(); } 復制代碼
在 app/src/main/java
目錄下執行命令 javah

4.2 編寫 C 源碼
在hello-jni.c
文件引用生成的頭文件,並編寫測試代碼。
#include <string.h> #include <jni.h> #include "com_flueky_demo_MainActivity.h" #include "util/log.h" /** * JNI 示例,演示native方法返回一個字符串,Java 源碼見 * * ndk-sample/app/src/main/java/com/flueky/demo/MainActivity.java */ JNIEXPORT jstring JNICALL Java_com_flueky_demo_MainActivity_hello( JNIEnv* env, jobject thiz ) { #if defined(__arm__) #if defined(__ARM_ARCH_7A__) #if defined(__ARM_NEON__) #if defined(__ARM_PCS_VFP) #define ABI "armeabi-v7a/NEON (hard-float)" #else #define ABI "armeabi-v7a/NEON" #endif #else #if defined(__ARM_PCS_VFP) #define ABI "armeabi-v7a (hard-float)" #else #define ABI "armeabi-v7a" #endif #endif #else #define ABI "armeabi" #endif #elif defined(__i386__) #define ABI "x86" #elif defined(__x86_64__) #define ABI "x86_64" #elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ #define ABI "mips64" #elif defined(__mips__) #define ABI "mips" #elif defined(__aarch64__) #define ABI "arm64-v8a" #else #define ABI "unknown" #endif LOGD("日志輸出示例"); return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI "."); } 復制代碼
4.3 運行截圖
頁面截圖:

日志截圖:

5 源碼獲取
工程源碼已開放在GitHub,下載地址。 可以直接編寫 C 源碼並進行調試和生成 so 文件。 Google 官方資料需要翻牆才可以閱讀。想了解翻牆方法,請點SSR。