android中jni详细,Android中的JNI使用简介

2023-05-16

一、本文说明

本文不对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使用方法:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android中jni详细,Android中的JNI使用简介 的相关文章

  • Kapt 未在即时应用程序功能模块中生成类

    我在我的 Android 应用程序中使用 dagger2 即使没有错误 它也不会生成匕首组件类 我已经在设置中启用了注释处理器并重新启动了我的 android studio 但这对我来说不起作用 我也读过这个帖子Dagger2 不生成 Da
  • OPENGL ES 不工作:无当前上下文

    我尝试了 OpenGL ES2 for Android 一书中所示的程序 但它不起作用 我已经在Odroid E 三星s3 三星y 三星star上进行了测试 the gl version suported returns 2 but i g
  • 添加动态数据时 footable 出现问题

    我需要 jQuery Mobile 方面的一些帮助富表 http css tricks com footable a jquery plugin for responsive data tables 我正在表中动态添加数据 HTML tab
  • 编译后从字节代码中删除注释

    我们正在使用一个包含使用 JAXB 注释进行注释的 bean 的库 我们使用这些类的方式完全不依赖于 JAXB 换句话说 我们不需要 JAXB 也不依赖注释 但是 由于注释存在 它们最终会被处理注释的其他类引用 这要求我将 JAXB 捆绑到
  • 维护 HttpUrlConnection 调用之间的会话(Native/Webview)

    让我从我做的开始desire 我想制作一个应用程序part native and part webviews Problem 维护本机和 webview 部分之间的会话 My 处理方法 this 我打算实现一个本机登录 其中我向用户展示两个
  • ListView:防止视图回收

    我有一个使用回收视图的 ListView 我试图阻止视图被回收 所以我使用 setHasTransientState android support v4 view ViewCompatJB setHasTransientState Vie
  • 我的 Android 设备需要安装哪个驱动程序才能运行我的应用程序?

    我购买了 intex mobile 来在真实设备中测试我的 Android 应用程序 然而 该设备不存在于 OEM USB 驱动程序列表中 android 提供的设备列表中 我检查了 intex 官方网站 但不确定到底需要安装哪个驱动程序
  • 多语言 Android 应用程序:在电子邮件和密码字段中显示英文键盘

    我们正在开发一款多语言 Android 应用程序 针对英语和阿拉伯语 面临的问题是在登录和注册屏幕中 我们希望仅以英文文本输入用户名和密码字段 从而显示英文键盘 无论设备区域设置语言如何 已尝试在 edittext 中设置 inputtyp
  • Android 谷歌地图 V2 已停止

    我正在尝试构建地图应用程序并关注这个链接 https blog emildesign rhcloud com p 435一步步 我在这里找到了类似的主题 但对我没有帮助 我想显示地图 但是当我运行它时 它返回强制关闭和我的 Android
  • 无法在云控制台中启用 Maps SDK for Android

    我在云控制台中启用适用于 Android 的 Maps SDK 时遇到此问题 https console cloud google com https console cloud google com 它会抛出以下错误 附截图 我收到错误消
  • 更改 Android 中的媒体音量?

    我可以更改媒体音量吗 如何 到目前为止我用过这个 setVolumeControlStream AudioManager STREAM MUSIC 但有一个搜索栏并且想要更改媒体音量 而不是铃声音量 那么有人可以告诉我如何更改媒体音量onC
  • Android - 多次实例化一个片段?

    我正在创建一个在 ListView 中显示数据的应用程序 数据分为两种类型 热门 收藏夹 我有一个活动和两个片段 片段根据类别显示项目列表 我为此使用了 ListView 然后我有两个fragment layouts 它们在设计上完全相同
  • 如何更改 Android 12 启动屏幕中的图标形状?

    我想要矩形形状的启动屏幕图标 而不是 android 12 中的圆形形状 我不相信你可以 如果你看这里的第 3 点 https developer android com about versions 12 features splash
  • 无法登录 Google Play 游戏服务

    我在开发者控制台上使用包名称和正确的签名证书设置了我的游戏 并为其创建了排行榜 但没有创建任何成就 然后 我从以下位置下载了示例 Type A Number Challenge 和 BaseGameUtils https developer
  • BitmapFactory.decodeResource() 忽略 jpg 图像的 inPreferredConfig 选项

    我尝试将jpeg资源图像加载到ARGB 8888格式的位图 BitmapFactory Options opts new BitmapFactory Options opts inPreferredConfig Bitmap Config
  • 如何在 kotlin 中检查 lambda 空值

    在 Kotlin 中如何检查 lambda 是否为空 例如 我有这样的签名 onError Throwable gt Unit 我如何区分它的默认值是应用于主体还是应用于此函数的值 您无法测试 lambda 的主体是否为空 因此它不包含源代
  • 在片段之间切换时底部导航栏会向下推

    在我的活动中 我有一个底部导航栏和框架布局来显示片段 一切正常 但问题是当我开始按顺序从 1 4 移动时 底部导航栏保持在其位置 但当我突然从 4 跳到2 然后底部导航栏就会超出屏幕 当再次单击同一项目时 它就会回到正常位置 该视频将清楚地
  • 如何在 onDraw() 方法中定义与像素无关的高度

    我扩展了 View 来构建自定义小部件 我想用独立的像素单位定义小部件的高度 我认为可以通过将像素密度乘以所需的高度来完成 但我不知道该怎么做 到目前为止我所拥有的 最小化 public class Timeline extends Vie
  • 如何通过 AppCompatActivity 使用 YouTube Android 播放器 API

    为了在我的应用程序中播放视频 我决定扩展 YouTube Android Player API 但问题是我的菜单消失了 因为我没有从 AppCompatActivity 扩展 问题是 如何使用 YouTube Android Player
  • 使用 RecyclerView.Adapter 在 onBindViewHolder() 内设置 onItemClickListener

    我有一个自定义对象 学生班 public class Student private String name private String age public String getName return name public void

随机推荐