快轉到主要內容

Android JNI 環境建置

·1223 字·3 分鐘
Denny Cheng / 月月冬瓜
作者
Denny Cheng / 月月冬瓜
獸控兼工程師兼鍵盤武術家

介紹
#

主要參考這篇:Android studio 1.5.1 NDK JNI環境安裝與執行原理
不過Android 2.1.2上步驟稍有簡化,還是可以達到同樣效果。

  1. 載NDK
  2. 設定external tool (僅javah)
  3. 在java中增加native code,並利用javah產生JNI header
  4. 實作JNI
  5. 設定gradle (僅ndk區塊)

載NDK
#

開啟SDK Manager,選擇SDK Tools,將NDK之選項給打勾。

SDK Manager

設定external tool
#

與連結相同,不過只要設定javah即可,其他不需要。

javah設定

快速複製區 來源

$JDKPath$/bin/javah

-v -jni -d $ModuleFileDir$/src/main/jni $FileClass$

$SourcepathEntry$

Windows和Linux在第1行稍有分別,只有在Windows系統中,執行檔的副檔名才是.exe。Mac和Linux皆否。 個人是用linux系統,所以第1行javah後面並不接.exe,若是Windows系統則第1行變為。

$JDKPath$/bin/javah.exe

JNI header
#

在想要使用JNI的class加入如下的程式碼。

    static {
            System.loadLibrary("myJNI");
    }
    public native String getMycstring();
    public native void testLog();

static區塊中的myJNI會變成將來再build gradle當中的moduleName
method加上native關鍵詞後,程式就會知道這是需要依靠JNI實作之程式碼。

再來在該java檔上按右鍵,使用剛剛建置好的external library javah,自動的產生JNI header。

使用javah

此時在java目錄下,應該會多出一個jni的資料夾,裡頭放置著 OOXX.h
打開.h檔,建置出來的code應該會如下形式

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

#ifndef _Included_com_example_isa_myapplication_MainActivity
#define _Included_com_example_isa_myapplication_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_isa_myapplication_MainActivity
 * Method:    getMycstring
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_isa_myapplication_MainActivity_getMycstring
  (JNIEnv *, jobject);

/*
 * Class:     com_example_isa_myapplication_MainActivity
 * Method:    testLog
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_isa_myapplication_MainActivity_testLog
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

實作JNI
#

步驟如下

  1. 在jni資料夾中,新增一個myJNI.cpp
  2. include剛剛自動產生的header
  3. 實作剛剛只宣告未實作的程式碼
  4. (細節注意)

新增myJNI.cpp
#

在jni資料夾中,新增一個myJNI.cpp

include剛剛自動產生的header
#

#include "com_example_isa_myapplication_MainActivity.h"

實作剛剛只宣告未實作的程式碼
#

由於Log功能要使用到android/log.h library,所以也要include進來。

#include "com_example_isa_myapplication_MainActivity.h"
#include <android/log.h>

JNIEXPORT jstring JNICALL Java_com_example_isa_myapplication_MainActivity_getMycstring
  (JNIEnv *env, jobject jobj){
        return (*env).NewStringUTF("MY !!  NDKString!!");
  }
  
JNIEXPORT void JNICALL Java_com_example_isa_myapplication_MainActivity_testLog
  (JNIEnv * env, jobject jobj){
    __android_log_print(ANDROID_LOG_INFO, "JNI", "JNI Test");
  }

細節注意
#

當然僅僅如此還不夠,觀看.h檔可以發現有extern “C"包圍著宣告的函式,所以也要一併複製。

#include "com_example_isa_myapplication_MainActivity.h"
#include <android/log.h>


#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jstring JNICALL Java_com_example_isa_myapplication_MainActivity_getMycstring
  (JNIEnv *env, jobject jobj){
        return (*env).NewStringUTF("MY !!  NDKString!!");
  }
  
  JNIEXPORT void JNICALL Java_com_example_isa_myapplication_MainActivity_testLog
  (JNIEnv * env, jobject jobj){
    __android_log_print(ANDROID_LOG_INFO, "JNI", "JNI Test");
  }
  
#ifdef __cplusplus
}
#endif

切記不要連 #ifndef _Included_com_example_isa_myapplication_MainActivity 的相關句子也複製進來。
在.h檔已經定義過,若在.cpp重複定義,則中間的所有內容都會被忽略掉。

設定gradle
#

在defaultConfig這個區塊內加入

ndk{
  moduleName "myJNI"
  ldLibs "log"
}

moduleName為你剛剛在System.loadLibrary裡面設定的名字。
ldLibs “log"可以讓cpp檔include log功能的時候不會出錯。

整體看起來是這樣

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.example.isa.myapplication"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        ndk{
            moduleName "myJNI"
            ldLibs "log"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

最後編譯就完成了。

參考資料
#

Android studio 1.5.1 NDK JNI環境安裝與執行原理