android中jni详细,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工程,勾选该项后的工程有以下不同:

d06616158264

工程目录

d06616158264

gradle配置

可以看到,多出了一个native-lib.cpp(C++代码)文件和一个CMakeLists.txt(C++相关配置)文件,app模块下的build.gradle文件也添加了相关配置。

在该默认工程中,我们可以看到一些JNI相关代码:

Java中加载本地库,并且定义native方法:

// 加载的library名称,注意:不是C++文件的名称

static {

System.loadLibrary("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 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 method String String.substring(int, int) is represented in bytecode as Ljava/lang/String.substring(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;)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函数或者变量时,一般以如下方式寻找:

寻找函数:需要知道java函数的函数名、返回值类型、参数类型

寻找变量:需要知道java变量的变量名、数据类型

2.如何获取签名

根据规则自己编写

在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命令获取

1.获取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

}

2.获取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调用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;

例如:

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)

七、C++中调用Java方法

1.首先获取Java类

根据包名+类名获取Java类

//参数name是带包名的类名,例如要获取android.widget.Toast类,那么写法就是

//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)

....

获取类中的静态变量

和获取成员变量相似,首先获取jfieldID

jfieldID 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 the exception (via the function's return value,ExceptionCheck, or ExceptionOccurred) and return, or clear the exception and handle it.

The only JNI functions that you are allowed to call while an exception is pending are:

DeleteGlobalRef

DeleteLocalRef

DeleteWeakGlobalRef

ExceptionCheck

ExceptionClear

ExceptionDescribe

ExceptionOccurred

MonitorExit

PopLocalFrame

PushLocalFrame

ReleaseArrayElements

ReleasePrimitiveArrayCritical

ReleaseStringChars

ReleaseStringCritical

ReleaseStringUTFChars

例如在抛出exception后执行

env->NewStringUTF("hello world, after exception")

将导致crash

十、根据crash日志中的指令地址定位C++中的crash代码位置

NDK中有一个很方便的debug工具:addr2line,可直接根据指令地址定位代码crash位置。

addr2line的位置在:{NDK安装目录}\toolchains\{平台相关目录}\prebuilt\windows-x86_64\bin目录。

我们先在native层写个错误示范代码,再运行,可以看到日志中出现了crash信息:

d06616158264

crash信息

找到工程相关的动态库文件,可以看到出现crash的so文件为/lib/x86/libnative-lib.so以及指令地址为00000E61,动态库文件在工程目录的位置如下图:

d06616158264

so文件路径

此时使用addr2line工具进行crash位置查找,用法为:

addr2line -e so文件路径 指令地址

addr2line -e D:\android-project\study\JNIDemo\app\build\intermediates\cmake\debug\obj\x86\libnative-lib.so 00000e61

运行结果如下

d06616158264

运行addr2line结果

可以看到定位到的导致crash的代码是native-lib.cpp文件的33行:

d06616158264

crashCode

demo可直接clone:

详细的JNI使用方法:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值