1. 程式人生 > >Android Studio配置CMake開發NDK

Android Studio配置CMake開發NDK

1.在SDK Tools中勾選安裝CMake、LLDB、NDK

Paste_Image.png

2.配置一些快捷方式

引數講解

    javah   用於生成標頭檔案
    Program:$JDKPath$/bin/javah
    Parameters:-d ../jni -jni $FileClass$
    Working directory:$SourcepathEntry$\..\java
    ndk-build   用於構建so包
    注意:MAC/Linux用ndk-build,沒有.cmd字尾
    Program:D:\adt\sdk\ndk-bundle\ndk-build
.cmd Parameters:什麼都不用填 Working directory:$ModuleFileDir$\src\main

Paste_Image.png

Paste_Image.png

3.在工程的local.properties檔案中配置NDK的目錄

sdk.dir=C\:\\Users\\yuxue\\AppData\\Local\\Android\\sdk
ndk.dir=C\:\\Users\\yuxue\\AppData\\Local\\Android\\sdk\\ndk-bundle

Paste_Image.png

也可以使用圖形介面,單擊模組選擇Open Moude Setting,選擇好NDK的路徑

Paste_Image.png

4.編譯時如果檢查NDK過時了可以在gradle.properties檔案中增加“android.useDeprecatedNdk=true”使它可以使用過時的NDK

android.useDeprecatedNdk=true

5.建立CMakeLists.txt檔案並放在模組的的根目錄

