JNI是一種本地編程接口隐砸。它允許運(yùn)行在JAVA虛擬機(jī)中的JAVA代碼和用其他編程語言圃庭,諸如C語言、C++病往、匯編捣染,應(yīng)用和庫之間的交互操作。 不只是Android特有的東西
1. Java調(diào)用c++方法
靜態(tài)加載so
static {
System.loadLibrary("native-lib");
}
c++中和java中對應(yīng)方法
public static native void native11();
extern "C"
JNIEXPORT jstring JNICALL
Java_com_dds_anyndk_AnyNdk_native11(JNIEnv *env, jclass type, jint a, jstring str_, jfloat f) {
// 獲得c字符串
const char *str = env->GetStringUTFChars(str_, JNI_FALSE);
char returnStr[100];
//格式化字符串
sprintf(returnStr, "C++ string:%d,%s,%f", a, str, f);
//釋放掉內(nèi)存
env->ReleaseStringUTFChars(str_, str);
return env->NewStringUTF(returnStr);
}
CmakeLists.txt配置
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-lib
SHARED
native11.cpp)
target_link_libraries(
native-lib
log)
2. JNI數(shù)據(jù)類型
JNIEXPORT 和 JNICALL停巷,定義在jni.h
頭文件中耍攘。
JNIEXPORT:
? 在 Windows 中,定義為__declspec(dllexport)
。因?yàn)閃indows編譯 dll 動態(tài)庫規(guī)定畔勤,如果動態(tài)庫中的函數(shù)要被外部調(diào)用蕾各,需要在函數(shù)聲明中添加此標(biāo)識,表示將該函數(shù)導(dǎo)出在外部可以調(diào)用庆揪。
? 在 Linux/Unix/Mac os/Android 這種 Like Unix系統(tǒng)中式曲,定義為__attribute__ ((visibility ("default")))
JNICALL:
? 在類Unix中無定義,在Windows中定義為:_stdcall
缸榛,一種函數(shù)調(diào)用約定
類Unix系統(tǒng)中這兩個(gè)宏可以省略不加吝羞。
Java類型 | 本地類型 | 描述 |
---|---|---|
boolean | jboolean | C/C++8位整型 |
byte | jbyte | C/C++帶符號的8位整型 |
char | jchar | C/C++無符號的16位整型 |
short | jshort | C/C++帶符號的16位整型 |
int | jint | C/C++帶符號的32位整型 |
long | jlong | C/C++帶符號的64位整型 |
float | jfloat | C/C++32位浮點(diǎn)型 |
double | jdouble | C/C++64位浮點(diǎn)型 |
Object | jobject | 任何Java對象,或者沒有對應(yīng)java類型的對象 |
Class | jclass | Class對象 |
String | jstring | 字符串對象 |
Object[] | jobjectArray | 任何對象的數(shù)組 |
boolean[] | jbooleanArray | 布爾型數(shù)組 |
byte[] | jbyteArray | 比特型數(shù)組 |
char[] | jcharArray | 字符型數(shù)組 |
short[] | jshortArray | 短整型數(shù)組 |
int[] | jintArray | 整型數(shù)組 |
long[] | jlongArray | 長整型數(shù)組 |
float[] | jfloatArray | 浮點(diǎn)型數(shù)組 |
double[] | jdoubleArray | 雙浮點(diǎn)型數(shù)組 |
獲取數(shù)組類型的數(shù)據(jù)
extern "C"
JNIEXPORT jstring JNICALL
Java_com_dds_anyndk_AnyNdk_native11_12(JNIEnv *env, jclass type, jobjectArray strs,
jintArray ints_) {
//1内颗、 獲得字符串?dāng)?shù)組
int32_t str_length = env->GetArrayLength(strs);
LOGD("字符串 數(shù)組長度:%d", str_length);
for (int i = 0; i < str_length; ++i) {
jstring str = jstring(env->GetObjectArrayElement(strs, i));
const char *c_str = env->GetStringUTFChars(str, JNI_FALSE);
LOGD("字符串有:%s", c_str);
//使用完釋放
env->ReleaseStringUTFChars(str, c_str);
}
//2钧排、獲得基本數(shù)據(jù)類型數(shù)組
int32_t int_length = env->GetArrayLength(ints_);
LOGD("int 數(shù)組長度:%d", int_length);
jint *ints = env->GetIntArrayElements(ints_, nullptr);
for (int i = 0; i < int_length; i++) {
LOGD("int 數(shù)據(jù)有:%d", ints[i]);
}
env->ReleaseIntArrayElements(ints_, ints, 0);
return env->NewStringUTF("hello");
}
3. C/C++反射Java
在C/C++中反射創(chuàng)建Java的對象,調(diào)用Java的方法
基本數(shù)據(jù)類型的簽名采用一系列大寫字母來表示, 如下表所示:
Java類型 | 簽名 |
---|---|
boolean | Z |
short | S |
float | F |
byte | B |
int | I |
double | D |
char | C |
long | J |
void | V |
引用類型 | L + 全限定名 + ; |
數(shù)組 | [+類型簽名 |
需要反射的方法
public class JavaHelper {
private static final String TAG = "dds_native11";
//private和public 對jni開發(fā)來說沒任何區(qū)別 都能反射調(diào)用
public void instanceMethod(String a, int b, boolean c) {
Log.e(TAG, "instanceMethod a=" + a + " b=" + b + " c=" + c);
}
public static void staticMethod(String a, int b, boolean c) {
Log.e(TAG, "staticMethod a=" + a + " b=" + b + " c=" + c);
}
}
反射調(diào)用方法
extern "C"
JNIEXPORT jstring JNICALL
Java_com_dds_anyndk_AnyNdk_native11_13(JNIEnv *env, jclass type) {
jclass class_helper = env->FindClass("com/dds/anyndk/JavaHelper");
// 反射調(diào)用靜態(tài)方法
jmethodID method_staticMethod = env->GetStaticMethodID(class_helper, "staticMethod",
"(Ljava/lang/String;IZ)V");
jstring staticStr = env->NewStringUTF("C++調(diào)用靜態(tài)方法");
env->CallStaticVoidMethod(class_helper, method_staticMethod, staticStr, 1, true);
// 反射調(diào)用構(gòu)造方法
jmethodID constructMethod = env->GetMethodID(class_helper,"<init>","()V");
jobject helper = env->NewObject(class_helper,constructMethod);
jmethodID instanceMethod = env->GetMethodID(class_helper,"instanceMethod","(Ljava/lang/String;IZ)V");
jstring instanceStr= env->NewStringUTF("C++調(diào)用實(shí)例方法");
env->CallVoidMethod(helper,instanceMethod,instanceStr,2,0);
// 釋放資源
env->DeleteLocalRef(class_helper);
env->DeleteLocalRef(staticStr);
env->DeleteLocalRef(instanceStr);
env->DeleteLocalRef(helper);
return env->NewStringUTF("dds");
}
可以使用javap來獲取反射方法時(shí)的簽名
javap -s com.dds.anyndk.JavaHelper
反射修改變量
需要反射的方法
public class JavaHelper {
int a = 10;
static String b = "java字符串";
public void testReflect(JavaHelper javaHelper) {
Log.e(TAG, "修改前 : a = " + a + " b=" + b);
AnyNdk.native11_4(javaHelper);
Log.e(TAG, "修改后 : a = " + a + " b=" + b);
}
}
反射
#define LOG_TAG "dds_native4"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__ )
extern "C"
JNIEXPORT void JNICALL
Java_com_dds_anyndk_AnyNdk_native11_14(JNIEnv *env, jclass type, jobject javaHelper) {
jclass clazz = env->GetObjectClass(javaHelper);
//獲得int a的標(biāo)示
jfieldID a = env->GetFieldID(clazz, "a", "I");
// 獲取a的值
int value = env->GetIntField(javaHelper, a);
LOGD("獲得java屬性a:%d", value);
env->SetIntField(javaHelper, a, 100);
// 獲取String b 的標(biāo)示
jfieldID b = env->GetStaticFieldID(clazz, "b", "Ljava.lang.String;");
// 獲取靜態(tài)變量的值
auto bStr = jstring(env->GetStaticObjectField(clazz, b));
const char *bc_str = env->GetStringUTFChars(bStr, JNI_FALSE);
LOGD("獲得java屬性b:%s", bc_str);
jstring new_str = env->NewStringUTF("C++字符串");
env->SetStaticObjectField(clazz, b, new_str);
// 釋放資源
env->ReleaseStringUTFChars(bStr, bc_str);
env->DeleteLocalRef(new_str);
env->DeleteLocalRef(clazz);
}
4. JNI_OnLoad
調(diào)用System.loadLibrary()函數(shù)時(shí)均澳, 內(nèi)部就會去查找so中的 JNI_OnLoad 函數(shù)恨溜,如果存在此函數(shù)則調(diào)用符衔。
JNI_OnLoad會:
告訴 VM 此 native 組件使用的 JNI 版本。
? 對應(yīng)了Java版本糟袁,android中只支持JNI_VERSION_1_2 判族、JNI_VERSION_1_4、JNI_VERSION_1_6
? 在JDK1.8有 JNI_VERSION_1_8项戴。
jint JNI_OnLoad(JavaVM* vm, void* reserved){
// 2形帮、4、6都可以
return JNI_VERSION_1_4;
}
動態(tài)注冊
在此之前我們一直在jni中使用的 Java_PACKAGENAME_CLASSNAME_METHODNAME 來進(jìn)行與java方法的匹配肯尺,這種方式我們稱之為靜態(tài)注冊沃缘。
而動態(tài)注冊則意味著方法名可以不用這么長了,在android aosp源碼中就大量的使用了動態(tài)注冊的形式
//Java:
native void dynamicNative();
native String dynamicNative(int i);
//C++:
void dynamicNative1(JNIEnv *env, jobject jobj){
LOGE("dynamicNative1 動態(tài)注冊");
}
jstring dynamicNative2(JNIEnv *env, jobject jobj,jint i){
return env->NewStringUTF("我是動態(tài)注冊的dynamicNative2方法");
}
//需要?jiǎng)討B(tài)注冊的方法數(shù)組
static const JNINativeMethod mMethods[] = {
{"dynamicNative","()V", (void *)dynamicNative1},
{"dynamicNative", "(I)Ljava/lang/String;", (jstring *)dynamicNative2}
};
//需要?jiǎng)討B(tài)注冊native方法的類名
static const char* mClassName = "com/dds/anyndk/AnyNdk";
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
//獲得 JniEnv
int r = vm->GetEnv((void **) &env, JNI_VERSION_1_4);
if (r != JNI_OK) {
return -1;
}
jclass activityCls = env->FindClass(mClassName);
// 注冊 如果小于0則注冊失敗
r = env->RegisterNatives(activityCls, mMethods, 2);
if (r != JNI_OK) {
return -1;
}
return JNI_VERSION_1_4;
}
5. c++線程中調(diào)用Java
native調(diào)用java需要使用JNIEnv這個(gè)結(jié)構(gòu)體则吟,而JNIEnv是由Jvm傳入與線程相關(guān)的變量槐臀。
但是可以通過JavaVM的AttachCurrentThread方法來獲取到當(dāng)前線程中的JNIEnv指針。
JavaVM* _vm = 0;
jobject _instance = 0;
jint JNI_OnLoad(JavaVM* vm, void* reserved){
_vm = vm;
return JNI_VERSION_1_4;
}
void *task(void *args) {
JNIEnv *env;
_vm->AttachCurrentThread(&env, 0);
jclass clazz = env->GetObjectClass(_instance);
jmethodID methodId = env->GetStaticMethodID(clazz, "staticMethod", "(Ljava/lang/String;IZ)V");
jstring staticStr = env->NewStringUTF("C++調(diào)用靜態(tài)方法");
env->CallStaticVoidMethod(clazz, methodId, staticStr, 1, true);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(staticStr);
_vm->DetachCurrentThread();
return 0;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_dds_anyndk_AnyNdk_native11_15(JNIEnv *env, jclass type, jobject javaHelper) {
pthread_t pid;
_instance = env->NewGlobalRef(javaHelper);
// 開啟線程
pthread_create(&pid, 0, task, 0);
}