NDK 知識(shí)梳理(1) - 使用 CMake 進(jìn)行 NDK 開發(fā)之初體驗(yàn)

一窜骄、前言

Eclipse的時(shí)代,我們進(jìn)行NDK的開發(fā)一般需要通過(guò)手動(dòng)執(zhí)行NDK腳本生成*.so文件摆屯,再將.so文件放到對(duì)應(yīng)的目錄之后邻遏,之后再進(jìn)行打包。

而如果使用的是Android Studio進(jìn)行NDK開發(fā)虐骑,在2.2的版本以后准验,我們可以不需要手動(dòng)地運(yùn)行NDK腳本來(lái)生成*.so文件,而是將這一過(guò)程作為Gradle構(gòu)建過(guò)程的依賴項(xiàng)廷没,事先編寫好編譯的腳本文件糊饱,然后在build.gradle中指定編譯腳本文件的路徑就可以一次性完成生成原生庫(kù)并打包成APK的過(guò)程。

目前這種AS + GradleNDK開發(fā)方式又可以分為三種:ndk-build颠黎、CMakeExperimental Gradle

  • ndk-build:和上面談到的傳統(tǒng)方式相比另锋,它們兩個(gè)的目錄結(jié)構(gòu)相同,Gradle腳本其實(shí)最終還是依賴于Android.mk文件盏缤,對(duì)于使用傳統(tǒng)方式的項(xiàng)目來(lái)說(shuō)砰蠢,比較容易過(guò)度蓖扑。
  • CMakeGradle腳本依賴的是CMakeLists.txt文件唉铜。
  • Experimental Gradle:需要引入實(shí)驗(yàn)性的gradle插件,全部的配置都可以通過(guò)build.gradle來(lái)完成律杠,不再需要編寫Android.mk或者CMakeLists.txt潭流,可能坑比較多竞惋,對(duì)于舊的項(xiàng)目來(lái)說(shuō)過(guò)度困難。

目前灰嫉,Android Studio已經(jīng)將CMake作為默認(rèn)的NDK實(shí)現(xiàn)方式拆宛,并且官網(wǎng)上對(duì)于NDK的介紹也是基于CMake,聲稱要永久支持讼撒。按照官方的教程使用下來(lái)浑厚,感覺這種方式有幾點(diǎn)好處:

  • 不需要再去通過(guò)javah根據(jù)java文件生成頭文件,并根據(jù)頭文件生成的函數(shù)聲明編寫cpp文件
  • 當(dāng)在Java文件中定義完native接口根盒,可以在cpp文件中自動(dòng)生成對(duì)應(yīng)的native函數(shù)钳幅,所需要做的只是補(bǔ)全函數(shù)體中的內(nèi)容
  • 不需要手動(dòng)執(zhí)行ndk-build命令得到so,再將so拷貝到對(duì)應(yīng)的目錄
  • 在編寫cpp文件的過(guò)程中炎滞,可以有提示了
  • CMakeLists.txt要比Android.mk更加容易理解

下面敢艰,我們就來(lái)介紹一下如何使用CMake進(jìn)行簡(jiǎn)單的NDK開發(fā),整個(gè)內(nèi)容主要包括兩個(gè)方面:

  • 創(chuàng)建支持C/C++的全新項(xiàng)目
  • 在現(xiàn)有的項(xiàng)目中添加C/C++代碼

二册赛、創(chuàng)建支持C/C++的全新項(xiàng)目

2.1 安裝組件

在新建項(xiàng)目之前钠导,我們需要通過(guò)SDK Manager安裝一些必要的組件:

  • NDK
  • CMake
  • LLDB

2.2 創(chuàng)建工程

在安裝完必要的組件之后,我們創(chuàng)建一個(gè)全新的工程森瘪,這里需要記得勾選include C++ Support選項(xiàng):


接下來(lái)一路Next牡属,在最后一步我們會(huì)看見如下的幾個(gè)選項(xiàng),它們的含義為:

  • C++ Standard:選擇C++的標(biāo)準(zhǔn)柜砾,Toolchain Default表示使用默認(rèn)的CMake配置湃望,這里我們選擇默認(rèn)。
  • Excptions Support:如果您希望啟用對(duì)C++異常處理的支持痰驱,請(qǐng)選中此復(fù)選框证芭。如果啟用此復(fù)選框,Android Studio會(huì)將-fexceptions標(biāo)志添加到模塊級(jí) build.gradle文件的cppFlags中担映,Gradle會(huì)將其傳遞到CMake废士。
  • Runtime Type information Support:如果您希望支持RTTI,請(qǐng)選中此復(fù)選框蝇完。如果啟用此復(fù)選框官硝,Android Studio會(huì)將-frtti標(biāo)志添加到模塊級(jí) build.gradle文件的cppFlags中,Gradle會(huì)將其傳遞到CMake短蜕。

