1. 程式人生 > >NDK-JNI實戰教程(四)再談新工具及NDK開發除錯

NDK-JNI實戰教程(四)再談新工具及NDK開發除錯

1 背景

時隔一年,多了些磨礪,懂了些故事,悟出些道理,但這一年技術卻停留在了原地,以前的 Unix 高階 C 在現在公司暫時無用武之地了,說句實話,打心裡我還是喜歡硬體和 C 語言,只是可惜了。有很多人私信想讓 NDK 系列文章持續下去,所以就順帶作為自己對過去的回顧整理吧。開始本篇前還是建議先按順序看看前面這三篇吧,否則可能有些脫節。

上面這三篇內容其實在某種程度上只具備指點意義了,因為很多工具在這一年裡發生了很大變化,所以裡面的一些配置在現在看來似乎有些不太恰當了,不過你能自己發現這些版本的相容性問題並改正它也是一種自我除錯的能力。

這裡寫圖片描述

2 約談AS及Gradle新版本

還記得之前的 Google IO 大會麼? Google 除了在打包系統中集成了 NDK 的支援還將 JetBrains 的 C/C++ IDE CLion 直接整合在了 AS 中,關於 CLion 就不用多說了,做 C 開發且喜歡嚐鮮的相信都不陌生的;這個結合對於現在的NDK開發來說簡直是屌爆了,因為再也不像以前那麼苦逼使用 Eclipse 等工具去寫 C 程式碼,完事再編寫 Android.mk 與Application.mk 用 ndk-build 命令列去巴拉巴拉編譯了,對於應用層的 NDK 開發一下就變的 Easy 的許多了(現在再想想剛畢業那會在上家公司做 Token Lib 的 NDK 開發時是多麼的麻煩與蛋疼!),不過原始碼下的 NDK 開發還是建議繼續使用 Android.mk 的方式去匹配 Android 原始碼編譯框架吧。當然了,近期的 AS 與 Gradle 版本的快速更新對 NDK 開發又有了更加牛叉的體驗,因為它完全支援使用 GDB 和 LLDB (不清楚這兩是啥的請自行腦部Unix程式設計基礎)來 GUI 化 debug 我們得 native 程式碼了(以前真的好蛋疼,命令列巴拉巴拉的,淚奔啊!)。

總之現在的 AS 和 Gradle 已經趨於實驗完善 NDK 開發了,主要表現在如下方面:

  • AS 完全支援 GUI 模式的 GDB/LLDB 除錯 native 程式碼(LLDB除錯引擎需要gradle-experimental plugin的支援)。
  • AS 可以很好的直接編寫 native 程式碼。
  • Java 層程式碼宣告好以後 AS 可以自動幫我們生成 JNI 介面規範程式碼。
  • 推出了幾乎針對 NDK 的實驗版 Gradle 外掛。

可以看見,現在 NDK 開發已經漸漸的變得越來越方便了,牛叉的一逼!

2-1 原Gradle與AS下NDK開發

當我們在 AS 中進行 NDK 開發時 Gradle 指令碼的編寫其實在前面幾篇文章已經給出了,基本模板如下:

//Project的build.gradle檔案
buildscript {
    repositories {
       jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}
//Module的build.gradle檔案
apply plugin: 'com.android.application'

android {
    compileSdkVersion = 23
buildToolsVersion = "23.0.2" defaultConfig { applicationId = "com.example.hellojni" minSdkVersion 19 targetSdkVersion 23 ndk { moduleName = "hello-jni" //...... } } buildTypes { release { minifyEnabled = false proguardFiles.add(file('proguard-rules.txt')) } } }

可以發現很常規,就是在 android 閉包中新增 ndk 閉包寫明一些 lib 相關東西即可,然後其他流程和我們前面幾篇沒啥區別的。不過有的同學使用新版 AS 和 Gradle 構造上面的指令碼可能會遇上一個錯誤,如下:

Error:(12, 1) A problem occurred evaluating project ':app'.
> Error: NDK integration is deprecated in the current plugin.  Consider trying the new experimental plugin.  For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental.  Set "android.useDeprecatedNdk=true" in gradle.properties to continue using the current NDK integration.
Information:BUILD FAILED

哈哈,看見沒有,這個錯誤的原因就是我們 2-2 中準備要介紹的,因為在當前 Gradle 外掛版本中這樣整合 NDK 的寫法已經過時不推薦了,它推薦了新的實驗版 Gradle 外掛;有的小夥伴可能覺得實驗版不爽,就是想用當前版本 Gradle 外掛,那也沒事,這裡給出解決方法,如下:

1、在Project的根目錄下建立 gradle.properties 檔案;
2、在建立的檔案中寫上 android.useDeprecatedNdk=true 即可;
3、這樣就聲明瞭繼續使用過時的 NDK ,然後我們再次構建工程就OK了;

其他流程和前面幾篇文章沒有區別,直接使用即可,下面我們就來看看新的實驗版 Gradle 外掛 NDK 開發吧。

2-2 新實驗版Gradle外掛與AS下NDK開發

前面幾篇 NDK 文章都是基於上面 2-1 的 Gradle 指令碼來構建了,既然說到了新實驗版本 Gradle,那我們就來看看有啥區別(除過 Gradle 區別別的與前幾篇 NDK 開發流程保持一致即可),如下:

//Project的build.gradle檔案
buildscript {
    repositories {
       jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle-experimental:0.7.0-alpha1'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}
//Module的build.gradle檔案
apply plugin: 'com.android.model.application'

model {
    android {
        compileSdkVersion = 23
        buildToolsVersion = "23.0.2"

        defaultConfig.with {
            applicationId = "com.example.hellojni"
            minSdkVersion.apiLevel = 4
            targetSdkVersion.apiLevel = 23
        }
    }

    /*
     * native build settings
     */
    android.ndk {
        moduleName = "hello-jni"
        /*
         * Other ndk flags configurable here are
         * cppFlags.add("-fno-rtti")
         * cppFlags.add("-fno-exceptions")
         * ldLibs.addAll(["android", "log"])
         * stl       = "system"
         */
    }
    android.buildTypes {
        release {
            minifyEnabled = false
            proguardFiles.add(file('proguard-rules.txt'))
        }
    }
    android.productFlavors {
        // for detailed abiFilter descriptions, refer to "Supported ABIs" @
        // https://developer.android.com/ndk/guides/abis.html#sa
        create("arm") {
            ndk.abiFilters.add("armeabi")
        }
        create("arm7") {
            ndk.abiFilters.add("armeabi-v7a")
        }
        create("arm8") {
            ndk.abiFilters.add("arm64-v8a")
        }
        create("x86") {
            ndk.abiFilters.add("x86")
        }
        create("x86-64") {
            ndk.abiFilters.add("x86_64")
        }
        create("mips") {
            ndk.abiFilters.add("mips")
        }
        create("mips-64") {
            ndk.abiFilters.add("mips64")
        }
        // To include all cpu architectures, leaves abiFilters empty
        create("all")
    }
}

看見了嗎?Google 當前正在實驗的 Gradle 外掛已經更新到了 gradle-experimental 0.7.0-alpha1 ,可以明顯感覺到 Project 和 Module 的 build.gradle 檔案編寫閉包都有了變化,細節的相信不需要我再解釋了,只是讓大家知道有這麼回事就行了;框框架架有調整,但是實質其實沒啥變化的,大家注意這裡新的閉包規則就行了。

PS:關於變化和詳細的ABI等細節參見官方文件就行了,非常詳細的。

2-3 約談題外技能

還記不記得我們前面的幾篇 NDK 開發都需要手動命令列去 javah 生成 jni 標頭檔案嗎?是不是覺得很蛋疼呢?哈哈,AS 有一個功能相信你不陌生吧,那就是 External Tools 自定義功能。所以我們可以通過自定義 AS 的Tools-External Tools,新增 javah 的支援即可通過 AS 直接生成 jni 標頭檔案,設定如下圖:
這裡寫圖片描述

當然了,有時候我們需要使用 Android.mk 和 Application.mk 的做法生成 so 等庫檔案釋出使用,這個也容易,也是直接自定義 Tools-External Tools 即可,如下是 ndk-build 與 ndk-build clean 命令的 AS 自定義:
這裡寫圖片描述

這裡寫圖片描述

有了上面這種自定義的 Tools 那就提高很多效率了,譬如我們編譯完 Java 檔案後在 Project 欄選中要生成 jni 標頭檔案的 java 原始檔右鍵到 External Tools 中的 javah 就能看見 jni 資料夾下面自動生成了對應的 jni 規範標頭檔案;在 Project 欄選中 jni 目錄右鍵到 External Tools 中的 ndk-xxx 相關就能編譯生成 so 等檔案或者清空編譯結果。

當然了,你依舊可以選擇像前幾篇文章敘述的那樣開發 NDK,或者直接在原始碼環境下開發,又或者混合開發,這都是你自己的選擇而已。至於當前選擇那個版本的 Gradle 進行 NDK 開發也一樣看你個人興趣了。

3 約談NDK除錯

關於 NDK 的除錯一直都是個坑,好在 Google 在大力佈局 AS 時開始來填坑了;以前 NDK 的除錯都只能依賴於 GDB 命令列的除錯,而現在卻變得 GUI 化,方便了許多。關於 NDK 的除錯這裡也是點到為止,因為不是很複雜,有 C 語言開發經驗的都很熟悉的,沒啥深奧的,只用記住流程就行了。

3-1 約談AS下新Gradle外掛NDK除錯

在現在版本的 AS 中通過 gradle-experimental plugin 外掛和 LLDB 的配合使 NDK 除錯變得很人性化了,現在基本都是傻瓜模式的 GUI 操作了,具體表現如下圖:
這裡寫圖片描述

可以看見,使用 AS 除錯 NDK 和除錯 Java 程式碼基本沒啥區別,只是這裡的目標選擇的是 app-native,同時需要在 SDK Manager 中確認安裝了 LLDB 工具。

3-2 約談AS下原Gradle外掛NDK除錯

當然了,有些人可能不喜歡實驗板的 Gradle 外掛,就想用舊的,可是你會發現按照上面那樣選擇 native-app 後使用 AS 除錯會丟擲如下提示視窗:
這裡寫圖片描述

看著紅色地方了嗎?不可除錯版本,所以我們如果想用原 Gradle 除錯 NDK 就需要在 module 的 gradle 檔案中進行如下配置:

    buildTypes {
        debug {
            jniDebuggable true
        }
    }

就這樣 OK 了,我們就可以隨意的除錯了,享受此刻吧!

3-3 約談其他方式的NDK除錯

所謂的其他方式就當做緬懷吧,這可能是我剛接觸 NDK 時用的最多也最原始的方式了,那就是 ndk-gdb 手動命令列除錯。我想現在基本沒必要擁抱它了,所以也就不多介紹了。

4 總結

本文其實沒啥技術含量,只是一個工具版本過度的介紹文章,但是大家一定要了解 NDK 的靈魂是 C/C++ 程式碼的編寫,因為很多人一直都說 JNI 開發啥玩意的,其實這裡再次闡明這個誤區。NDK 開發是 C/C++ 的開發,上層是 Java 的開發,Java 與下面 C/C++ 相互調運的約定接口才叫做 JNI,JNI 介面與 NDK 程式碼最終合併編譯為庫檔案供上面的 Java 調運而已,一般 NDK 開發和 Java JNI 開發不屬於一個團隊進行,但是介面規則的約定卻時雙方商榷的。

這裡寫圖片描述