NDK開發-零散知識點整理

JavaVM

標準Java平臺下,每個Process能夠產生不少JavaVM對象,但在Android平臺上,每個Process只能產生一個Dalvik VM對象,也就是說在Android進程中是經過一個虛擬器對象來服務全部Java和c/c++代碼。html

JavaVM使用

  • 在加載動態連接庫的時候,JVM會調用JNI_OnLoad(JavaVM* jvm, void* reserved)(若是定義了該函數)。第一個參數會傳入JavaVM指針。java

  • 在native code中調用JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)能夠獲得JavaVM指針。android

兩種狀況下,均可以用全局變量,好比JavaVM* g_jvm來保存得到的指針以便在任意上下文中使用。c++

JNIEnv

JNIEnv是一個指針, 指向一個線程相關的結構, 線程相關結構指向JNI函數指針數組, 這個數組中存放了大量的JNI函數指針, 這些指針指向了具體的JNI函數。
數組

JNIEnv做用:

  • 調用Java函數:JNIEnv表明Java運行環境, 可使用JNIEnv調用Java中的代碼;緩存

  • 操做Java對象:Java對象傳入JNI層就是Jobject對象, 須要使用JNIEnv來操做這個Java對象;markdown

JNIEnv使用:

當本地c/c++代碼想得到當前線程的JNIEnv時,可使用兩種方法:oracle

  • vm->AttachCurrentThread(&env, 0)
  • vm->GetEnv((void**)&env, JNI_VERSION_1_6)

須要強調的是JNIEnv是跟線程相關的,最好仍是不要緩存這個JNIEnv* 。app

當建立的線程須要獲取JNIEnv* 的時候,最好在剛建立的時候調用一次AttachCurrentThread,而且不要忘記線程結束的時候執行DettachCurrentThread。jvm

實例方法&靜態方法

實例方法:

public native String getStringFromNative();
原生實例方法經過第二個參數獲取實例引用,是jobject類型:

JNIEXPORT jstring JNICALL Java_com_dean_testndk_JNIHelper_getStringFromNative
        (JNIEnv *, jobject);

靜態方法:

public static native String getStringFromNative();
靜態方法沒有與實例綁定,所以第二個參數是jclass類型:

JNIEXPORT jstring JNICALL Java_com_dean_testndk_JNIHelper_getStringFromNative
        (JNIEnv *, jclass);

C/C++

原生代碼中,c與c++調用JNI函數的語法不一樣:

C:

return (*env)->NewStringUTF(env, "Hello from JNI !");

C代碼中,JNIEnv是指向JNIVativeInterface結構的指針,爲了訪問任何一個JNI函數,該指針須要首先被解引用。由於不瞭解JNI環境,因此須要將JNIEnv傳遞給調用者

C++:

return env->NewStringUTF("Hello from JNI !");

C++代碼中,JNIEnv是C++類實例,JNI函數以成員函數的形式存在。所以不須要給調用着傳遞參數便可使用。

數據類型

Java中有兩種數據類型:

基本數據類型:

boolean, byte, char, short, int, long, float, double

Java基本數據類型,能夠直接與C/C++的相應基本數據類型映射

引用類型:

字符串類,數組類,以及其餘類

與基本類型不一樣,引用類型對原生方法不透明,所以引用類型不能直接使用和修改,須要經過JNIEnv接口指針來調用JNI提供的API。

類&對象

獲取類

  • FindClass:jclass FindClass(JNIEnv *env, const char *name);
    經過傳入類的徹底限定名(注意JNI這邊類的徹底限定名經過」/」分隔,而不是Java那邊的」.」),便可獲得一個對應的jclass對象。
jclass clz = (*env)->FindClass(env, "com/example/hello_jnicallback/JniHandler");
  • GetObjectClass: jclass GetObjectClass(JNIEnv *env, jobject obj);
    根據一個jobject對象獲得這個對象的jclass對象。這事獲取jclass的另外一種方式。
jclass clz = (*env)->GetObjectClass(env, instance);

建立對象

  • jobject NewObject(JNIEnv *env, jclass clazz,
    jmethodID methodID, ...);

經過類,方法ID,對應參數來建立一個對象的實例:

jclass clz = (*env)->FindClass(env, "com/example/hello_jnicallback/JniHandler");

jmethodID jniHelperCtor = (*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");

jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);

域&方法

在JNI開發中,常常會在Native中調用Java的域和方法。

Java的域和方法都有兩類:

  • 實例域,靜態域;
  • 實例方法,靜態方法;

以下:

public class JavaClass {

    // 實例域
    private String instanceFiled = "Instance Field";
    // 靜態域
    private static String staticFiled = "Static Field";

    // 實例方法
    private String instanceMethod() {
        return "Instance Method";
    }
    // 靜態方法
    private static String staticMethod() {
        return "Static Method";
    }
}

下面例子是獲取實例域,方法的代碼。靜態域,靜態方法使用基本相同,都是先獲取描述它的ID,而後在經過ID調用相應方法。

void nativeCallJavaClass(JNIEnv *env, jobject instance) {

    jclass clazz = env->GetObjectClass(instance);

    // 獲取實例域的FieldID
    jfieldID instanceFieldId = env->GetFieldID(clazz, "instanceField", "Ljava/lang/String;");
    // 獲取實例域
    jstring instanceField = (jstring) env->GetObjectField(instance, instanceFieldId);

    // 獲取實例方法的MethodID
    jmethodID instanceMethodId = env->GetMethodID(clazz, "staticMethod", "Ljava/lang/String;");
    // 調用方法得到返回值
    jstring  instanceMethod = (jstring) env->CallObjectMethod(clazz, instanceMethodId);

};

