Android JNI開發工具篇(1)-開發環境搭建

原文出處:http://www.ccbu.cc/index.php/android/android-jni-dev-env.html

工欲善其事,必先利其器

1. 開發環境準備

搭建開發環境是我們進行開發前首先要完成的任務,進行Android jni開發,依賴的基本開發環境包括:

  1. Android sdk
  2. android ndk
  3. cmake
  4. android studio

Android studio的sdk manager已經包括了上面所說的sdk,ndk,cmake等工具的安裝,所以一般只用下載android studio,然後再使用sdk manager工具下載這些工具就可以了。

默認情況下,Android studio使用的編譯工具是cmake,但很多沿用的項目都是使用NDK的ndk-build工具來編譯的,所以android studio也支持ndk-build。

2. 使用android studio創建本地C++工程

1.新建工程, 在嚮導的 Choose your project 部分中,選擇Natvie C++ 項目類型 。

2.在設置工程名,包名,保存路徑和語言,此處我們選擇Java語言。

3.在嚮導的 Customize C++ Support 部分中,可以選則C++ Toolchain,一般情況下,選擇默認就可以,如果開發中需要用到C++11,或者c++14等一些較高級的C++標準的特性時,可以選擇對應的Toolchain

4.點擊finish,開始構建工程,工程構建完成以後,整個項目及其gradle配置文件如下:

默認情況下,Android studio使用cmake編譯鏈工具,通過gradle腳本進行配置,默認cmake配置如下:

externalNativeBuild {
    cmake {
        path "src/main/cpp/CMakeLists.txt"
        version "3.10.2"
    }
}

cmake文件和c++源代碼都在src/main/cpp/目錄下。

Android studio也支持ndk-buid,根據實際需求,我們也可以配置爲ndk-build,當然,這需要我們先寫好對應的Android.mkAppplication.mk(可選)配置文件,然後通過修改gradle配置中的externalNativeBuild配置項來進行更改。配置爲nkd-build編譯工具,則其配置文件如下:

externalNativeBuild {
	ndkBuild {
		path file('src/main/cpp/Android.mk')
	}
}

3. 使用現有android studio工程鏈接C++工程

當一個普通的不帶C++本地庫支持的項目需要引入一個現有的c++本地庫時,可以使用android studio的Link C++ Psroject with Gradle功能來導入一個本地C++庫,導入的庫需要提供可用的cmake配置文件或Android.mk配置文件,導入工作是通過加載這些本地庫編譯配置文件來完成的。

4.在android studio配置javah工具

在Settings->Tools->External Tools下創建NDK group,在NDK group下創建javah工具。

詳細的配置參數如下:

配置項 參數
Programe $JDKPath$\bin\javah.exe
Arguments -classpath $ModuleFileDir$\src\main\java -jni -d $ModuleFileDir$\src\main\cpp $FileClass$
Working directory $FileDir$

使用時,只需要在定義了native方法的java類上右鍵選擇NDK->javah即可生成對應的c++本地函數定義的頭文件。

截圖中例子中,TestJni.java定義了本地函數add

package com.android.jnitest;

public class TestJni {
    public native int add(int a, int b);
}

使用javah生成的對應本地c++頭文件com_android_jnitest_TestJni.h內容如下:

#include <jni.h>
/* Header for class com_android_jnitest_TestJni */

#ifndef _Included_com_android_jnitest_TestJni
#define _Included_com_android_jnitest_TestJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_android_jnitest_TestJni
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_android_jnitest_TestJni_add
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

5. 工程配置

5.1 可選參數配置

可以在模塊級 build.gradle 文件的 defaultConfig 塊中配置另一個 externalNativeBuild 塊,爲 CMake 或 ndk-build 指定可選參數和標記 。

android {
    ...
    defaultConfig {
        ...
        // This block is different from the one you use to link Gradle
        // to your CMake or ndk-build script.
        externalNativeBuild {
          // For ndk-build, instead use the ndkBuild block.
          cmake {
            // Passes optional arguments to CMake.
            arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
            // Sets a flag to enable format macro constants for the C compiler.
            cFlags "-D__STDC_FORMAT_MACROS"
            // Sets optional flags for the C++ compiler.
            cppFlags "-fexceptions", "-frtti"
          }
        }
    }
    ...
}

通過上面的配置,可用對cmake的編譯選項及C和C++編譯選項做一些配置。

5.2 指定 ABI

默認情況下,Gradle 會針對 NDK 支持的應用二進制接口 (ABI) 將您的原生庫編譯到單獨的 .so 文件中,並將這些文件全部打包到您的 APK 中。如果您希望 Gradle 僅編譯和打包原生庫的部分 ABI 配置,您可以在模塊級文件 build.gradle 中使用 ndk.abiFilters 標記指定這些配置,如下所示:

android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
        cmake {...}
        // or ndkBuild {...}
        }

        // Similar to other properties in the defaultConfig block,
        // you can configure the ndk block for each product flavor
        // in your build configuration.
        ndk {
        // Specifies the ABI configurations of your native
        // libraries Gradle should build and package with your APK.
        abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
        'arm64-v8a'
        }
    }
    buildTypes {...}
    externalNativeBuild {...}
}

6. 原生庫支持

