Android NDK 編譯工具ndk-build的使用
-
- 1.建立native相關方法
-
- 1.1 native相關方法去掉報紅
- 2.建立c/c++檔案
-
- 2.1 生成頭檔案
- 2.2 添加 c/c++檔案
- 3.添加mk檔案
-
- 3.1 添加 `Android.mk`檔案(必加)
- 3.2 添加 `Application.mk`檔案(可選)
- 4.編譯so庫檔案
- 4.用Gradle連結c++項目
- 6.加載so庫、運作app
- 7.下載下傳位址
- 參考資料
1.建立native相關方法
建立
JNIUtils.java
,并建立native相關方法
public class JNIUtils {
/**
* 通過JNI從C中擷取資料
*
* @return String
*/
public static native String getDataFromC();
/**
* 通過JNI從C++中擷取資料
*
* @return String
*/
public static native String getDataFromCPP();
}
1.1 native相關方法去掉報紅
取消檢測即可,打開 Settings>Editor>Inspections>Android>Missing JNI function 去掉勾選。
去掉後,效果如下:
2.建立c/c++檔案
2.1 生成頭檔案
- Terminal終端,通過下面指令 切換到
目錄下。項目xx\app\
cd D:\Brainbg\AndroidNotes\NDK-First\app
注:由于下面的路徑都比較長,我們可以右擊相應的檔案目錄進行快捷複制:
- 根據java檔案生成c的頭檔案, 執行如下指令
格式: javah d jni -encoding utf-8 classpath /java檔案夾路徑 包名+類名
javah d jni classpath D:\Brainbg\AndroidNotes\NDK-First\app\src\main\java com.brainbg.ndkfirst.JNIUtils
或者
javah d jni -encoding utf-8 classpath D:\Brainbg\AndroidNotes\NDK-First\app\src\main\java com.brainbg.ndkfirst.JNIUtils
其中
- javah :生成頭檔案
- d jni :目前目錄下建立jni檔案夾
- -encoding utf-8 :指定編碼格式為utf-8
- classpath D:\Workspace\NDKFirst\app\src\main\java\ :到java目錄的路徑
- com.brainbg.ndkfirst.JNIUtils :包名+類名
注:不加上
-encoding utf-8
,可能會提示
錯誤: 編碼GBK的不可映射字元
。
- 執行後,收縮app目錄後重新打開,會發現多了一個jni的目錄,
就是新生成的頭檔案。com_brainbg_ndkfirst_JNIUtils.h
com_brainbg_ndkfirst_JNIUtils.h
内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_brainbg_ndkfirst_JNIUtils */
#ifndef _Included_com_brainbg_ndkfirst_JNIUtils
#define _Included_com_brainbg_ndkfirst_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_brainbg_ndkfirst_JNIUtils
* Method: getDataFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_brainbg_ndkfirst_JNIUtils_getDataFromC
(JNIEnv *, jclass);
/*
* Class: com_brainbg_ndkfirst_JNIUtils
* Method: getDataFromCPP
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_brainbg_ndkfirst_JNIUtils_getDataFromCPP
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
其中,裡面的2個方法
Java_com_brainbg_ndkfirst_JNIUtils_getDataFromC
、
JNICALL Java_com_brainbg_ndkfirst_JNIUtils_getDataFromCPP
是重點,後面需要直接複制進C/C++的檔案中進行修改,同時,我們也能發現它生成的格式:
格式:Java_包名_類名_方法名
2.2 添加 c/c++檔案
在jni檔案夾中添加相應的檔案,同時複制上面頭檔案生成的方法修改。
- 添加c檔案
firstc.c
#include <jni.h>
#include "com_brainbg_ndkfirst_JNIUtils.h"
JNIEXPORT jstring JNICALL
Java_com_brainbg_ndkfirst_JNIUtils_getDataFromC(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "This is my first jni from C!");
}
- 添加cpp檔案
firstcpp.cpp
#include <jni.h>
#include "com_brainbg_ndkfirst_JNIUtils.h"
JNIEXPORT jstring JNICALL
Java_com_brainbg_ndkfirst_JNIUtils_getDataFromCPP(JNIEnv *env, jclass jclass) {
return env->NewStringUTF("This is my first jni from CPP!");
}
修改好的内容後,你會留意到上面還有提示:大意就是目前的c/c++檔案還不屬于項目中的一部分!為此,我們還需要處理build.gradle、Android.mk等檔案。
3.添加mk檔案
3.1 添加 Android.mk
檔案(必加)
Android.mk
注意mk檔案裡面不能添加注釋,不然編譯不通過。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := first-jni
LOCAL_SRC_FILES := firstc.c firstcpp.cpp
include $(BUILD_SHARED_LIBRARY)
- LOCAL_PATH := $(call my-dir):此變量表示源檔案在開發樹中的位置。在這行代碼中,編譯系統提供的宏函數 my-dir 将傳回目前目錄(Android.mk 檔案本身所在的目錄)的路徑。
- include $(CLEAR_VARS):其值由編譯系統提供。CLEAR_VARS 變量指向一個特殊的 GNU Makefile,後者會清除許多 LOCAL_XXX 變量,例如 LOCAL_MODULE、LOCAL_SRC_FILES 和 LOCAL_STATIC_LIBRARIES。請注意,GNU Makefile 不會清除 LOCAL_PATH。此變量必須保留其值,因為系統在單一 GNU Make 執行環境(其中的所有變量都是全局變量)中解析所有編譯控制檔案。在描述每個子產品之前,必須聲明(重新聲明)此變量。
- LOCAL_MODULE:LOCAL_MODULE 變量存儲您要編譯的子產品的名稱。請在應用的每個子產品中使用一次此變量。每個子產品名稱必須唯一,且不含任何空格。編譯系統在生成最終共享庫檔案時,會對您配置設定給 LOCAL_MODULE 的名稱自動添加正确的字首和字尾。例如,上述示例會生成名為 libfirst-jni.so 的庫。
- LOCAL_SRC_FILES:
,變量必須包含要編譯到子產品中的 C 和/或 C++ 源檔案清單。多個檔案以空格分隔開
- nclude $(BUILD_SHARED_LIBRARY):幫助系統将所有内容連接配接到一起,變量指向一個 GNU Makefile 腳本,該腳本會收集您自最近 include 以來在 LOCAL_XXX 變量中定義的所有資訊。此腳本确定要編譯的内容以及編譯方式。
注意:LOCAL_MODULE中,如果子產品名稱的開頭已經是 lib,則編譯系統不會附加額外的 lib 字首;而是按原樣采用子產品名稱,并添加 .so 擴充名。是以,比如原來名為 libfoo.c 的源檔案仍會生成名為 libfoo.so 的共享對象檔案。此行為是為了支援 Android 平台源檔案根據 Android.mk 檔案生成的庫;所有這些庫的名稱都以 lib 開頭。
更多内容,可以直接檢視:Android.mk 官方介紹
3.2 添加 Application.mk
檔案(可選)
Application.mk
APP_PLATFORM := android-16
APP_ABI :=all
- APP_PLATFORM :指定so庫所支援最低的API,即 會聲明編譯此應用所面向的 Android API 級别,并對應于應用的
minSdkVersion
注: 如果未指定,ndk-build 将以 NDK 支援的最低 API 級别為目标。最新 NDK 支援的最低 API 級别總是足夠低,可以支援幾乎所有使用中的裝置。
警告:将 APP_PLATFORM 設定為高于應用的 minSdkVersion 可能會生成一個無法在舊裝置上運作的應用。在大多數情況下,庫将無法加載,因為它們引用了在舊裝置上不可用的符号。
- APP_ABI:指定生成平台的so庫
預設情況下,NDK 編譯系統會為所有非棄用 ABI 生成代碼。您可以使用 APP_ABI 設定為特定 ABI 生成代碼。
指令集 | 值 |
---|---|
32 位 ARMv7 | APP_ABI := armeabi-v7a |
64 位 ARMv8 (AArch64) | APP_ABI := arm64-v8a |
x86 | APP_ABI := x86 |
x86-64 | APP_ABI := x86_64 |
所有支援的 ABI(預設) | APP_ABI := all |
ABI 和支援的指令集
,可以檢視Google的ABI 管理,其中有相關内容如下:
注:不添加
Application.mk
,會提示
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16.
更多内容,可以直接檢視:Application.mk 官方介紹
4.編譯so庫檔案
進入app目錄,執行
ndk-build
進行編譯
cd D:\Workspace\NDKFirst\app
ndk-build
執行成功後,效果如下
同時項目中會得到相應的so包,其中lib為核心,obj為編譯中産生的檔案,可删除。
4.用Gradle連結c++項目
- jni目錄中右擊任意檔案選擇
Link C++ project with Gradle
- 其中
選擇ndk-build ,Build System
選擇Android.mk的路徑,而後确認。Project Path
- 完成上面的操作後,app/build.gradle裡面會出現如下代碼
- build.gradle
android {
......
externalNativeBuild {
ndkBuild {
path file('jni/Android.mk')
}
}
}
當然,下次項目的話,我們也可以直接加入上面代碼即可。
6.加載so庫、運作app
- JNIUtils
public class JNIUtils {
public static final String TAG = JNIUtils.class.getSimpleName();
static {
try {
System.loadLibrary("first-jni"); //加載so庫
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
Log.e(TAG, "loadLibrary fail !");
}
}
/**
* 通過JNI從C中擷取資料
*
* @return String
*/
public static native String getDataFromC();
/**
* 通過JNI從C++中擷取資料
*
* @return String
*/
public static native String getDataFromCPP();
}
- MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tvC = findViewById(R.id.tv_c);
TextView tvCPP = findViewById(R.id.tv_cpp);
tvC.setText(JNIUtils.getDataFromC());
tvCPP.setText(JNIUtils.getDataFromCPP());
}
}
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_c"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="c-content" />
<TextView
android:id="@+id/tv_cpp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="cpp-content" />
</LinearLayout>
- 運作後效果
到此為止,第一個關于NDK、JNI的Demo已經完成,相關文章,後續可能、應該、大概也會推出吧。
7.下載下傳位址
GitHub項目下載下傳
參考資料
https://developer.android.google.cn/ndk/guides
https://blog.csdn.net/young_time/article/details/80346631
https://yq.aliyun.com/articles/60710?spm=a2c4e.11153940.0.0.11bc68d9CLrDix
https://www.jianshu.com/p/87ce6f565d37
作者:Brainbg(白雨)
GitHub:https://github.com/Brainbg
部落格:https://www.brainbg.com/
簡書:https://www.jianshu.com/u/94518ede7100
CSDN:https://blog.csdn.net/u014720022