JNI技术简介

2018-03-23  本文已影响0人  仙花斗影

JNI(Java Native Interface)

提供一种Java字节码调用C/C++的解决方案,JNI描述的是一种技术。


NDK(Native Development Kit)

Android NDK 是一组允许您将 C 或 C++(“原生代码”)嵌入到 Android 应用中的工具,NDK描述的是工具集。 能够在 Android 应用中使用原生代码对于想执行以下一项或多项操作的开发者特别有用:

JNI方法注册

静态注册

当Java层调用navtie函数时,会在JNI库中根据函数名查找对应的JNI函数。如果没找到,会报错。如果找到了,则会在native函数与JNI函数之间建立关联关系,其实就是保存JNI函数的函数指针。下次再调用native函数,就可以直接使用这个函数指针。

  1. JNI函数名格式(需将”.”改为”_”):

Java_ + 包名(com.example.auto.jnitest)+ 类名(MainActivity) + 函数名(stringFromJNI)

  1. 静态方法的缺点:

动态注册

通过提供一个函数映射表,注册给JVM虚拟机,这样JVM就可以用函数映射表来调用相应的函数,就不必通过函数名来查找需要调用的函数。

  1. Java与JNI通过JNINativeMethod的结构来建立函数映射表,它在jni.h头文件中定义,其结构内容如下:
typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;
  1. 创建映射表后,调用RegisterNatives函数将映射表注册给JVM;
  2. 当Java层通过System.loadLibrary加载JNI库时,会在库中查JNI_OnLoad函数。可将JNI_OnLoad视为JNI库的入口函数,需要在这里完成所有函数映射和动态注册工作,及其他一些初始化工作。

数据类型转换

基础数据类型转换

引用数据类型转换

除了Class、String、Throwable和基本数据类型的数组外,其余所有Java对象的数据类型在JNI中都用jobject表示。Java中的String也是引用类型,但是由于使用频率较高,所以在JNI中单独创建了一个jstring类型。

    //获得一维数组的类引用,即jintArray类型  
    jclass intArrayClass = env->FindClass("[I");   
    //构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为length,类型为 jsize
    jobjectArray obejctIntArray  =  env->NewObjectArray(length ,intArrayClass , NULL);

JNI函数签名信息

由于Java支持函数重载,因此仅仅根据函数名是没法找到对应的JNI函数。为了解决这个问题,JNI将参数类型和返回值类型作为函数的签名信息。

  1. JNI规范定义的函数签名信息格式:
    (参数1类型字符…)返回值类型字符

  2. 函数签名例子:
  3. JNI常用的数据类型及对应字符:

JNIEnv介绍

  1. JNIEnv概念 :
    JNIEnv是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境。通过JNIEnv可以调用到一系列JNI系统函数。

  2. JNIEnv线程相关性:
    每个线程中都有一个 JNIEnv 指针。JNIEnv只在其所在线程有效, 它不能在线程之间进行传递。

注意:在C++创建的子线程中获取JNIEnv,要通过调用JavaVM的AttachCurrentThread函数获得。在子线程退出时,要调用JavaVM的DetachCurrentThread函数来释放对应的资源,否则会出错。

  1. JNIEnv 作用:
    • 访问Java成员变量和成员方法;
    • 调用Java构造方法创建Java对象等。

JNI编译

ndkBuild

使用ndk-build编译生成so文件

Cmake编译

CMake 则是一个跨平台的编译工具,它并不会直接编译出对象,而是根据自定义的语言规则(CMakeLists.txt)生成 对应 makefile 或 project 文件,然后再调用底层的编译, 在Android Studio 2.2 之后支持Cmake编译。

#将compress.c 编译成 libcompress.so 的共享库
add_library(compress SHARED compress.c)
#指定 compress 工程需要用到 libjpeg 库和 log 库
target_link_libraries(compress libjpeg ${log-lib})
find_library(libX  X11 /usr/lib)
find_library(log-lib log)  #路径为空,应该是查找系统环境变量路径

Android NDK 开发:CMake 使用

Abi架构

ABI(Application binary interface)应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的 ABI (应用程序二进制接口),一段程序只有遵循这个接口规范才能在该 CPU 上运行,所以同样的程序代码为了兼容多个不同的CPU,需要为不同的 ABI 构建不同的库文件。当然对于CPU来说,不同的架构并不意味着一定互不兼容。

根据以上的兼容总结,我们还可以得到一些规律:

问题排查 addr2line

03-21 23:59:32.032 6770-6770/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
03-21 23:59:32.032 6770-6770/? A/DEBUG: Build fingerprint: 'google/sdk_gphone_x86/generic_x86:8.1.0/OPM1.171004.001/4376136:user/release-keys'
03-21 23:59:32.032 6770-6770/? A/DEBUG: Revision: '0'
03-21 23:59:32.032 6770-6770/? A/DEBUG: ABI: 'x86'
03-21 23:59:32.032 6770-6770/? A/DEBUG: pid: 6745, tid: 6745, name: ucai.nativedemo  >>> com.choufucai.nativedemo <<<
03-21 23:59:32.032 6770-6770/? A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x70
03-21 23:59:32.032 6770-6770/? A/DEBUG: Cause: null pointer dereference
03-21 23:59:32.032 6770-6770/? A/DEBUG:     eax 00000070  ebx a8a6479c  ecx 00000035  edx 00000075
03-21 23:59:32.032 6770-6770/? A/DEBUG:     esi ffffffff  edi ffffffff
03-21 23:59:32.032 6770-6770/? A/DEBUG:     xcs 00000073  xds 0000007b  xes 0000007b  xfs 0000003b  xss 0000007b
03-21 23:59:32.032 6770-6770/? A/DEBUG:     eip a89a2553  ebp bffa2408  esp bffa1e78  flags 00010202
03-21 23:59:32.228 6770-6770/? A/DEBUG: backtrace:
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #00 pc 0001d553  /system/lib/libc.so (strlen+51)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #01 pc 0005fd5d  /system/lib/libc.so (__vfprintf+5581)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #02 pc 0008439e  /system/lib/libc.so (vsnprintf+222)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #03 pc 00022f30  /system/lib/libc.so (__vsnprintf_chk+48)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #04 pc 000068de  /system/lib/liblog.so (__android_log_print+78)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #05 pc 00000ee2  /data/app/com.choufucai.nativedemo-c_F0BwkNYJA0ITdueTXEdg==/lib/x86/libnative-lib.so
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #06 pc 00647e67  /system/lib/libart.so (art_quick_generic_jni_trampoline+71)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #07 pc 00641e62  /system/lib/libart.so (art_quick_invoke_stub+338)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #08 pc 00115fdf  /system/lib/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+223)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #09 pc 0032143f  /system/lib/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+335)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #10 pc 0031a6a4  /system/lib/libart.so 

以上错误日志中backtrace就是堆栈信息,#00 #01 就是堆栈列表。 #00 就是堆栈顶层即是错误所在地址,pc后面的就是地址,可以通过以下命令查找出地址可以获得对应的源码文件和行号:

// -f 输出函数名  
// -e 输出错误代码行数和文件路径  
// xxx.so 对应出错的so文件, 在android工程obj目录下  
// addr 是具体的地址  
arm-linux-androideabi-addr2line -f -e xxx.so addr 

独立工具链

上一篇下一篇

猜你喜欢

热点阅读