爲了提高應用程序的性能,對於頻繁使用的域和方法,能夠緩存它們的ID方便下次使用。

描述符

域描述符

  • 基本類型的描述符
  • 引用類型的描述符
    • 引用類型:L + 該類型類描述符 + ;
      • String類型的域描述符爲 Ljava/lang/String;
    • 數組:[ + 其類型的域描述符 + ;
      • int[] [I
      • float[] [F
      • String[] [Ljava/lang/String;
      • Object[] [Ljava/lang/Object;
    • 多維數組:n個[ +該類型的域描述符 , N表明的是幾維數組。
      • int[][] [[I
      • float[][] [[F

類描述符

  • 類描述符:將完整的包名+類名中的.分隔符換成/分隔符

    • java.lang.String類的類描述符爲:java/lang/String或者使用域描述符[Ljava/lang/String;
  • 數組類型的描述符:同域描述符

方法描述符

  • String test() Ljava/lang/String;
  • int f(int i, Object object) (ILjava/lang/Object;)I
  • void set(byte[ ] bytes) ([B)V

Javap

java在bin中提供了javap命令,用於查看一個類方法的簽名

cd到.class對應路徑,而後執行javap -s classname
例如:

cd /Users/DeanGuo/TestNDK/app/build/intermediates/classes/debug/com/dean/testndk/;

javap -s JNIHelper

Compiled from "JNIHelper.java"
public class com.dean.testndk.JNIHelper {
  public com.dean.testndk.JNIHelper();
    descriptor: ()V

  public static native java.lang.String getStringFromNative();
    descriptor: ()Ljava/lang/String;
}
bogon:testndk DeanGuo$ javap -s JNIHelper
Warning: Binary file JNIHelper contains com.dean.testndk.JNIHelper
Compiled from "JNIHelper.java"
public class com.dean.testndk.JNIHelper {
  public com.dean.testndk.JNIHelper();
    descriptor: ()V

  public static native java.lang.String getStringFromNative();
    descriptor: ()Ljava/lang/String;
}

局部&全局引用

局部引用

局部引用不能在後續的調用中唄緩存及重用,主要由於它們的使用期限僅限於原生方法,一旦原生函數返回,局部引用就被釋放。也能夠用void DeleteLocalRef(JNIEnv *env, jobject localRef);函數顯示的釋放。

全局引用

全局引用在原生方法的後續調用過程當中依然有效,除非它們被原生代碼顯式釋放。

  • 建立全局引用:
jclass globalClazz;
jclass localClazz = env->FindClass("java/lang/String");
globalClazz = (jclass) env->NewGlobalRef(localClazz);
  • 刪除全局引用:
env->DeleteGlobalRef(globalClazz);

弱全局引用

與全局應用同樣,弱全局引用在原生方法的後續調用過程當中依然有效。不一樣的是,弱全局引用並不阻止潛在對象被垃圾回事。

  • 建立弱全局引用:
jclass weakGlobalClazz;
jclass localClazz = env->FindClass("java/lang/String");
weakGlobalClazz = (jclass) env->NewWeakGlobalRef(localClazz);
  • 有效性檢驗:
if (JNI_FALSE == env->IsSameObject(weakGlobalClazz, nullptr)) {
    // 有效
} else {
    // 對象被垃圾回收器回收, 不可以使用
}
  • 刪除弱全局引用:
env->DeleteGlobalRef(weakGlobalClazz);

LOG

NDK開發中JNI時,可使用__android_log_print打印log信息。

使用步驟:

  • 引入#include <android/log.h>頭文件
  • 加入Lib

    • gralde:在ndk標籤下加入ldLibs "log"
    • Android.mk:加入LOCAL_LDLIBS+= -L$(SYSROOT)/usr/lib -llog
  • 使用__android_log_print(ANDROID_LOG_INFO, "JNITag","string From Java To C : %s", str);

封裝:

#include <android/log.h>

// Android log function wrappers
static const char* kTAG = "testNDK";
#define LOGI(...) \
  ((void)__android_log_print(ANDROID_LOG_INFO, kTAG, __VA_ARGS__))
#define LOGW(...) \
  ((void)__android_log_print(ANDROID_LOG_WARN, kTAG, __VA_ARGS__))
#define LOGE(...) \
  ((void)__android_log_print(ANDROID_LOG_ERROR, kTAG, __VA_ARGS__))

// 使用以下:
LOGI("JNI_LOG");

標準庫

若是想在NDK中使用c++STL庫:

  • Application.mk:加入APP_STL := gnustl_static
  • gralde:在ndk標籤下加入stl "gnustl_static"
  • system:使用默認最小的C++運行庫,這樣生成的應用體積小,內存佔用小,但部分功能將沒法支持
  • stlport_static:使用STLport做爲靜態庫,這項是Android開發網極力推薦的
  • stlport_shared:STLport 做爲動態庫,這個可能產生兼容性和部分低版本的Android固件,目前不推薦使用。
  • gnustl_static:使用 GNU libstdc++ 做爲靜態庫

文章連接:

JNI官方文檔

相關文章
相關標籤/搜索