Android NDK開發,使用ndk-build編譯

1、開發環境

      win10   AndroidStudio 3.1.2  NDK 版本:R16java

2、配置 NDK 環境變量: 

       和配置 Java JDK 環境變量相同,不會的能夠自行百度,配置 NDK 環境變量有不少種方式;android

    

     

3、在本身項目建立本地方法:

即:在 Java 類中建立帶有 native 的方法;c++

項目或者應用的包名:com.ang.ndkdemomarkdown

public class MainActivity extends AppCompatActivity {
    
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
 
    //建立的本地方法,具體功能在C或者C++中實現
    public native String fromJNIString();
 
}
複製代碼

4、手動建立本地方法 fromJNIString() 對應的. h 頭文件

1,在電腦的 cmd 或者 AndroidStudio 的 Terminal 中輸入 javah -d D:\Demo\NDKDemo\app\src\main\jni -classpath D:\Demo\NDKDemo\app\src\main\java com.ang.ndkdemo.MainActivity數據結構

javah -d D:\Demo\NDKDemo\app\src\main\jni -classpath D:\Demo\NDKDemo\app\src\main\java 
com.ang.ndkdemo.MainActivity
複製代碼
  • a, -d  D:\Demo\NDKDemo\app\src\main\jni      建立 jni 文件夾並指定. h 輸出目錄
  • b, D:\Demo\NDKDemo\app\src\main\jni           要建立的. h 頭文件輸出的絕對路徑
  • c, D:\Demo\NDKDemo\app\src\main\java  com.ang.ndkdemo.MainActivity    包含本地方法 (fromJNIString()) 的類路徑;注意不要寫成了 D:\Demo\NDKDemo\app\src\main\java\com\ang\ndkdemo\MainActivity(把包名中的點 「.」 寫成了斜槓 「****」,這樣寫是不對的) **;**com.ang.ndkdemo.MainActivity(注意是包名 + 類名);
  • 參數說明

-classpath :類搜索路徑,這裏表示從當前的 D:\Demo\NDKDemo\app\src\main\java 目錄下查找架構

-d :將生成的頭文件放到當前的 jni 目錄下app

-o : 指定生成的頭文件名稱,默認以類全路徑名生成(包名 + 類名. h)ide

注意:-d 和 - o 只能使用其中一個參數。函數

注意: -d D:\Demo\NDKDemo\app\src\main\jni 和 -classpath D:\Demo\NDKDemo\app\src\main\java  位置能夠互換;一下寫法和等價於上面的寫法;工具

javah -classpath D:\Demo\NDKDemo\app\src\main\java -d D:\Demo\NDKDemo\app\src\main\jni com.ang.ndkdemo.MainActivity

複製代碼

            

補充:能夠經過 - o 指定生成的頭文件名稱,若是不指定,默認以類全路徑名生成(包名 + 類名. h)

javah -classpath E:\Demo\JNIDemo\app\src\main\java -o E:\Demo\JNIDemo\app\src\main\java\jni\JNITest.h com.ang.MainActivity

複製代碼

2,執行以上命令以後:就在項目的 main 文件夾下建立了 jni 文件夾,而且在 jni 文件夾下自動建立了. h 頭文件;頭文件名也是自動生成的,命名規則是 com_ang_ndkdemo_MainActivity.h(包名 + 類名. h)

              

3,自動生成的 com_ang_ndkdemo_MainActivity.h 頭文件代碼

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_ang_ndkdemo_MainActivity */
 
#ifndef _Included_com_ang_ndkdemo_MainActivity
#define _Included_com_ang_ndkdemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_ang_ndkdemo_MainActivity
 * Method:    fromJNIString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_ang_ndkdemo_MainActivity_fromJNIString
  (JNIEnv *, jobject);
 
/*JNIEnv* 是定義任意native函數的第一個參數(包括調用JNI的RegisterNatives函數註冊的函數),指向JVM函數表的指針,函數表中的每個入口指向一個JNI函數,每一個函數用於訪問JVM中特定的數據結構。*/
 
#ifdef __cplusplus
}
#endif
#endif
複製代碼

4,生成. h 頭文件時候,若是出現 「找不到類文件」 的錯誤請參考  blog.csdn.net/ezconn/arti… 這篇文章

注意:

a. 包名或類名或方法名中含下劃線 _ 要用 _1 鏈接;

b. 重載的本地方法命名要用雙下劃線 __ 鏈接;