Android NDK 會提供一組隨着新的 Android API 級別的後續發佈而逐漸添加的原生標頭和共享庫文件。 請執行以下兩個基本步驟的操作,以便讓您的應用使用 NDK 提供的庫:

  1. 在您的代碼中添加與您想使用的庫關聯的標頭。

  2. 通知編譯系統您的原生模塊需要在加載時鏈接庫。

    • 如果您使用的是 ndk-build:將原生庫添加到您 Android.mk 文件中的 LOCAL_LDLIBS 變量中。例如,要鏈接 /system/lib/libfoo.so,請添加以下這行代碼:
    LOCAL_LDLIBS := -lfoo  
    

    要列出多個庫,請使用空格作爲分隔符。

    • 如果您使用的是 CMake,向 CMake 編譯腳本添加 find_library() 命令以找到 NDK 庫並將其路徑存儲爲一個變量。

      find_library( # Defines the name of the path variable that stores the
                        # location of the NDK library.
                        log-lib
                        # Specifies the name of the NDK library that
                        # CMake needs to locate.
                        log )
      

      然後再 CMake 腳本中的 target_link_libraries() 命令來關聯庫:

      target_link_libraries( # Specifies the target library.
                                 native-lib
                                 # Links the log library to the target library.
                                 ${log-lib} )
      

常用的android原生庫包括以下一些:

API級別 庫名 鏈接代碼 說明
3 C 庫 系統自動添加,無需配置
3 動態鏈接器庫 LOCAL_LDLIBS := -ldl 動態鏈接器的 dlopen(3) 和 dlsym(3) 功能
3 Android日誌庫 LOCAL_LDLIBS := -llog 原生代碼向 logcat 發送日誌消息
3 ZLib 壓縮庫 LOCAL_LDLIBS := -lz
4 OpenGL ES 1.x LOCAL_LDLIBS := -lGLESv1_CM
5 OpenGL ES 2.0 LOCAL_LDLIBS := -lGLESv2
8 jnigraphics LOCAL_LDLIBS += -ljnigraphics
9 EGL LOCAL_LDLIBS += -lEGL 分配和管理 OpenGLES 表面的原生平臺接口
9 OpenSL ES LOCAL_LDLIBS += -lOpenSLES 原生音頻處理庫
9 Android 原生應用api LOCAL_LDLIBS += -landroid 使用原生代碼編寫整個 Android 應用
14 OpenMAX AL LOCAL_LDLIBS += -lOpenMAXAL 原生多媒體處理庫
14 OpenSL ES LOCAL_LDLIBS += -lOpenSLES 增加了 PCM 支持
18 OpenGL ES 3.0 LOCAL_LDLIBS := -lGLESv3
21 OpenGL ES 3.1 LOCAL_LDLIBS := -lGLESv3
24 OpenGL ES 3.2 LOCAL_LDLIBS := -lGLESv3

7. C++ 庫支持

7.1 C++ 運行時庫

NDK 支持多種 C++ 運行時庫。

名稱 庫文件 功能
libc++ 共享庫爲 libc++_shared.so
靜態庫爲 libc++_static.a
C++17 支持。
system /system/lib/libstdc++.so newdelete。(在 r18 中已棄用。)
none 無頭文件,有限 C++。

libc++

libc++ 同時提供靜態庫和共享庫 。LLVM 的 libc++ 是 C++ 標準庫,自 Lollipop 以來 Android 操作系統便一直使用該庫,並且從 NDK r18 開始成爲 NDK 中唯一可用的 STL。libc++ 的共享庫爲 libc++_shared.so,靜態庫爲 libc++_static.a。

system

系統運行時指的是 /system/lib/libstdc++.so。請勿將該庫與 GNU 的全功能 libstdc++ 混淆。在 Android 系統中,libstdc++ 只是 newdelete。對於全功能 C++ 標準庫,請使用 libc++。

none

不包括STL。在這種情況下,沒有關聯或授權要求。不提供 C++ 標準頭文件。

7.2 配置C++ 運行時

如果您要使用 CMake,則可使用模塊級 build.gradle 文件中的 ANDROID_STL 變量,指定表表格中的一個運行時 。如果您要使用 ndk-build,則可使用 Application.mk 文件中的 APP_STL 變量指定表 1 中的一個運行時。

APP_STL := c++_shared

只能爲應用選擇一個運行時,並且只能在 Application.mk 中進行選擇。

7.3 共享運行時

如果應用包括多個共享庫,則應使用 libc++_shared.so

在 Android 系統中,NDK 使用的 libc++ 不是操作系統的一部分。這使得 NDK 用戶能夠獲得最新的 libc++ 功能和問題修復程序,即使應用以舊版 Android 爲目標。需要權衡的是,如果使用 libc++_shared.so,則必須將其納入 APK 中。如果使用 Gradle 編譯應用,則此步驟會自動完成。

7.4 C++ 異常

C++ 異常受 libc++ 支持,但其在 ndk-build 中默認爲停用狀態。這是因爲之前 NDK 並不支持 C++ 異常。CMake 和獨立工具鏈默認啓用 C++ 異常。

要在 ndk-build 中針對整個應用啓用異常,請將下面這一行代碼添加至 Application.mk 文件:

APP_CPPFLAGS := -fexceptions

要針對單一 ndk-build 模塊啓用異常,請將下面這一行代碼添加至相應模塊的Android.mk中:

LOCAL_CPP_FEATURES := exceptions

或者,您可以使用:

LOCAL_CPPFLAGS := -fexceptions

7.4 RTTI

與異常一樣,RTTI 也受 libc++ 支持,但在 ndk-build 中默認爲停用狀態。CMake 和獨立工具鏈默認啓用 RTTI。

要在 ndk-build 中針對整個應用啓用 RTTI,請將下面這一行代碼添加至 Application.mk文件:

APP_CPPFLAGS := -frtti    

要針對單一 ndk-build 模塊啓用 RTTI,請將下面這行代碼添加至相應模塊的 Android.mk中:

LOCAL_CPP_FEATURES := rtti    

或者,您可以使用:

LOCAL_CPPFLAGS := -frtti

參考:

將 Gradle 關聯到您的原生庫

向您的項目添加 C 和 C++ 代碼

Android NDK 原生 API

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章