# 設定構建本地庫所需的最小版本的cbuild。
cmake_minimum_required(VERSION 3.4.1)
# 建立並命名一個庫,將其設定為靜態
#  或者共享,並提供其原始碼的相對路徑。
# 您可以定義多個庫,而cbuild為您構建它們。
#  Gradle自動將共享庫與你的APK打包。
add_library( hello-lib  #設定庫的名稱。即SO檔案的名稱,生產的so檔案為“libhello-lib.so”,在載入的時候“System.loadLibrary("hello-lib");”
SHARED # 將庫設定為共享庫。 src/main/jni/hello.cpp # 提供一個原始檔的相對路徑 src/main/jni/helloJni.cpp # 提供同一個SO檔案中的另一個原始檔的相對路徑 ) #搜尋指定的預構建庫,並將該路徑儲存為一個變數。因為cbuild預設包含了搜尋路徑中的系統庫,所以您只需要指定您想要新增的公共NDK庫的名稱。cbuild在完成構建之前驗證這個庫是否存在。 find_library(log-lib # 設定path變數的名稱。 log # 指定NDK庫的名稱 你想讓CMake來定位。 ) #指定庫的庫應該連結到你的目標庫。您可以連結多個庫,比如在這個構建指令碼中定義的庫、預構建的第三方庫或系統庫。 target_link_libraries( hello-lib #指定目標庫中。與 add_library的庫名稱一定要相同 ${log-lib} # 將目標庫連結到日誌庫包含在NDK。 ) #如果需要生產多個SO檔案的話,寫法如下 add_library( natave-lib #設定庫的名稱。另一個so檔案的名稱 SHARED # 將庫設定為共享庫。 src/main/jni/nataveJni.cpp # 提供一個原始檔的相對路徑 ) target_link_libraries( natave-lib #指定目標庫中。與 add_library的庫名稱一定要相同 ${log-lib} # 將目標庫連結到日誌庫包含在NDK。 )

6.在模組的build.gradle檔案中新增

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.0"
    defaultConfig {
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

Paste_Image.png

7.編寫Java中的Native方法

    public native  String getStr();
    public native  String gethelloJniStr();

#8.生成C的標頭檔案

Paste_Image.png
生成後

Paste_Image.png

9.編寫C函式

#include "stdio.h"
#include "jni.h"
#include "string"
extern  "C"
JNIEXPORT jstring JNICALL Java_com_ljp_learnandroidadvanced_MainActivity_getStr
        (JNIEnv *env,
jobject jobject1){
    return env->NewStringUTF("hello world from cpp");
}

Paste_Image.png

至此,這個專案就可以運行了

更多的學習

Paste_Image.png

(1) .externalNativeBuild資料夾:用於存放cmake編譯好的檔案,包括支援的各種硬體等資訊,有點類似於build.gradle檔案明確Gradle如何編譯APP;
(2) cpp資料夾:存放C/C++程式碼檔案,native-lib.cpp檔案預設生成的;
(3) CMakeLists.txt:cmake指令碼配置檔案,cmake會根據該指令碼檔案中的指令去編譯相關的C/C++原始檔,並將編譯後產物生成共享庫或靜態塊,然後Gradle將其打包到APK中。

CMakeLists.txt檔案解析如下:

# 指定cmke版本  
cmake_minimum_required(VERSION3.4.1)  
# add_library()命令用於向CMake新增依賴原始檔或庫  
# 指令需傳入三個引數(函式庫名稱、庫型別、依賴原始檔相對路徑)  
add_library(  # 生成函式庫的名稱,即libnative-lib.so或libnative-lib.a(lib和.so/.a預設預設)  
             native-lib  
             # 生成庫型別:動態庫為SHARED,靜態庫為STATIC  
             SHARED  
             # 依賴的c/cpp檔案(相對路徑)  
             src/main/cpp/native-lib.cpp )  
# find_library()命令用於定位NDK中的庫  
# 需傳入兩個引數(path變數、ndk庫名稱)  
find_library(  # 設定path變數的名稱,這裡為NDK中的日誌庫  
              log-lib  
                            #指定cmake查詢庫的名稱  
                            #即在ndk開發包中查詢liblog.so函式庫,將其路徑賦值給log-lib  
              log )  
#target_link_libraries()命令用於指定要關聯到的原生庫的庫  
target_link_libraries(# 指定目標庫,與上面指定的函式庫名一致  
                  native-lib  
                  # 連結的庫,根據log-lib變數對應liblog.so函式庫  
                  ${log-lib} )  
通過檢視native-lib.cpp方法,stringFromJNI目的是向Java層返回一個字串。如果要在native-lib.cpp檔案中新增新的方法,必須新增在extern”C” { } 中,或者在每個方法前加extern”C”, 否則會報找不到方法。如果原始檔為C,則須將extern“C”部分去掉,因為extern “C”的作用就是告訴編譯器以C方式編譯。

JNI開發列印日誌

#include <android/log.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

LOG的使用
    LOGD("TAGD,a=%d,b=%d",a,b);
    LOGI("TAGI,a=%d,b=%d",a,b);
    LOGW("TAGW,a=%d,b=%d",a,b);
    LOGE("TAGE,a=%d,b=%d",a,b);

Paste_Image.png

Android系統目前支援的CPU架構

ARMv5,ARMv7 (從2010年起)
x86 (從2011年起)
MIPS (從2012年起)
ARMv8,MIPS64和x86_64 (從2014年起)
每一個CPU架構對應一個ABI
CPU架構           ABI
ARMv5   --->    armeabi
ARMv7   --->    armeabi-v7a
x86     --->    x86
MIPS    --->    mips
ARMv8   --->    arm64-v8a
MIPS64  --->    mips64
x86_64  --->    x86_64
armeabi:預設選項,將建立以基於ARM* v5TE 的裝置為目標的庫。 具有這種目標
的浮點運算使用軟體浮點運算。 使用此ABI(二進位制介面)建立的二進位制程式碼將可以
在所有 ARM*裝置上執行。所以armeabi通用性很強。但是速度慢
armeabi-v7a:建立支援基於ARM* v7 的裝置的庫,並將使用硬體FPU指令。
armeabi-v7a是針對有浮點運算或高階擴充套件功能的arm v7 cpu。
mips:MIPS是世界上很流行的一種RISC處理器。MIPS的意思是“無內部互鎖流水級
的微處理器”(Microprocessor without interlocked piped stages),其機
制是儘量利用軟體辦法避免流水線中的資料相關問題。
x86:支援基於硬體的浮點運算的IA-32 指令集。x86是可以相容armeabi平臺執行
的,無論是armeabi-v7a還是armeabi,同時帶來的也是效能上的損耗,另外需要
指出的是,打包出的x86的so,總會比armeabi平臺的體積更小。
總結
如果專案只包含了 armeabi,那麼在所有Android裝置都可以執行;
如果專案只包含了 armeabi-v7a,除armeabi架構的裝置外都可以執行; 
如果專案只包含了 x86,那麼armeabi架構和armeabi-v7a的Android裝置是無法
執行的;
如果同時包含了 armeabi,armeabi-v7a和x86,所有裝置都可以執行,程式在運
行的時候去載入不同平臺對應的so,這是較為完美的一種解決方案,同時也會導致
包變大。