1. 程式人生 > >Android之從零開始JNI研發

Android之從零開始JNI研發

本文是基於Mac端Android Studio的JNI開發介紹。

1. NDK安裝以及環境配置

  • MAC端手撕NDK環境搭建

    • 解壓下載檔案
    • 設定環境,到使用者的根目錄,開啟.bash_profile檔案,設定NDK環境,參考文章Mac OS X配置環境變數
  • Windows手撕NDK環境搭建,因為C/C++在GCC的環境下編譯、執行,所以Windows環境下需要Cygwin模擬Linux編譯環境,參考http://www.jb51.net/softjc/159272.html

環境配置完成後,使用ndk-build指令檢視一下是否配置成功。

mac下執行效果:

> ndk-build                                                                                                                                         
Android NDK: Could not
find application project directory ! Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.

ndk環境搭建好後就可以開始動手了。

2. 定義帶有本地方法Java類

public class JNIDemoUtil {
    private native String getString();
}

3. 生成class檔案

build -> rebuild重新構建專案,生成class檔案,class檔案在<殼工程>/build/classes/debug

目錄下。


圖-1 class目錄結構圖

4. 生成.h標頭檔案

進入到<殼工程>/build/classes/debug目錄下執行以下指令

# -classpath指定類的路徑
javah -classpath . -jni {包名.類名}

例如, javah -classpath . -jni com.kyo.jnidemo.jni.JNIDemoUtil,會生成.h檔案。


圖-2 h檔案目錄圖

5. 編寫c/c++檔案

native的具體實現

#include <stdlib.h>
#include <jni.h>
#include "com_kyo_jnidemo_jni_JNIDemoUtil.h"
#ifdef __cplusplus extern "C" { #endif jstring Java_com_kyo_jnidemo_jni_JNIDemoUtil_getString(JNIEnv *env) { return (*env)->NewStringUTF(env, "hello world"); } #ifdef __cplusplus } #endif

6. 生成.so動態庫

上面拿到了h標頭檔案和具體實現的c/c++檔案,接下來是生成.so動態庫,根據放的目錄不同有三種方式。

  • 放在工程的根目錄jni中
  • 放在工程的內部子檔案中
  • 放在/src/main/jni目錄中

6.1 C/C++程式碼在根目錄jni


圖-3 根目錄生成so

新建mk檔案,設定一些屬性。

LOCAL_MODULE := ${call my-dir}/
include ${CLEAR_VARS}
LOCAL_SRC_FILES := /Users/wang/WorkPlace/MyWork/JNIDemo/jni/JNIDemoUtil.c
LOCAL_MODULE = libJNIDemo

include ${BUILD_SHARED_LIBRARY}

進入jni目錄,使用ndk-build指令,會在工程根目錄中自動生成libsobj兩個目錄,其中libs,目錄下就有so庫。


圖-4 執行結果圖

注意:只有C/C++把放在工程根目錄中的jni才可以使用ndk-build指令編譯

6.2 C/CC++程式碼在工程內部其他目錄


圖-5 非根目錄接結構圖



在工程根目錄新建Application.mk檔案。

APP_BUILD_SCRIPT := /Users/wang/WorkPlace/MyWork/JNIDemo/jni_c/src/Android.mk
# 因為針對多個CPU架構會生成多個so庫,使用APP_ABI限定生成支援某種CPU架構的so庫
APP_ABI := armeabi

在C/CC++程式碼目錄新建Android.mk檔案

LOCAL_MODULE := ${call my-dir}/
include ${CLEAR_VARS}
LOCAL_SRC_FILES := /Users/wang/WorkPlace/MyWork/JNIDemo/jni_c/src/JNIDemoUtil.c
LOCAL_MODULE = libJNIDemo

include ${BUILD_SHARED_LIBRARY}

在工程的根目錄下執行以下指令(可以直接進入C/C++原始碼目錄執行ndk-build生成so庫),會在工程根目錄中自動生成libsobj兩個目錄,其中libs,目錄下就有so庫。

ndk-build NDK_PROJECT_PATH={工程目錄} NDK_APPLICATION_MK={工程的Applicaion.mk目錄}

指令執行結果圖:


圖-6 執行結果圖

Application.mk指定了編譯的mk,而Android.mk指定了編譯的一些屬性,包括編譯原始檔等等,這些都可以靈活變化

6.3 C/C++放在src/main/jni目錄下


圖-7 自動生成so目錄圖



local.properties中新增ndk路徑,我的如下。

sdk.dir=/Users/wang/Android/Android_SDK
ndk.dir=/Users/wang/Android/Android_NDK_r13b

在殼工程(通常是app/build.gradle)的build.gradle配置生成的so庫名,找到defaultConfig這個節點,新增如下內容。

defaultConfig {
    ...
    ndk {
        moduleName "libJNIDemoJni" // so庫名
        abiFilters "armeabi" // 指定生成的CPU架構對應的so庫
    }
}

gradle.properties檔案中新增.

android.useDeprecatedNdk=true

然後rebuild專案,會自動生成so動態庫,在<殼工程>/build/intermedistes/ndk目錄下面,目錄如下圖:


圖-8 自動生成so的目錄

7. 載入so庫

將生成的so庫在<殼工程>/src/main/jniLibs目錄下。

同時在JNIDemoUtil中載入so庫程式碼。

public class JNIDemoUtil {

    // 注意庫的名字前面沒有lib
    static {
        System.loadLibrary("JNIDemo");
    }

    public native static String getString();
}

8. 編譯執行

準備工作做完後,可以直接執行啦。

public void onClick(View v) {
    int id = v.getId();
    if(id == R.id.jni_demo_btn){
        mInfoTv.setText(JNIDemoUtil.getString());
    }
}

執行結果圖:


圖-9 執行結果圖

9. 問題解答

java.lang.UnsatisfiedLinkError: Couldn’t load XXX indLibrary

Android NDK: Could not find application project directory !
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.

使用以下指令編譯

ndk-build NDK_PROJECT_PATH={工程目錄} NDK_APPLICATION_MK={工程的Applicaion.mk目錄}

10. 結束語

本篇幾乎沒有涉及C/C++與java之間變數以及語法等等一些知識點,這些會在下篇來介紹。本人也是在學習探索過程中,如果有錯誤希望大家指出來。