2023年7月7日发(作者:)
Android中的JNI使⽤简介⼀、本⽂说明本⽂不对Android⼯程的各种配置做说明,只是简单介绍下开发过程中如何进⾏C与Java互相调⽤以及出现异常情况的处理。⼆、NDK简介Android NDK 是⼀套允许您使⽤ C 和 C++ 等语⾔,以原⽣代码实现部分应⽤的⼯具集。在开发某些类型的应⽤时,这有助于您重复使⽤以这些语⾔编写的代码库。三、JNI简介JNI是Java Native Interface的缩写,它提供了若⼲的API实现了Java和其他语⾔的通信(主要是C&C++)。从Java1.1开始,JNI标准成为Java平台的⼀部分,它允许Java代码和其他语⾔写的代码进⾏交互。四、Android Studio创建简单的JNI⼯程在创建Android Project时,在创建的⾸页中勾选Include C++ support选项即可创建⼀个模板的hello world程序,可直接运⾏。相⽐于普通的Android⼯程,勾选该项后的⼯程有以下不同:⼯程⽬录gradle配置可以看到,多出了⼀个(C++代码)⽂件和⼀个(C++相关配置)⽂件,app模块下的⽂件也添加了相关配置。在该默认⼯程中,我们可以看到⼀些JNI相关代码:Java中加载本地库,并且定义native⽅法: // 加载的library名称,注意:不是C++⽂件的名称 static { brary("native-lib"); } /** * java中定义⽅法的名称,会根据包名、类名寻、参数、返回值类型寻找对应的C++⽅法 */ public native String stringFromJNI();C++中定义好对应的⽅法://对应于java中的stringFromJNI⽅法extern "C" JNIEXPORT jstring JNICALL Java_com_wsy_jnitest_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str());}这样即可实现C++向Java传递⼀个String五、Java签名介绍1. 什么是签名?为了介绍后⾯的东西,先介绍⼀个概念,类型签名。以下段落摘抄⾃维基百科:In computer science, a type signature or type annotation defines the inputs and outputs for a function,subroutine or method. A type signature includes the number of arguments, the types of arguments and theorder of the arguments contained by a function. A type signature is typically used during overloadresolution for choosing the correct definition of a function to be called among many overloaded the Java virtual machine,
internal type signatures are used to identify methods and classes at the level ofthe virtual machine e: The method
String ing(int, int) is represented in bytecode as
Ljava/lang/ing(II)Ljava/lang/String;.The signature of
main() method looks like this:public static void main(String[] args)And in the disassembled bytecode, it takes the form ofLsome/package/Main/main:([Ljava/lang/String;) method signature for the main() method contains three modifiers:publicstaticvoid indicates that the main() method can be called by any object. indicates that the main() method is a class method. indicates that the main() method has no return value.译:在计算机科学中,类型签名或类型注释定义了函数,⼦程序或⽅法的输⼊和输出。类型签名包括参数的数量,参数的类型以及函数包含的参数的顺序。在重载解析期间通常使⽤类型签名来选择在许多重载函数中正确的那⼀项。在Java虚拟机中,内部类型签名⽤于标识虚拟机代码级别的⽅法和类。⽰例:⽅法String ing(int,int)在字节码中表⽰为Ljava/lang/ing(II)Ljava/lang/String;。⽅法main()的签名如下所⽰:public static void main(String[] args)在反汇编的字节码中,它采⽤Lsome/package/Main/main:([Ljava/lang/String;)V的形式。main()publicstaticvoid⽅法的⽅法签名包含三个修饰符:表⽰main()⽅法可以被任何对象调⽤。表⽰main()⽅法是⼀个类⽅法。表⽰main()⽅法没有返回值。简单来说,签名就是能确保⼀个函数或⼀个变量的数据。jni在寻找⼀个java函数或者变量时,⼀般以如下⽅式寻找:寻找函数:需要知道java函数的函数名、返回值类型、参数类型寻找变量:需要知道java变量的变量名、数据类型2.如何获取签名根据规则⾃⼰编写在oracle相关⽂档中可以查到:Type SignatureZBCSIJFDLfully-qualified-class;[type( arg-types ) ret-typeJava Typebooleanbytecharshortintlongfloatdoublefully-qualified-classtype[]method typeFor example, the Java method:long f (int n, String s, int[] arr);has the following type signature:(ILjava/lang/String;[I)J使⽤javap命令获取1.获取class⽂件的签名javap -s classFile例如获取中⽅法和变量的签名(Java⽂件在底部github分享中):javap -s D:android-projectstudyJNIDemoappbuildintermediatesclassesdebugcomwsyjnidemoMainActivityCompiled from ""public class tivity extends patActivity { public tivity(); descriptor: ()V protected void onCreate(); descriptor: (Landroid/os/Bundle;)V public native testExceptionCrash() throws Exception; descriptor: ()Ljava/lang/String; public native testExceptionNotCrash(int) throws Exception; descriptor: (I)Ljava/lang/String; public native void nativeShowToast(ty); descriptor: (Landroid/app/Activity;)V public native void testCallJava(tivity); descriptor: (Lcom/wsy/jnidemo/MainActivity;)V public native void methodNotExists(); descriptor: ()V public void nativeThrowException(); descriptor: (Landroid/view/View;)V public void cCallJava(); descriptor: (Ljava/lang/String;)V public void callJavaFromC(); descriptor: (Landroid/view/View;)V public void nativeShowToast(); descriptor: (Landroid/view/View;)V public void callMethodNotExists(); descriptor: (Landroid/view/View;)V public void wrongSampleUsingJNIEnv(); descriptor: (Landroid/view/View;)V static {}; descriptor: ()V}2.获取jar中的class的⽅法和变量的签名javap -classpath -s fullyQualifiedClass例如获取中,类中⽅法和变量的签名:javap -classpath -s ompiled from ""public class { public static final int LENGTH_LONG; descriptor: I public static final int LENGTH_SHORT; descriptor: I public (t); descriptor: (Landroid/content/Context;)V public void show(); descriptor: ()V public void cancel(); descriptor: ()V public void setView(); descriptor: (Landroid/view/View;)V public getView(); descriptor: ()Landroid/view/View; public void setDuration(int); descriptor: (I)V public int getDuration(); descriptor: ()I public void setMargin(float, float); descriptor: (FF)V public float getHorizontalMargin(); descriptor: ()F public float getVerticalMargin(); descriptor: ()F public void setGravity(int, int, int); descriptor: (III)V public int getGravity(); descriptor: ()I public int getXOffset(); descriptor: ()I public int getYOffset(); descriptor: ()I public static makeText(t, quence, int); descriptor: (Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast; public static makeText(t, int, int) throws ces$NotFoundException; descriptor: (Landroid/content/Context;II)Landroid/widget/Toast; public void setText(int); descriptor: (I)V public void setText(quence); descriptor: (Ljava/lang/CharSequence;)V}六、Java调⽤C++⽅法JNI⽅法的注册⼀般分为两种:静态注册和动态注册。静态注册就是通过固定的命名规则映射Java和native函数;动态注册⼀般是通过重写JNI_OnLoad函数,⽤jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)函数将Java中定义的native函数和C/C++中定义的函数进⾏映射。静态注册:在Java代码中定义好native⽅法,并且在C++代码中编写好对应的⽅法:extern "C" JNIEXPORT RETURN_TYPE JNICALL Java_PackageConnectedByUnderline_ClassName_FunctionName(JNIEnv *env, jobject /* this */, ...params)动态注册:编写JNI_OnLoad函数,在其内部实现动态注册,⽰例代码如下jstring dynamicRegister(JNIEnv *jniEnv, jobject obj) { return jniEnv->NewStringUTF("dynamicRegister");}int JNI_OnLoad(JavaVM *javaVM, void *reserved) { JNIEnv *jniEnv; if (JNI_OK == javaVM->GetEnv((void **) (&jniEnv), JNI_VERSION_1_4)) { // 动态注册的Java函数所在的类 jclass registerClass = jniEnv->FindClass("com/wsy/jnidemo/MainActivity"); JNINativeMethod jniNativeMethods[] = { //3个参数分别为 Java函数的名称,Java函数的签名(不带函数名),本地函数指针 {"dynamicRegister", "()Ljava/lang/String;", (void *) (dynamicRegister)} }; if (jniEnv->RegisterNatives(registerClass, jniNativeMethods, sizeof(jniNativeMethods) / sizeof((jniNativeMethods)[0])) < 0) { return JNI_ERR; } } return JNI_VERSION_1_4;}其中,java的Object对象传递给C++时类型都⽤jobject表⽰,java中的基础类型都⽤j基础类型表⽰,java中的数组对象类型(java数组对象类型其实也是Object类型)都⽤jXXXXArray表⽰(包含Object数组和基础类型数组),具体如下:基础类型:/* Primitive types that match up with Java equivalents. */typedef uint8_t jboolean; /* unsigned 8 bits */typedef int8_t jbyte; /* signed 8 bits */typedef uint16_t jchar; /* unsigned 16 bits */typedef int16_t jshort; /* signed 16 bits */typedef int32_t jint; /* signed 32 bits */typedef int64_t jlong; /* signed 64 bits */typedef float jfloat; /* 32-bit IEEE 754 */typedef double jdouble; /* 64-bit IEEE 754 */Object(数组、异常等):/* * Reference types, in C++ */class _jobject {};class _jclass : public _jobject {};class _jstring : public _jobject {};class _jarray : public _jobject {};class _jobjectArray : public _jarray {};class _jbooleanArray : public _jarray {};class _jbyteArray : public _jarray {};class _jcharArray : public _jarray {};class _jshortArray : public _jarray {};class _jintArray : public _jarray {};class _jlongArray : public _jarray {};class _jfloatArray : public _jarray {};class _jdoubleArray : public _jarray {};class _jthrowable : public _jobject {};typedef _jobject* jobject;typedef _jclass* jclass;typedef _jstring* jstring;typedef _jarray* jarray;typedef _jobjectArray* jobjectArray;typedef _jbooleanArray* jbooleanArray;typedef _jbyteArray* jbyteArray;typedef _jcharArray* jcharArray;typedef _jshortArray* jshortArray;typedef _jintArray* jintArray;typedef _jlongArray* jlongArray;typedef _jfloatArray* jfloatArray;typedef _jdoubleArray* jdoubleArray;typedef _jthrowable* jthrowable;typedef _jobject* jweak;例如:tivity类中的定义的⽅法public native void testCallJava(MainActivity activity)对应的C++⽅法为extern "C" JNIEXPORT voidJNICALLJava_com_wsy_jnidemo_MainActivity_testCallJava( JNIEnv *env, jobject /* this */, jobject activity)tivity类中的定义的⽅法public native String testExceptionNotCrash(int i) throws CustomException;对应的C++⽅法为extern "C" JNIEXPORT jstringJNICALLJava_com_wsy_jnidemo_MainActivity_testExceptionNotCrash( JNIEnv *env, jobject /* this */, jint i)七、C++中调⽤Java⽅法1.⾸先获取Java类根据包名+类名获取Java类//参数name是带包名的类名,例如要获取类,那么写法就是//jclass cls = env->FindClass("android/widget/Toast");jclass FindClass(const char* name)获取jobject的Java类//java中的Object对象传给native层后即为jobject,我们可根据jobject获取其Java类jclass GetObjectClass(jobject obj)2.再获取Java类中指定的⽅法获取成员⽅法//参数clazz是java类,name是java⽅法名称,sig是java⽅法签名jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)获取静态⽅法//参数clazz是java类,name是java⽅法名称,sig是java⽅法签名jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)3.调⽤Java⽅法调⽤⽆返回值的成员⽅法//参数obj是被调⽤⽅法的java对象,methodID是使⽤GetMethodID获取的回传值,后⾯填⽅法需要的参数void CallVoidMethod(jobject obj, jmethodID methodID, ...)调⽤带返回值的成员⽅法//调⽤返回值类型为Object的⽅法jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID methodId, ...);//调⽤返回值类型为基本类型的⽅法jint CallIntMethod(JNIEnv* env, jobject obj, jmethodID methodId, ...);jlong CallLongMethod(JNIEnv* env, jobject obj, jmethodID methodId, ...);jfloat CallFloatMethod(JNIEnv* env, jobject obj, jmethodID methodId, ...);...调⽤⽆返回值的静态⽅法//参数clazz是被调⽤⽅法的类,methodID是使⽤GetStaticMethodID获取的回传值,后⾯填⽅法需要的参数void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)调⽤带返回值的静态⽅法//调⽤返回值类型为Object的⽅法jobject CallStaticObjectMethod(jclass clazz, jmethodID methodId, ...);//调⽤返回值类型为基本类型的⽅法jint CallStaticIntMethod(JNIEnv* clazz, jclass, jmethodID, ...);jlong CallStaticLongMethod(JNIEnv* clazz, jclass, jmethodID, ...);jfloat CallStaticFloatMethod(JNIEnv* clazz, jclass, jmethodID, ...);...⼋、C++中获取Java类中的变量获取类中的成员变量⾸先获取jfieldID//参数clazz是被获取成员变量的对象的类,name是变量名,sig是变量的签名jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)再根据jfieldID获取成员变量//获取Object类型成员变量jobject GetObjectField(jobject obj, jfieldID fieldID)//获取基础类型成员变量jint GetIntField(jobject obj, jfieldID fieldID)jlong GetLongField(jobject obj, jfieldID fieldID)jfloat GetFloatField(jobject obj, jfieldID fieldID)....获取类中的静态变量和获取成员变量相似,⾸先获取jfieldIDjfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)再根据fieldID获取静态变量//获取Object类型静态变量jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)//获取基础类型静态变量jint GetStaticIntField(jclass clazz, jfieldID fieldID)jlong GetStaticLongField(jclass clazz, jfieldID fieldID)jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID)....九、在native层向Java抛出Exception⾸先需要找到Java中的Exception类,再使⽤ThrowNew⽅法抛出,要注意最终将jclass释放jclass exceptionCls = env->FindClass("com/wsy/jnidemo/CustomException");env->ThrowNew(exceptionCls, "i am an exception");env->DeleteLocalRef(exceptionCls);
需要注意的是,在exception发⽣时,在native层已不能调⽤⼤部分的⽅法,例如对象创建。NDK官⽹说明如下:You must not call most JNI functions while an exception is pending. Your code is expected to notice theexception (via the function's return value,ExceptionCheck, or ExceptionOccurred) and return, or clear theexception and handle only JNI functions that you are allowed to call while an exception is pending are:DeleteGlobalRefDeleteLocalRefDeleteWeakGlobalRefExceptionCheckExceptionClearExceptionDescribeExceptionOccurredMonitorExitPopLocalFramePushLocalFrameRelease
发布者:admin,转转请注明出处:http://www.yc00.com/xiaochengxu/1688678000a161795.html
评论列表(0条)