Android NDK開發(二) 使用ndk-build構建工具進行NDK開發

容華謝後發表於2019-03-04
3270074-f6174585761de35e.png
封面

本文主要講解了在Windows環境下如何使用ndk-build構建工具來進行NDK開發,以及ndk-build構建工具在Android Stuido中的快捷工具配置。

在上一篇文章《Android NDK開發(一) 使用CMake構建工具進行NDK開發》中,我們學習瞭如何使用CMake構建工具來進行NDK開發,但是一些老專案還是使用的ndk-build構建工具進行開發的,今天我們就來學習一下如何使用ndk-build構建工具。

1.環境搭建

在SDK Tools中安裝NDK開發環境(File > Settings > Appearance & Behavior > System Settings > Android SDK > SDK Tools):

3270074-276cda63421330e3.png
安裝NDK開發環境

新建一個普通的Android專案,在main目錄下新建jni目錄,在此目錄下編寫原生程式碼:

3270074-a98ffb04009e84bd.png
新建jni目錄

在main目錄下新建jniLibs目錄,此目錄為Android Stuido載入so檔案的預設目錄,看下專案結構:

3270074-adce3bfbf651e862.png
專案結構

2.快捷鍵配置

開啟File > Settings > Tools > External Tools選項,點選【+】按鈕新增生成jni標頭檔案以及ndk-build命令的快捷工具:

生成標頭檔案

3270074-ce665091de886f5e.png
生成標頭檔案
  • Name:javah-jni

    工具名稱

  • Program:$JDKPath$/bin/javah

    javah所在的路徑,$JDKPath$代表在環境變數中配置的JDK路徑。

  • Parameters:-jni -encoding UTF-8 -d $ModuleFileDir$\src\main\jni $FileClass$

    命令引數:

    • -jni代表生成JNI樣式的標標頭檔案,檔名為當前包名+類名($FileClass$)

    • -encoding代表編碼格式為UTF-8

    • -d代表指定標頭檔案的輸出路徑為jni目錄($ModuleFileDir$\src\main\jni )

  • Working directory:$ModuleFileDir$\src\main\java

    工作目錄,$ModuleFileDir$為當前module的路徑。

javah用法: 
  javah [options] <classes>
其中, [options] 包括:
  -o <file>                輸出檔案 (只能使用 -d 或 -o 之一)
  -d <dir>                 輸出目錄
  -v  -verbose             啟用詳細輸出
  -h  --help  -?           輸出此訊息
  -version                 輸出版本資訊
  -jni                     生成 JNI 樣式的標標頭檔案 (預設值)
  -force                   始終寫入輸出檔案
  -classpath <path>        從中載入類的路徑
  -cp <path>               從中載入類的路徑
  -bootclasspath <path>    從中載入引導類的路徑
<classes> 是使用其全限定名稱指定的
(例如, java.lang.Object)。

NDK構建

3270074-777d0dbb5a3654aa.png
NDK構建

ndk-build的配置和javah-jni類似,其中C:\Tools\NDK\android-ndk-r14b\ndk-build.cmd為ndk-build構建工具的路徑,需要按照實際NDK安裝路徑進行修改。

如何呼叫

右擊專案選擇External Tools:

3270074-4271912104f2c11c.png
使用快捷工具

3.NDK開發

準備工作都做完了,下面進入正題,看下MainActivity:

public class MainActivity extends AppCompatActivity {

    // 載入native-lib,不加lib字首
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 將獲取的字串顯示在TextView上
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * native-lib中的原生方法
     */
    public native String stringFromJNI();
}

首先載入native-lib庫,然後呼叫其中的stringFromJNI方法,將其返回的字串顯示在TextView上,此時還沒有native-lib庫,別急,繼續往下看:

對著MainActivity的類名右擊滑鼠,選擇External Tools > javah-jni,控制檯執行完命令後,會在jni目錄生成一個標頭檔案:

3270074-b0843f3f6734768b.png
生成標頭檔案

看下生成的標頭檔案:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_yl_ndkdemo_MainActivity */

#ifndef _Included_com_yl_ndkdemo_MainActivity
#define _Included_com_yl_ndkdemo_MainActivity

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_yl_ndkdemo_MainActivity
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_yl_ndkdemo_MainActivity_stringFromJNI
        (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

可以看到第一行註釋寫到:這是自動生成的,不要去修改它。好,不改就不改,Go on:

在jni目錄中新建cpp類native-lib.cpp:

#include "com_yl_ndkdemo_MainActivity.h"

JNIEXPORT jstring JNICALL Java_com_yl_ndkdemo_MainActivity_stringFromJNI
        (JNIEnv *env, jobject) {
    return env->NewStringUTF("Hello from C++");
}

引用上文中生成的標頭檔案,返回一個字串給Java層,方法名是通過 Java_包名類名方法名 的方式命名的。

接著在jni目錄下建立Android.mk和Application.mk配置檔案,分別來看看:

Android.mk

# 當前路徑
LOCAL_PATH := $(call my-dir)

# 清除LOCAL_XXX變數
include $(CLEAR_VARS)

# 原生庫名稱
LOCAL_MODULE := native-lib

# 原生程式碼檔案
LOCAL_SRC_FILES =: native-lib.cpp

# 編譯動態庫
include $(BUILD_SHARED_LIBRARY)

在app的build.gradle檔案中關聯Android.mk:

android {
    ...
    
    externalNativeBuild {
        ndkBuild {
            path 'src/main/jni/Android.mk'
        }
    }
}

相當於執行了【Link C++ Project with Gradle】:

3270074-f72e85bec668512d.png
在Gradle配置中關聯C++專案

Application.mk

# 原生庫名稱
APP_MODULES := native-lib

# 指定機器指令集
APP_ABI := armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64

到這裡基本的開發流程就已經完成了,執行程式看下效果:

3270074-defea33c1dabda4e.png
執行效果

分析一下APK檔案,可以看到so檔案已經打包進去了:

3270074-6c0a55936fea0448.png
分析APK檔案

4.編譯so檔案

對著jni目錄右擊滑鼠,選擇External Tools > ndk-build,會在main目錄下生成libs和obj目錄,編譯出的so檔案就在libs目錄下:

3270074-ec9577bab9ad9926.png
編譯so檔案

將so檔案拷貝到jniLibs目錄下就可以正常使用了,也可以在app的build.gradle檔案中設定so檔案的路徑。

注意:編譯出的so檔案就相當於java中的jar包,上文中的jni就相當於library,兩者不要重複使用。

5.遇到的問題

在ndk-build的過程中遇到了下面這行警告,但是沒有影響編譯so檔案,沒有找到好的解決方法,有知道的同學可以留言告訴我,多謝!

Android NDK: WARNING: Unsupported source file extensions in jni/Android.mk for module native-lib

5.寫在最後

原始碼已經上傳到GitHub上了,歡迎Fork,覺得還不錯就Start一下吧!

GitHub傳送門

相關文章