天天看點

Android NDK 編譯工具ndk-build的使用

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();
}
           
Android NDK 編譯工具ndk-build的使用

1.1 native相關方法去掉報紅

取消檢測即可,打開 Settings>Editor>Inspections>Android>Missing JNI function 去掉勾選。

Android NDK 編譯工具ndk-build的使用

去掉後,效果如下:

Android NDK 編譯工具ndk-build的使用

2.建立c/c++檔案

2.1 生成頭檔案

  1. Terminal終端,通過下面指令 切換到

    項目xx\app\

    目錄下。
cd D:\Brainbg\AndroidNotes\NDK-First\app
           
Android NDK 編譯工具ndk-build的使用

注:由于下面的路徑都比較長,我們可以右擊相應的檔案目錄進行快捷複制:

Android NDK 編譯工具ndk-build的使用
  1. 根據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的不可映射字元

Android NDK 編譯工具ndk-build的使用
  1. 執行後,收縮app目錄後重新打開,會發現多了一個jni的目錄,

    com_brainbg_ndkfirst_JNIUtils.h

    就是新生成的頭檔案。
Android NDK 編譯工具ndk-build的使用

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檔案夾中添加相應的檔案,同時複制上面頭檔案生成的方法修改。

Android NDK 編譯工具ndk-build的使用
  • 添加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等檔案。

Android NDK 編譯工具ndk-build的使用

3.添加mk檔案

3.1 添加

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

檔案(可選)

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 管理,其中有相關内容如下:

Android NDK 編譯工具ndk-build的使用

注:不添加

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
           

執行成功後,效果如下

Android NDK 編譯工具ndk-build的使用

同時項目中會得到相應的so包,其中lib為核心,obj為編譯中産生的檔案,可删除。

Android NDK 編譯工具ndk-build的使用

4.用Gradle連結c++項目

  1. jni目錄中右擊任意檔案選擇

    Link C++ project with Gradle

Android NDK 編譯工具ndk-build的使用
  1. 其中

    Build System

    選擇ndk-build ,

    Project Path

    選擇Android.mk的路徑,而後确認。
Android NDK 編譯工具ndk-build的使用
  1. 完成上面的操作後,app/build.gradle裡面會出現如下代碼
Android NDK 編譯工具ndk-build的使用
  • 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>
           
  • 運作後效果
Android NDK 編譯工具ndk-build的使用

到此為止,第一個關于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