c. 參數簽名的斜槓 「/」 改成下劃線 「_」 鏈接,分號 「;」 改成 「_2」 鏈接,左方括號 「[」 改成 「_3」 鏈接;

另外,對於 Java 的 native 方法,static 和非 static 方法的區別在於第二個參數,static 的爲 jclass,非 static 的 爲 jobject;JNI 函數中是沒有修飾符的。 

5、在 jni 目錄下建立 c 或者 c++ 文件;

文件名能夠隨意寫,但須要注意文件類型;Hello.c 文件(.c 後綴的文件爲 C)表明內容是 C 代碼;Hello.cpp(.cpp 後綴的文件爲 C++)文件表明內容是 C++ 代碼;

C++ 代碼(注意 C 和 C++ 代碼是有區別),如下分別給出 C 和 C++ 兩種實現方式:

  • a,Hello.c 文件。在 C 中沒有引用,傳遞的 env 是個兩級指針,用(*env)-> 調用方法且方法中要傳入 env.   
#include <jni.h>
#include "com_ang_ndkdemo_MainActivity.h"
 JNIEXPORT jstring JNICALL
 Java_com_ang_ndkdemo_MainActivity_fromJNIString(JNIEnv* env, jobject obj) {
     return (*env)->NewStringUTF(env,"I am From Native C");
 }
複製代碼
  • b, Hello.cpp 文件。C++ 中 env 爲一級指針,用 env-> 調用方法,無需傳入 env;C++ 語言在編譯的時候爲了解決函數的多態問題,會將函數名和參數聯合起來生成一箇中間的函數名稱,而 C 語言則不會,所以會形成連接時找不到對應函數的狀況,此時 C 函數就須要用 extern "C" 進行連接指定,這告訴編譯器,請保持個人名稱,不要給我生成用於連接的中間函數名;exter  "C"{jni 代碼}。
#include <com_ang_ndkdemo_MainActivity.h>
#include <stdio.h>
 
JNIEXPORT jstring JNICALL
Java_com_ang_ndkdemo_MainActivity_fromJNIString(JNIEnv *env, jobject obj)
{
    return env->NewStringUTF("I am From Native C");
}
複製代碼

Java 的 native 方法是如何連接 C/C++ 中的函數的呢?能夠經過靜態和動態的方式註冊 JNI。 以上是經過靜態註冊的方式。

靜態註冊:根據函數名創建 Java 本地方法和 JNI 函數的一一對應關係。

動態註冊:直接告訴 Java native 方法其在 JNI 中對應函數的指針。

6、配置 build.gradle(Model:App)

也能夠不配置 ndk{}, 這裏只是指定編譯出哪幾種對應的 abi 架構的. so 庫,若是不配置,會根據 ndk-build 默認輸出對應的 abi 架構的. so 庫;最好配置,否則不能編譯出本身想要的對應 ABI 架構的. so,若是本身的項目中已經引用其餘的. so 庫還要作適配;

  

defaultConfig {
        applicationId "com.ang.demo"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
 
        //ndk編譯生成.so文件
        ndk{
            moduleName "Java2c"    //生成的so名字,Android.mk文件中已經指定了,這裏能夠不寫
            abiFilters "armeabi", "armeabi-v7a", "x86"  //輸出指定三種abi體系結構下的so庫。
        }
    }
複製代碼

7、編寫 Android.mk 文件

Android.mk 文件通常包含以下信息就夠了,差很少可算得上一個模板;根據本身的. so 庫名和 C 或者 C++ 文件名修改一下就能夠用了;

LOCAL_PATH:= $(call my-dir)#不用修改
 
 
include $(CLEAR_VARS)#不用修改
 
LOCAL_MODULE:= hello #動態庫名稱
LOCAL_SRC_FILES:= hello.c #C文件,裏面就是咱們寫的C代碼
 
include $(BUILD_SHARED_LIBRARY)#生成.so動態庫
 
#include $(BUILD_STATIC_LIBRARY) 編譯出.a的靜態庫
複製代碼

還有一種方式,就是讓 androidstudio 自動生成;以下是我獲取自動生成的 Android.mk 文件的方式:

a, 緊接着步驟六以後,點擊 Androidstudio 菜單欄 Build ->ReBuildProject

 報錯:

        

b, 在 app ——> build ——>intermediater——>ndk(自動建立) 目錄下自動建立了一個 Android.mk 文件

         

Android.mk 文件以下: 

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
 
LOCAL_MODULE := Java2c
LOCAL_LDFLAGS := -Wl,--build-id
LOCAL_SRC_FILES := \
	D:\Demo\NDKDemo\app\src\main\jni\Hello.cpp \
 
LOCAL_C_INCLUDES += D:\Demo\NDKDemo\app\src\debug\jni
LOCAL_C_INCLUDES += D:\Demo\NDKDemo\app\src\main\jni
 
include $(BUILD_SHARED_LIBRARY)
複製代碼

8、修改默認編譯工具

鼠標右鍵項目,點擊 Link C++ Project with Gradle 修改 Androidstudio 默認編譯工具, 在 BuildSystem 欄選擇 ndk—build, 在 ProjectPath 選項欄,找到剛纔建立的 Android.mk 文件(其實就是 Android.mk 文件路徑),點擊 OK 以後就在 build.gradle(Model:App)的 android{} 中自動生成了 externalNativeBuild { ndkBuild { path 'src/main/jni/Android.mk'} }

//增長以後以下信息以後,右鍵項目的時候Link C++ Project with Gradle選項再也不顯示;  
externalNativeBuild {
        ndkBuild {
            path 'src/main/jni/Android.mk'
        }
 }
複製代碼

注意:有的時候須要再次顯示 Link C++ Project with Gradle 選項,刪掉 externalNativeBuild {ndkBuild {   path 'src/main/jni/Android.mk'} } 點擊 sync now 同步一下;再次右鍵項目就能夠出現了;

**相關知識:**要將 Gradle 關聯到原生庫,須要提供一個指向 CMake 或 ndk-build 腳本文件的路徑。在構建應用時,Gradle 會以依賴項的形式運 CMake 或 ndk-build,並將共享的. so 庫打包到 APK 中。externalNativeBuild 就是配置的腳本路徑;

9、最後在 MainActivity 中加載咱們生成的動態庫:

注意:加載生成的動態庫指定的文件名(System.loadLibrary("Java2c");)和生成. so 時指定的名字(buil.gradle 中的 ndk{moduleName "Java2c" }),還有 Android.mk 中 LOCAL_MODULE := Java2c 三者是否一致;

例如: 下圖加載生成的動態庫指定的文件名爲:Java2JNI 和上面生成. so 時指定的名字爲:Java2c 還有 Android.mk 中 LOCAL_MODULE := Java2c 三者不一致,就會出現 UnsatisfiedLinkError 異常

public class MainActivity extends AppCompatActivity {
 
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toast.makeText(this,new Java2C().fromJNIString(),Toast.LENGTH_LONG).show();
    }
    //加載.so庫 Java2c爲庫名
    static {
        System.loadLibrary("Java2c");
    }
 
    public native String fromJNIString();
 
}
複製代碼

