在上一篇文章中,咱们已经了解了第一个JNI工程的一些细节,以及对JNI调用进行了一些简单说明。接下来详细介绍下如何在Java和Native代码之间进行互相调用。html
再看一下native函数:java
extern "C" JNIEXPORT jstring JNICALL
Java_com_wsy_jnidemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
复制代码
这个函数的内容也很简单,建立了一个string
对象,而后将其转换为jstring
对象并回传。android
咱们看一下NewStringUTF这个函数:git
声明github
jstring NewStringUTF(const char* bytes)
复制代码
art/runtime/jni/jni_internal.cc 中的实现(其余函数也能够参考这里的实现)数组
static jstring NewStringUTF(JNIEnv* env, const char* utf) {
if (utf == nullptr) {
return nullptr;
}
ScopedObjectAccess soa(env);
ObjPtr<mirror::String> result = mirror::String::AllocFromModifiedUtf8(soa.Self(), utf);
return soa.AddLocalReference<jstring>(result);
}
复制代码
该函数的功能是根据传入的const char*
对象建立一个java.lang.String
对象。sass
一个简单的native函数介绍完了,接下来介绍下Java和Native的调用方法。bash
在进行Java和Native的交互前,咱们先理一下,他们是怎样确保对方的惟一性的?oracle
(包括native函数、Java函数、Java变量、Java类)app
native:
对于native函数,咱们经过函数名、函数参数、返回值类型确保其惟一性。
Java:
对于Java而言,每一个函数、变量、类,都有其惟一标识,就是签名,这里着重介绍下Java中的签名。
如下段落摘抄自维基百科:
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 the order of the arguments contained by a function. A type signature is typically used during overload resolution for choosing the correct definition of a function to be called among many overloaded forms.
In the Java virtual machine, internal type signatures are used to identify methods and classes at the level of the virtual machine code.
Example: The methodString String.substring(int, int)
is represented in bytecode asLjava/lang/String.substring(II)Ljava/lang/String;
.
The signature ofmain()
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;)V
.
The method signature for the main() method contains three modifiers:
public
indicates that the main() method can be called by any object.
static
indicates that the main() method is a class method.
void
indicates that the main() method has no return value.
译:
在计算机科学中,类型签名或类型注释定义了函数,子程序或方法的输入和输出。类型签名包括参数的数量,参数的类型以及函数包含的参数的顺序。在重载解析期间一般使用类型签名来选择在许多重载函数中正确的那一项。
在Java虚拟机中,内部类型签名用于标识虚拟机代码级别的方法和类。
示例: 方法String String.substring(int,int)
在字节码中表示为Ljava/lang/String.substring(II)Ljava/lang/String;
。
方法main()
的签名以下所示:
public static void main(String[] args)
在反汇编的字节码中,它采用Lsome/package/Main/main:([Ljava/lang/String;)V
的形式。
main()
方法的方法签名包含三个修饰符:
public
表示main()方法能够被任何对象调用。
static
表示main()方法是一个类方法。
void
表示main()方法没有返回值。
简单来讲,签名就是能确保一个函数或一个变量的数据。jni在寻找一个java函数或者变量时,通常以以下方式寻找:
根据规则本身编写
在oracle相关文档中能够查到:
Type Signature | Java Type |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
Lfully-qualified-class; | fully-qualified-class |
[type | type[] |
( arg-types ) ret-type | method type |
For example, the Java method:
long f (int n, String s, int[] arr);
has the following type signature:(ILjava/lang/String;[I)J
使用javap命令获取
获取class文件的签名
javap -s classFile
复制代码
例如获取MainActivity.class中方法和变量的签名(Java文件在底部github分享中):
javap -s D:\android-project\study\JNIDemo\app\build\intermediates\classes\debug\com\wsy\jnidemo\MainActivity
复制代码
Compiled from "MainActivity.java"
public class com.wsy.jnidemo.MainActivity extends android.support.v7.app.AppCompatActivity {
public com.wsy.jnidemo.MainActivity();
descriptor: ()V
protected void onCreate(android.os.Bundle);
descriptor: (Landroid/os/Bundle;)V
public native java.lang.String testExceptionCrash() throws com.wsy.jnidemo.CustomException;
descriptor: ()Ljava/lang/String;
public native java.lang.String testExceptionNotCrash(int) throws com.wsy.jnidemo.CustomException;
descriptor: (I)Ljava/lang/String;
public native void nativeShowToast(android.app.Activity);
descriptor: (Landroid/app/Activity;)V
public native void testCallJava(com.wsy.jnidemo.MainActivity);
descriptor: (Lcom/wsy/jnidemo/MainActivity;)V
public native void methodNotExists();
descriptor: ()V
public void nativeThrowException(android.view.View);
descriptor: (Landroid/view/View;)V
public void cCallJava(java.lang.String);
descriptor: (Ljava/lang/String;)V
public void callJavaFromC(android.view.View);
descriptor: (Landroid/view/View;)V
public void nativeShowToast(android.view.View);
descriptor: (Landroid/view/View;)V
public void callMethodNotExists(android.view.View);
descriptor: (Landroid/view/View;)V
public void wrongSampleUsingJNIEnv(android.view.View);
descriptor: (Landroid/view/View;)V
static {};
descriptor: ()V
}
复制代码
获取jar中的class的方法和变量的签名
javap -classpath XXX.jar -s fullyQualifiedClass
复制代码
例如获取android.jar中,android.widget.Toast
类中方法和变量的签名:
javap -classpath android.jar -s android.widget.Toast
复制代码
Compiled from "Toast.java"
public class android.widget.Toast {
public static final int LENGTH_LONG;
descriptor: I
public static final int LENGTH_SHORT;
descriptor: I
public android.widget.Toast(android.content.Context);
descriptor: (Landroid/content/Context;)V
public void show();
descriptor: ()V
public void cancel();
descriptor: ()V
public void setView(android.view.View);
descriptor: (Landroid/view/View;)V
public android.view.View 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 android.widget.Toast makeText(android.content.Context, java.lang.CharSequence, int);
descriptor: (Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
public static android.widget.Toast makeText(android.content.Context, int, int) throws android.content.res.Resources$NotFoundException;
descriptor: (Landroid/content/Context;II)Landroid/widget/Toast;
public void setText(int);
descriptor: (I)V
public void setText(java.lang.CharSequence);
descriptor: (Ljava/lang/CharSequence;)V
}
复制代码
以上介绍了Java中的签名,下面介绍下如何使用签名进行Java和Native的交互。
其中,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 */
复制代码
/* * 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;
复制代码
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) 复制代码
例如: com.wsy.jnidemo.MainActivity
类中的定义的方法
public native void testCallJava(MainActivity activity) 复制代码
对应的C++方法为
extern "C" JNIEXPORT void JNICALL Java_com_wsy_jnidemo_MainActivity_testCallJava( JNIEnv *env, jobject /* this */, jobject activity) 复制代码
com.wsy.jnidemo.MainActivity
类中的定义的方法
public native String testExceptionNotCrash(int i) throws CustomException;
复制代码
对应的C++方法为
extern "C" JNIEXPORT jstring JNICALL Java_com_wsy_jnidemo_MainActivity_testExceptionNotCrash( JNIEnv *env, jobject /* this */, jint i) 复制代码
动态注册:
编写JNI_OnLoad
函数,在其内部实现动态注册,示例代码以下
Java:
public native String dynamicRegister();
复制代码
C++:
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;
}
复制代码
须要注意到的是,在进行动态注册时,因为动态注册时传入的是函数指针,所以即便函数名发生了变动也不会致使运行时找不到对应的函数,因此不用加extern "C"
、JNIEXPORT
这些标识。
顺便一提,翻阅Android源码中RegisterNative
源码的具体实现时发现了这么一段代码;
if (*sig == '!') {
is_fast = true;
++sig;
}
复制代码
意思就是,若是签名是以!
开头的话,会把这个函数标记为fast jnicall
,看到后我立刻试了下,可是发现加不加好像速度上并无什么区别。后来回来继续看代码,很遗憾的发现,后面又加了这么一段代码和注释:
if (UNLIKELY(is_fast)) {
// There are a few reasons to switch:
// 1) We don't support !bang JNI anymore, it will turn to a hard error later. // 2) @FastNative is actually faster. At least 1.5x faster than !bang JNI. // and switching is super easy, remove ! in C code, add annotation in .java code. // 3) Good chance of hitting DCHECK failures in ScopedFastNativeObjectAccess // since that checks for presence of @FastNative and not for ! in the descriptor. LOG(WARNING) << "!bang JNI is deprecated. Switch to @FastNative for " << m->PrettyMethod(); is_fast = false; // TODO: make this a hard register error in the future. } 复制代码
意思就是说,这种前面加个!
的方式被弃用了,推荐在Java
代码中添加@FastNative
这个注解,运行后的确也打印出了这段日志。
Java
代码中添加这个注解,发现加不了,这玩意是在dalvik.annotation.optimization
包下的, 普通的开发者用不了。 下面是相关的代码和注释:
static jint RegisterNatives(JNIEnv* env,
jclass java_class,
const JNINativeMethod* methods,
jint method_count) {
if (UNLIKELY(method_count < 0)) {
JavaVmExtFromEnv(env)->JniAbortF("RegisterNatives", "negative method count: %d",
method_count);
return JNI_ERR; // Not reached except in unit tests.
}
CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives", java_class, JNI_ERR);
ScopedObjectAccess soa(env);
StackHandleScope<1> hs(soa.Self());
Handle<mirror::Class> c = hs.NewHandle(soa.Decode<mirror::Class>(java_class));
if (UNLIKELY(method_count == 0)) {
LOG(WARNING) << "JNI RegisterNativeMethods: attempt to register 0 native methods for "
<< c->PrettyDescriptor();
return JNI_OK;
}
CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives", methods, JNI_ERR);
for (jint i = 0; i < method_count; ++i) {
const char* name = methods[i].name;
const char* sig = methods[i].signature;
const void* fnPtr = methods[i].fnPtr;
if (UNLIKELY(name == nullptr)) {
ReportInvalidJNINativeMethod(soa, c.Get(), "method name", i);
return JNI_ERR;
} else if (UNLIKELY(sig == nullptr)) {
ReportInvalidJNINativeMethod(soa, c.Get(), "method signature", i);
return JNI_ERR;
} else if (UNLIKELY(fnPtr == nullptr)) {
ReportInvalidJNINativeMethod(soa, c.Get(), "native function", i);
return JNI_ERR;
}
bool is_fast = false;
// Notes about fast JNI calls:
//
// On a normal JNI call, the calling thread usually transitions
// from the kRunnable state to the kNative state. But if the
// called native function needs to access any Java object, it
// will have to transition back to the kRunnable state.
//
// There is a cost to this double transition. For a JNI call
// that should be quick, this cost may dominate the call cost.
//
// On a fast JNI call, the calling thread avoids this double
// transition by not transitioning from kRunnable to kNative and
// stays in the kRunnable state.
//
// There are risks to using a fast JNI call because it can delay
// a response to a thread suspension request which is typically
// used for a GC root scanning, etc. If a fast JNI call takes a
// long time, it could cause longer thread suspension latency
// and GC pauses.
//
// Thus, fast JNI should be used with care. It should be used
// for a JNI call that takes a short amount of time (eg. no
// long-running loop) and does not block (eg. no locks, I/O,
// etc.)
//
// A '!' prefix in the signature in the JNINativeMethod
// indicates that it's a fast JNI call and the runtime omits the // thread state transition from kRunnable to kNative at the // entry. if (*sig == '!') { is_fast = true; ++sig; } // Note: the right order is to try to find the method locally // first, either as a direct or a virtual method. Then move to // the parent. ArtMethod* m = nullptr; bool warn_on_going_to_parent = down_cast<JNIEnvExt*>(env)->GetVm()->IsCheckJniEnabled(); for (ObjPtr<mirror::Class> current_class = c.Get(); current_class != nullptr; current_class = current_class->GetSuperClass()) { // Search first only comparing methods which are native. m = FindMethod<true>(current_class, name, sig); if (m != nullptr) { break; } // Search again comparing to all methods, to find non-native methods that match. m = FindMethod<false>(current_class, name, sig); if (m != nullptr) { break; } if (warn_on_going_to_parent) { LOG(WARNING) << "CheckJNI: method to register \"" << name << "\" not in the given class. " << "This is slow, consider changing your RegisterNatives calls."; warn_on_going_to_parent = false; } } if (m == nullptr) { c->DumpClass(LOG_STREAM(ERROR), mirror::Class::kDumpClassFullDetail); LOG(ERROR) << "Failed to register native method " << c->PrettyDescriptor() << "." << name << sig << " in " << c->GetDexCache()->GetLocation()->ToModifiedUtf8(); ThrowNoSuchMethodError(soa, c.Get(), name, sig, "static or non-static"); return JNI_ERR; } else if (!m->IsNative()) { LOG(ERROR) << "Failed to register non-native method " << c->PrettyDescriptor() << "." << name << sig << " as native"; ThrowNoSuchMethodError(soa, c.Get(), name, sig, "native"); return JNI_ERR; } VLOG(jni) << "[Registering JNI native method " << m->PrettyMethod() << "]"; if (UNLIKELY(is_fast)) { // There are a few reasons to switch: // 1) We don't support !bang JNI anymore, it will turn to a hard error later.
// 2) @FastNative is actually faster. At least 1.5x faster than !bang JNI.
// and switching is super easy, remove ! in C code, add annotation in .java code.
// 3) Good chance of hitting DCHECK failures in ScopedFastNativeObjectAccess
// since that checks for presence of @FastNative and not for ! in the descriptor.
LOG(WARNING) << "!bang JNI is deprecated. Switch to @FastNative for " << m->PrettyMethod();
is_fast = false;
// TODO: make this a hard register error in the future.
}
const void* final_function_ptr = m->RegisterNative(fnPtr);
UNUSED(final_function_ptr);
}
return JNI_OK;
}
复制代码
首先获取Java类
// 参数name是带包名的类名,例如要获取android.widget.Toast类,那么写法就是
// jclass cls = env->FindClass("android/widget/Toast");
jclass FindClass(const char* name)
复制代码
// java中的Object对象传给native层后即为jobject,咱们可根据jobject获取其Java类
jclass GetObjectClass(jobject obj)
复制代码
再获取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)
复制代码
调用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, ...);
...
复制代码
首先在MainActivity
中定义以下变量和函数:
private int code = 10;
private String msg = "hello world";
public void cCallJava(String str) {
Log.i(TAG, "cCallJava: " + str);
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
public native void testCallJava(MainActivity activity);
复制代码
Native代码的实现:
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,"wsy" ,__VA_ARGS__)
// 第二个参数this被省略了,实际上,这个参数也是activity,可是在这里咱们手动传入演示
extern "C" JNIEXPORT void JNICALL Java_com_wsy_jnidemo_MainActivity_testCallJava( JNIEnv *env, jobject /* this */, jobject activity) {
// 首先获取MainActivity的类
jclass cls = env->GetObjectClass(activity);
// 获取这个类中的code成员变量ID
jfieldID codeId = env->GetFieldID(cls, "code", "I");
// 获取这个类中的msg成员变量ID
jfieldID msgId = env->GetFieldID(cls, "msg", "Ljava/lang/String;");
// 获取code成员变量的值
jint code = env->GetIntField(activity, codeId);
// 获取msg成员变量的值
jstring msg = (jstring) env->GetObjectField(activity, msgId);
// 获取java.lang.String对象中的内容
const char *cMsg = env->GetStringUTFChars(msg, JNI_FALSE);
// 打印日志
LOGI("code = %d,msg = %s", code, cMsg);
// 用完String后要释放
env->ReleaseStringUTFChars(msg, cMsg);
// 找到MainActivity类中的cCallJava函数
jmethodID callJavaMethodId = env->GetMethodID(cls, "cCallJava", "(Ljava/lang/String;)V");
// 建立一个java.lang.String对象,内容以下
jstring nativeMsg = env->NewStringUTF("java method cCallJava called");
// 调用java中的cCallJava方法
env->CallVoidMethod(activity, callJavaMethodId, nativeMsg);
// 这里的DeleteLocalRef能够不执行,在函数执行完毕后LocalRef会自动释放,
// 可是在循环次数较多的循环中须要Delete,不然可能会溢出
env->DeleteLocalRef(msg);
env->DeleteLocalRef(nativeMsg);
env->DeleteLocalRef(cls);
}
复制代码
运行效果:
以上大体就是Java
和Native
的交互介绍。