使用CMake構建Android JNI工程
Android Studio(2.2+)構建native庫可以使用原生構建工具包ndk-build,也可以使用外部構建工具CMake,搭配Gradle外掛可以方便的構建原生庫,進行Android JNI的開發。使用Android Studio建立的native工程預設使用的是CMake構建工具,本文從零開始介紹使用CMake搭建一個JNI工程。
為了闡述方便,我們以建立一個預設的Android工程為例,不使用建立嚮導裡的Include C++ Support或者建立C++工程。現在我們在native程式碼(C++)中實現一個獲取字串並返回的操作,然後使用java jni來呼叫。
一、下載NDK和構建工具
開啟Android Studio -> Perferences -> Appearance&Behavoir -> System Setting -> Android SDK,或者直接在左側搜尋Android SDK,選擇SDK Tools,下載NDK、CMake、LLDB這三個工具包。
新建的native工程(Include C++ Support)在local.properties中都會配置ndk的預設的路徑:
ndk.dir=/Users/derek/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/derek/Library/Android/sdk
如果沒有,或者ndk在別的目錄下,需要手動新增或修改路徑。
二、在java類中宣告native方法
在java中宣告要使用的native方法,這些方法以native字首,只需宣告,無需實現。這些方法可以宣告為static或非static方法,可以是任何訪問許可權。
package com.tsia.example.jnitest;
...
public class MainActivity extends Activity {
...
public native String stringFromJNI(String str);
}
三、新增C/C++程式碼
在c++程式碼中需新增和native方法對應的函式,注意如下幾點:
- 檔名稱可隨意指定,可以只有原始檔
- 標頭檔案或原始檔中要#include <jni.h>
- 方法宣告要和java中的native方法對應:
- 方法名稱。Java_包名_類名_方法名,包名也使用_分隔。
- 引數。
- 第一個引數為JNIEnv *
- 如果為static方法,第二個引數為jclass;如果非static方法,第二個引數為jobject
- 從第三個引數開始,和java的native方法的引數型別和順序要一一對應
- 返回值。
- 返回值型別要對應java中的型別
- 需要JNIEXPORT、JNICALL字首
方法格式可以概括為:JNIEXPORT <返回型別> JNICALL Java_<包名><類名><方法名>(JNIEnv *, jobject,<引數>); 包名中的“.”用下劃線“_”代替。
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL Java_com_tsia_example_jnitest_MainActivity_stringFromJNI
(JNIEnv *env, jobject jobj, jstring str) {
const char* c_name = env->GetStringUTFChars(str, NULL);
std::string hello = "Hello, ";
std::string name(c_name);
return env->NewStringUTF((hello+name).c_str());
}
如果擔心函式宣告寫錯,可以使用命令列生成native方法對應的C++函式標頭檔案,只需要到java檔案所在的包名目錄下執行javah命令,比如在com所在目錄下執行:
tsias-MacBook-Pro:java tsia$ javah -jni com.tsia.example.jnitest.MainActivity
執行後會在該目錄下生成一個標頭檔案:com_tsia_example_jnitest_MainActivity.h,自動生成的格式為包名+類名.h,中間會使用_分隔。(這個檔名為命令自動生成的格式,可以隨意修改)
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_tsia_example_jnitest_MainActivity */
#ifndef _Included_com_tsia_example_jnitest_MainActivity
#define _Included_com_tsia_example_jnitest_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_tsia_example_jnitest_MainActivity
* Method: stringFromJNI
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_tsia_example_jnitest_MainActivity_stringFromJNI
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
可以在.h檔案中看到所有native方法的宣告,格式就是我們上文講的一樣。手動編寫時也可參考自動生成標頭檔案的格式,避免出錯。
在呼叫native方法的時候會匹配函式名和引數,需要按照格式書寫,不可隨意修改。
四、編寫CMakeLists.txt檔案
接下來就是建立CMake的構建指令碼,它是一個純文字檔案,必須命名為CMakeLists.txt。構建指令碼用來告訴CMake將如何建立一個so庫,例子中我們要將c++程式碼編譯成一個名為native-lib的庫,給jni呼叫。
cmake_minimum_required(VERSION 3.4.1)
add_library(
# 設定so檔名稱.
native-lib
# 設定這個so檔案為動態庫(SHARED)。靜態庫使用STATIC
SHARED
# c/c++原始檔的相對路徑(相對於CMakeLists.txt)
src/main/java/jnitest.cpp)
當工程編譯的時候,Gradle會自動將動態庫native-lib庫打包到APK中。指令碼中指定的庫名稱為native-lib,但實際CMake生成的名稱為libnative-lib.so。
CMake 使用以下規範來為庫檔案命名:
lib庫名稱.so
五、配置Gradle關聯
在構建應用時,Gradle 會以依賴項的形式執行CMake,並將共享的庫打包到的 APK中,因此我們需要提供一個指向 CMake指令碼檔案的路徑。
手動配置
將 externalNativeBuild {}
塊新增到模組級 build.gradle
檔案中,並使用 cmake {}
對其進行配置
android {
...
defaultConfig {...}
buildTypes {...}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
這裡的path為相對於build.gradle檔案的路徑,需正確配置。
使用Android Studio配置關聯
從IDE左側開啟 Project 窗格並選擇 Android檢視,右鍵點選您想要關聯到原生庫的模組(例如 app 模組),並從選單中選擇 Link C++ Project with Gradle。
選擇使用CMake構建,並制定CMakeLists的路徑。
完成後可以看到模組級
build.gradle
檔案中會增加externalNativeBuild {}
塊配置,和我們手動配置的結果是一樣的。因為gradle配置有修改,sync project下。
此時執行build -> Make Module 'app',可以看到所有架構的so都會打到APK的lib目錄下。
五、在java檔案中載入so庫
在Java程式碼中載入so庫時,請使用您在 CMake 構建指令碼中指定的名稱。如CMake生成的而檔案是libnative-lib.so,只要指定載入native-lib即可。
package com.tsia.example.jnitest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String ret = stringFromJNI("world");
Log.i("jnitest", ret+"");
}
public native String stringFromJNI(String str);
}
然後在程式碼中呼叫native方法列印結果。執行後列印結果: Hello, world
上述就是一個簡單的jni工程搭建步驟,如下是在構建和執行時的基本過程:
- Gradle呼叫外部構建指令碼CMakeLists.txt
- CMake按照構建指令碼中的命令將 C++原始檔jnitest.cpp 編譯到共享的物件庫中,並命名為libnative-lib.so,Gradle隨後在編譯的時候會將其打包到APK中。
- 執行時,應用的
MainActivity
會使用System.loadLibrary()
載入原生庫。這時候應用可以使用庫的原生函式stringFromJNI(String str)
-
MainActivity.onCreate()
呼叫stringFromJNI("world")
,這將返回“Hello, world”並列印。
參考:https://developer.android.com/studio/projects/add-native-code
相關文章
- Android JNI的基本使用(CMake)Android
- cmake + JNI
- 【經驗分享】win10 cmake 構建 Tengine 工程Win10
- Android NDK祕籍--初識NDK、JNI、Makefile/CMakeAndroid
- 從零構建Android工程Android
- CMAKE 《window構建專案》
- cmake構建Qt外掛QT
- vscode中使用cmake構建c++專案VSCodeC++
- cmake配置VS工程配置使用dll
- 通過Kotlin構建android工程KotlinAndroid
- CMake構建學習筆記17-uriparser庫的構建和使用筆記
- CMake構建學習筆記16-使用VS進行CMake專案的開發筆記
- CMake構建學習筆記8-OpenSceneGraph庫的構建筆記
- CMake構建學習筆記10-OsgQt庫的構建筆記QT
- CMake構建學習筆記13-opencv庫的構建筆記OpenCV
- CMake構建學習筆記7-freetype庫的構建筆記
- CMake構建學習筆記9-Eigen庫的構建筆記
- CMake構建學習筆記11-minizip庫的構建筆記
- CMake構建學習筆記12-libzip庫的構建筆記
- CMake構建學習筆記3-libpng庫的構建筆記
- CMake構建學習筆記2-zlib庫的構建筆記
- CMake構建學習筆記4-libjpeg庫的構建筆記
- CMake構建學習筆記5-libtiff庫的構建筆記
- CMake構建學習筆記6-giflib庫的構建筆記
- cmake執行工程
- CMake構建學習筆記1-概述筆記
- CMake構建學習筆記18-cpp-httplib庫的構建筆記HTTP
- Android Studio中jni的使用Android
- Android Note - 使用構建分析工具Android
- CMake VS工程總結
- 在 Android 中使用 JNI 的總結Android
- Android Framework層JNI的使用淺析AndroidFramework
- android使用JNI呼叫C,C++程式AndroidC++
- android: jni socketAndroid
- Maven構建多模組工程Maven
- Android JNI&NDK程式設計小結及建議Android程式設計
- CMake 使用
- Android JNI原理分析Android