在新建工程完畢之后氢架,我們得到的工程的結(jié)構(gòu)如下圖所示:



與傳統(tǒng)的工程相比,它有如下幾點(diǎn)區(qū)別:

(1) cpp 文件夾

用于存放C/C++的源文件朋魔,在磁盤上對(duì)應(yīng)于app/src/main/cpp文件夾岖研,當(dāng)新建工程時(shí),它會(huì)生成一個(gè)native-lib.cpp的事例文件,其內(nèi)容如下:

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_com_demo_lizejun_cmakenewdemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

(2) 增加 CMakeList.txt 腳本

構(gòu)建腳本孙援,在磁盤上對(duì)應(yīng)于app/目錄下的txt文件害淤,其內(nèi)容為如下圖所示,這里面涉及到的CMake語(yǔ)法包括下面四種拓售,關(guān)于CMake的語(yǔ)法窥摄,可以查看 官方的 API 說(shuō)明

  • cmake_minimum_required
  • add_library
  • find_library
  • target_link_libraries
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

(3) build.gradle 腳本

與傳統(tǒng)的項(xiàng)目相比,該模塊所對(duì)應(yīng)的build.gradle需要在里面指定CMakeList.txt所在的路徑础淤,也就是下面externalNativeBuild對(duì)應(yīng)的選項(xiàng)崭放。

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"
    defaultConfig {
        applicationId "com.demo.lizejun.cmakenewdemo"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    testCompile 'junit:junit:4.12'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
}

(4) 打印字符串

MainActivity中,我們加載原生庫(kù)鸽凶,并調(diào)用原生庫(kù)中的方法獲取了一個(gè)字符串展示在界面上:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
}

(5) 運(yùn)行結(jié)果

最后莹菱,我們運(yùn)行一下這個(gè)工程,會(huì)得到下面的結(jié)果:


通過(guò)APK Analyzer工具吱瘩,我們可以看到在APK當(dāng)中道伟,增加了libnative-lib.so文件:

2.3 原理

下面,我們來(lái)解釋一下這一過(guò)程:

  • 首先使碾,在構(gòu)建時(shí)蜜徽,通過(guò)build.gradlepath所指定的路徑,找到CMakeList.txt票摇,解析其中的內(nèi)容拘鞋。
  • 按照腳本中的命令,將src/main/cpp/native-lib.cpp編譯到共享的對(duì)象庫(kù)中矢门,并將其命名為libnative-lib.so盆色,隨后打包到APK中。
  • 當(dāng)應(yīng)用運(yùn)行時(shí)祟剔,首先會(huì)執(zhí)行MainActivitystatic代碼塊的內(nèi)容隔躲,使用System.loadLibrary()加載原生庫(kù)。
  • onCreate()函數(shù)中物延,調(diào)用原生庫(kù)的函數(shù)得到字符串并展示宣旱。

2.4 小結(jié)

當(dāng)通過(guò)CMake來(lái)對(duì)應(yīng)用程序增加C/C++的支持時(shí),對(duì)于應(yīng)用程序的開發(fā)者叛薯,只需要關(guān)注以下三個(gè)方面:

  • C/C++源文件
  • CMakeList.txt腳本
  • 在模塊級(jí)別的build.gradle中通過(guò)externalNativeBuild/cmake進(jìn)行配置

三浑吟、在現(xiàn)有的項(xiàng)目中添加C/C++代碼

下面,我們演示一下如何在現(xiàn)有的項(xiàng)目中添加對(duì)于C/C++代碼的支持耗溜。

(1) 創(chuàng)建一個(gè)工程

和第二步不同组力,這次創(chuàng)建的時(shí)候,我們不勾選nclude C++ Support選項(xiàng)抖拴,那么會(huì)得到下面這個(gè)普通的工程:

(2) 定義接口

這一次燎字,我們將它定義在一個(gè)單獨(dú)的文件當(dāng)中:

public class NativeCalculator {

    private static final String SELF_LIB_NAME = "calculator";

    static {
        System.loadLibrary(SELF_LIB_NAME);
    }

    public native int addition(int a, int b);

    public native int subtraction(int a, int b);
}

這時(shí)候因?yàn)闆]有找到對(duì)應(yīng)的本地方法,因此會(huì)有如下的錯(cuò)誤提示:


(3) 定義 cpp 文件

在模塊根目錄下的src/main/新建一個(gè)文件夾cpp,在其中新增一個(gè)calculator.cpp文件轩触,這里,我們先只引入頭文件:

#include <jni.h>

(4) 定義 CMakeLists.txt

在模塊根目錄下新建一個(gè)CMakeLists.txt文件:

cmake_minimum_required(VERSION 3.4.1)

add_library(calculator SHARED src/main/cpp/calculator.cpp)