關於 UnsatisfiedLinkError 異常的緣由還有不少,這裏針對 NDK 開發總結幾種可能的緣由:https://blog.csdn.net/ezconn/article/details/82531893             

**總結:**以上不用編譯成. so 庫放到指定的路徑下;若是須要. so 庫(給其餘項目使用,例如使用百度地圖服務,就要使用其提供的. so 庫)以下手動編譯並使用. so 庫;

                                         手動編譯. so 文件

從步驟八開始的第二種方式,不指定 AndroidStudio 編譯工具(Cmake 或者 ndk-build),直接手動生成. so 庫

a, cmd 或者 Android studio 的 Terminal 中進入 jni 的上一級目錄

      

b, 輸入 ndk-build 命令,在 jni 同級的目錄中生成了一個 libs 文件夾,裏面生成了各個 cup 架構對應的. so 文件,

      

c, 應用. so 動態庫   

  • 1, 若是不更改手動生成後的. so 庫的位置,須要在 build.gradle(Model.app) 配置,由於. so 庫的默認存放位置是 src/main/jniLibs
// gradle高版本新寫法
    sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/libs']
        }
    }
複製代碼
  • 2, 能夠把生成的. so 複製到 main 目錄下和 java 同級的 jniLibs(若是沒有,手動建立一個)目錄下,默認的庫文件路徑就是 src/main/jniLibs,這樣就不用在 build.gradle(Model:app) 中配置 sourceSets 了;

              

  • 3, 也能夠把. so 庫複製到 app 目錄下的 libs 目錄中,這裏也須要在 build.gradle(Model.app) 配置  注意:和 2 的區別
sourceSets {
        main {
           jniLibs.srcDirs = ['libs']
        }
    }
複製代碼

d, 注意:這種手動生成. so 庫的方式,使用 ndk17 生成失敗,以上都是應用的 ndk16,因爲 ndk17 已不在支持 mips、armeabi CPU 架構,

其餘相關

使用 NDK 編譯代碼主要有三種方法:

1.基於 Make 的 ndk-build。

2.CMake。

3.獨立工具鏈,用於與其餘編譯系統集成,或與基於 configure 的項目搭配使用。

若是對您有所幫助的話

不妨加個關注,點個贊哈,您的每一個小小舉動都是對我莫大的支持!

相關文章
相關標籤/搜索