(5) 在 build.gradle 中進(jìn)行配置

之后家夺,我們需要讓Gradle腳本確定CMakeLists.txt所在的位置脱柱,我們可以在CMakeLists.txt上點(diǎn)擊右鍵,之后選擇Link C++ Project with Gradle


那么在該模塊下的build.gradle就會(huì)新增下面這句:

在這步完成之后拉馋,我們選擇Build -> Clean Project榨为。

**(6) 實(shí)現(xiàn) C++ **

在配置完上面的信息之后,會(huì)發(fā)現(xiàn)一個(gè)神奇的地方煌茴,之前我們定義native接口的地方随闺,多出了一個(gè)選項(xiàng),它可以幫助我們直接在對(duì)應(yīng)的C++文件中生成函數(shù)的定義:


點(diǎn)擊Create function之后蔓腐,我們的calculator.cpp文件變成了下面這樣:

#include <jni.h>

extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_addition(JNIEnv *env, jobject instance, jint a, jint b) {
    
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_subtraction(JNIEnv *env, jobject instance,  jint a, jint b) {
    
}

下面我們?cè)诤瘮?shù)體當(dāng)中添加實(shí)現(xiàn):

#include <jni.h>

extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_addition(JNIEnv *env, jobject instance, jint a, jint b) {
    return a + b;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_subtraction(JNIEnv *env, jobject instance,  jint a, jint b) {
    return a - b;
}

(9) 調(diào)用本地函數(shù)

public class MainActivity extends AppCompatActivity {

    private NativeCalculator mNativeCalculator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mNativeCalculator = new NativeCalculator();
        Log.d("Calculator", "11 + 12 =" + (mNativeCalculator.addition(11,12)));
        Log.d("Calculator", "11 - 12 =" + (mNativeCalculator.subtraction(11,12)));
    }
}

最終運(yùn)行的結(jié)果為:


四矩乐、小結(jié)

以上就是使用Android Studio 2.2以上版本,通過(guò)CMake來(lái)進(jìn)行NDK開發(fā)的一個(gè)簡(jiǎn)單例子回论,主要是學(xué)習(xí)一下開發(fā)的流程散罕,下一篇文章,要學(xué)習(xí)一下CMakeLists.txt中的語(yǔ)法傀蓉。

五欧漱、參考文獻(xiàn)

(1) 向您的項(xiàng)目添加 C 和 C++ 代碼
(2) NDK筆記(二) - 在Android Studio中使用 ndk-build
(3) NDK開發(fā) 從入門到放棄(一:基本流程入門了解)
(4) NDK開發(fā) 從入門到放棄(七:Android Studio 2.2 CMAKE 高效 NDK 開發(fā))
(5) The new NDK support in Android Studio
(6) Google NDK 官方文檔
(7) Android Studio 2.2 對(duì) CMake 和 ndk-build 的支持
(8) Android開發(fā)學(xué)習(xí)之路--NDK、JNI之初體驗(yàn)
(9) NDK- JNI實(shí)戰(zhàn)教程(一) 在Android Studio運(yùn)行第一個(gè)NDK程序
(10) cmake-commands
(11) CMake
(12) 開發(fā)自己的 NDK 程序
(13) NDK開發(fā)-Android Studio+gradle-experimental 開發(fā) ndk
(14) Android NDK 開發(fā)入門指南

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末葬燎,一起剝皮案震驚了整個(gè)濱河市误甚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谱净,老刑警劉巖窑邦,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異壕探,居然都是意外死亡奕翔,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門浩蓉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)派继,“玉大人,你說(shuō)我怎么就攤上這事捻艳〖菘撸” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵认轨,是天一觀的道長(zhǎng)绅络。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么恩急? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任杉畜,我火速辦了婚禮,結(jié)果婚禮上衷恭,老公的妹妹穿的比我還像新娘此叠。我一直安慰自己,他們只是感情好随珠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布灭袁。 她就那樣靜靜地躺著,像睡著了一般窗看。 火紅的嫁衣襯著肌膚如雪茸歧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天显沈,我揣著相機(jī)與錄音软瞎,去河邊找鬼。 笑死拉讯,一個(gè)胖子當(dāng)著我的面吹牛铜涉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播遂唧,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼芙代,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了盖彭?” 一聲冷哼從身側(cè)響起纹烹,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎召边,沒想到半個(gè)月后铺呵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隧熙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年片挂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贞盯。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡音念,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出躏敢,到底是詐尸還是另有隱情闷愤,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布件余,位于F島的核電站讥脐,受9級(jí)特大地震影響遭居,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜旬渠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一俱萍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧告丢,春花似錦枪蘑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)成翩。三九已至觅捆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間麻敌,已是汗流浹背栅炒。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留术羔,地道東北人赢赊。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像级历,于是被迫代替她去往敵國(guó)和親释移。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容