• Hi,我是小余。 本文已收录到GitHub Androider-Planet中。这儿有 Android 进阶成长常识体系,关注大众号 [小余的自习室] ,在成功的路上不走失!

1.ndk简介

ndk全称Native Developer Kits,Android NDK也是Android SDK的一个扩展集,用来扩展SDK的功用。 NDK打通了Java和C/C++之间的开发障碍,让Android开发者也能够运用C/C++言语开发APP

众所周知:

Java是在C/C++之上的言语,言语金字塔越往上对开发者就更加靠近,也便是更简略开发,可是功用相对也就越低。越往下对开发人员的要求也就越高,可是完结后的产品功用也越高,由于能够自己操控内存等模块的运用,而不是让Java虚拟机自行处理。

NDK的运用场景一般在:

  • 1.为了提高这些模块的功用,对图形,视频,音频等计算密集型应用,将复杂模块计算封装在.so或许.a文件中处理。
  • 2.运用的是C/C++进行编写的第三方库移植。如ffmppeg,OpenGl等。
  • 3.某些状况下为了提高数据安全性,也会封装so来完结。毕竟运用纯Java开发的app是有许多逆向东西能够破解的。

NDK开发并不必定适合一切程序员,光要会用Java又要懂C/C++这一点就已经淘汰许多了,更别说还得了解CPU架构相关常识。大部分Android开发只需求运用Java代码以及sdk供给的api就能够了,一同这也是中级和高档开发的分水岭。

不熟悉NDK开发,谁又敢说自己通晓Android呢?

2.目录

【NDK】聊聊NDK开发那些事
!](F:\联迪日子文档\笔记\code\15-ndk开发\NDK.png)

3.NDK架构分层

咱们知道运用NDK开发终究方针是为了将C/C++代码编译生成.so动态库或许.a静态库文件,并将库文件供给给Java代码调用。 所以按架构来分能够分为以下三层

  • 1.构建层
  • 2.Java层
  • 3.native层

3.1:构建层:

要得到方针的so文件,需求有个构建环境以及进程,将这个进程和环境称为构建层

构建层需求将C/C++代码编译为动态库so,那么这个编译的进程就需求一个构建东西,构建东西依照开发者指定的规则办法来构建库文件,相似apk的Gradle构建进程。

在解说NDK构建东西之前,咱们先来了解一些关于CPU架构的常识点:Android abi

Android abi

ABI即Application Binary Interface,界说了二进制接口交互规则,以适应不同的CPU,一个ABI对应一种类型的CPU

Android现在支撑以下7种ABI:

  • 1.armeabi:第5代和6代的ARM处理器,早期手机用的比较多。
  • 2.armeabi-v7a:第7代及以上的 ARM 处理器。
  • 3.arm64-v8a:第8代,64位ARM处理器
  • 4.x86:一般用在平板,模拟器。
  • 5.x86_64:64位平板。

大多数CPU都支撑多种ABI,可是为了取得最佳功用,最好运用CPU的首要ABI。如一同存在多个ABI(比方so文件),会装置最优ABI,其他的不会装置

【NDK】聊聊NDK开发那些事

留意:64位设备支撑运用32位的so库,可是以32位办法运转时,会丢掉64位一些功用特性(ART, WebView, Media, etc)

有了abi常识的衬托,下面咱们再来解说下关于NDK中构建东西的运用

构建东西

常规的NDK构建东西有两种:

  • 1.ndk-build
  • 2.Cmake

1.ndk-build:

ndk-build其实便是一个脚本。早期的NDK开发一直都是运用这种办法,

运转ndk-build相当于运转一下指令:

$GNUMAKE -f <ndk>/build/core/build-local.mk

$GNUMAKE 指向 GNU Make 3.81 或更高版别,<ndk> 则指向 NDK 装置目录

运用ndk-build需求配合两个mk文件:Android.mkApplication.mk

下面咱们来了解ndk-build构建模型

【NDK】聊聊NDK开发那些事

上图中画出了一个完好的so库的生成以及运用进程:能够大致分为三个进程:

  • 1.运用JNI编译带native的Java文件,生成对应的.h头文件。

  • 2.带上1中生成的头文件,C/C++文件以及其他三方库.a或许.so,一同编译并链接为so动态库。

  • 3.Java代码经过JNI调用到so库中的函数。

依据上面的so库的生成进程,咱们能够提取出以下元素:

C/C++的src文件,.h头文件链接库.a/.so编译的库称号name编译类型静态库/动态库abi类型(armeabi,armeabi-v7a..)

以上相关元素的装备都会在Android.mk和Application.mk中体现出来

Android.mk

Android.mk文件更像是一个传统的makefile文件,其界说源代码途径,头文件途径,链接器的途径来定位库,模块名,构建类型等。

语法:

  • LOCAL_PATH :=$(call my-dir)

    call my-dir表明的意思是调用构建体系供给的my-dir函数,获取当时文件地点的文件体系目录。每个Android.mk文件都有必要在开端时调用这个函数获取LOCAL_PATH。

  • include $(CLEAR_VARS)

    这儿表明铲除当时体系的各种变量,能够简略了解为进行一个初始化的操作。

  • LOCAL_SRC_FILES := xx.c xxx.c

    用来指定当时运用的C/C++源文件。留意这儿不需求增加头文件,编译体系会主动给咱们查找。

  • LOCAL_LDLIBS := -llog

    表明指定需求运用到的第三方库文件,静态或许动态都能够。运用 -l 前缀传递特定体系库的称号。例如,以上示例指示链接器生成在加载时链接到 /system/lib/liblog.so 的模块:

  • TARGET_PLATFORM := android-3

    指定当时需求编译的方针Android版别号

  • LOCAL_MODULE := xxname

    指定当时库的称号,生成的so文件会主动在name前面增加lib前缀,如指定的LOCAL_MODULE 为log,则获取到的so库文件的liblog。

  • include $(BUILD_SHARED_LIBRARY)

    指定当时库是静态库仍是动态库或许其他类型库。包括下面几种:

    • BUILD_STATIC_LIBRARY: 构建静态库
    • PREBUILT_STATIC_LIBRARY: 对已有的静态库进行包装,使其成为一个模块。
    • BUILD_SHARED_LIBRARY:构建动态库、
    • PREBUILT_SHARED_LIBRARY: 对已有的静态库进行包装,使其成为一个模块。
    • BUILD_EXECUTABLE: 构建可履行文件。

这儿列出了关于Android.mk的常用语法,其他语法能够参考官网。

Application.mk

其界说了Android app的相关特点。如:Android Sdk版别调试或许发布办法方针渠道ABI规范C/C++库

  • APP_ABI := XXX:指定方针渠道ABI,能够选填的有 x86 、X86_64 、armeabi-v8a、armeabi-v7a、all 等,如若挑选 all 则会构建构建出一切渠道的 so,假如不填写该项,那么将默许构建为 armeabi 渠道下的库。

    您也能够指定多个值,办法是将它们放在同一行上,中间用空格分隔。

    APP_ABI := armeabi-v7a arm64-v8a x86
    
  • APP_STL := gnustl_static:NDK 构建体系供给了由 Android 体系给出的最小 C++ 运转时库 (system/lib/libstdc++.so)的 C++ 头文件。

  • APP_CPPFLAGS :=-std=gnu++11 -fexceptions,:指定编译进程的 flag ,能够在该选项中开启 exception rtti 等特性,可是为了功率考虑,最好封闭 rtti。

  • APP_PLATFORM :=android-21:指定创立的动态库的渠道

    其他字段能够参考官网

关于ndk-build就讲到这儿了,毕竟这种办法以及很少用了。下面咱们来解说别的一种编译办法。

2.Cmake

Cmake简介

在AS2.2之后,东西中增加了Cmake的支撑。

cmake和unix make东西不相同,其并不是一个编译体系,而是一个编译体系的生成器,简略了解便是,他是用来生成makefile文件的,而前面解说的Android.mk其实便是一个makefile类文件,cmake运用一个CmakeLists.txt的装备文件来生成对应的makefile文件。

运用Cmake构建进程如下图:

【NDK】聊聊NDK开发那些事

能够看到Cmake构建so的进程其实包括两步:

进程1:运用Cmake生成编译的makefiles文件

进程2:运用Make东西对进程1中的makefiles文件进行编译为库或许可履行文件。

那运用Cmake优势在哪里呢?信任了解Gradle构建的都知道,为什么现在的apk构建进程会这么快,便是由于其在编译apk之前会生成一个使命依靠树,因而在多核状态下,使命能够在异步状态下履行,所以apk构建进程会非常快。而咱们的Cmake也是相似,其在生成makefile进程中会主动剖析源代码,创立一个组件之间依靠的关系树,这样就能够大大减缩在make编译阶段的时刻。

这儿再发一张编译体系的流程图:

【NDK】聊聊NDK开发那些事

从图中咱们也能够看到可履行文件在被生成之前,是有许多依靠使命的,这些使命运用Cmake创立一张使命依靠树,能够大大降低编译时刻。这也是为什么谷歌在运用了Cmake编译之后,对ndk-build的支撑就大大降低了。

所以在2.2今后有两种办法来编译C/C++代码、谷歌为了兼容一些旧项目,扔保留了ndk-build的办法。

假如非有必要,不引荐运用ndk-build来构建,由于这样构建源码后,是无法运用办法跳转、办法提示等功用的!假如要改代码,就等于文本编辑器写代码。相反 CMake 是支撑这些的,因而更有助于提高开发功率,且运用CMake最大长处便是能够动态调试C/C++代码,和VS中调试相同。是不是很cool、。

解说了这么多,下面咱们要点来看Cmake的语法:

Cmake根本语法:

Cmake供给了许多对编译进行装备的语法,这儿只提取平时用的比较频频的几个:

cmake_minimum_required:要求的Cmake最低版别

cmake_minimum_required(VERSION 3.10.2)

file( […]):文件操作,参数opr:表明文件类型如读文件仍是写文件或许文件是Sting类型仍是二进制类型等

Reading
  file(READ <filename> <out-var> [...])
  file(STRINGS <filename> <out-var> [...])
  file(<HASH> <filename> <out-var>)
  file(TIMESTAMP <filename> <out-var> [...])
  file(GET_RUNTIME_DEPENDENCIES [...])
Writing
  file({WRITE | APPEND} <filename> <content>...)
  file({TOUCH | TOUCH_NOCREATE} [<file>...])
  file(GENERATE OUTPUT <output-file> [...])
  file(CONFIGURE OUTPUT <output-file> CONTENT <content> [...])
...

find_file:查找文件

find_file(myfile
        ${CMAKE_CURRENT_SOURCE_DIR}"/include/SerialPort.h)

add_library:增加项目so库,便是你需求创立的方针库声明处

add_library( # Sets the name of the library.
        dy-register-lib
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).//这儿能够运用多个文件
        dy_register.cpp people/people.cpp)

find_library:查找第三方库,这个在某个库需求相关其他库进行衔接的时分就需求查找到库

find_library( # Sets the name of the path variable.
              log-lib
              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

target_link_libraries:将第三方库链接到方针库中,运用find_library找到第三方库之后,运用target_link_libraries某个库就能够在链接时找到第三方库。

target_link_libraries( # Specifies the target library.
        dy-register-lib
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib} )

link_libraries:将第三方库链接到之后声明的一切方针库中,和target_link_libraries不同之处,这儿不需求声明相关的库称号,而是之后声明的一切库文件

link_libraries(${log-lib} //方针库称号)

target_include_directories:方针库相关的include头文件夹。

target_include_directories(log-lib //方针库称号
        PUBLIC //库类型
        ${CMAKE_CURRENT_SOURCE_DIR}/include //方针include文件夹途径)

include_directories:一切方针库相关的include头文件夹。能够增加多个途径

include_directories(
        ${CMAKE_CURRENT_SOURCE_DIR}
        ${CMAKE_CURRENT_BINARY_DIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/include)

set:设置变量,如运用某个变量存储途径等。

set(SRC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include/SerialPort.h)

aux_source_directory:查找在一个文件夹下一切文件,并写入一个List变量下面,之后对这个文件夹下的src文件的一切操作都能够运用这个变量来处理,就不需求一个个文件导入了,便利和安全许多。

aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src _SRC_FILES)
add_library( # Sets the name of the library.
        dy-register-lib
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).//这儿能够运用多个文件
        ${_SRC_FILES})

messages:打印日志,如需求打印某个变量的值,能够运用这个办法:

General messages
  message([<mode>] "message text" ...)
  mode:FATAL_ERROR SEND_ERROR WARNING,DEBUG等值。用法和logcat相似
Reporting checks
  message(<checkState> "message text" ...)
  checkState:CHECK_START  CHECK_PASS  CHECK_FAIL等值
Configure Log
  message(CONFIGURE_LOG <text>...)

好了,关于Cmake语法就讲到这儿,需求看更多指令的能够参考官方文档

Cmake构建项目装备

运用Cmake进行构建需求在build.gradle装备文件中声明externalNativeBuild

android {
    defaultConfig {
        externalNativeBuild {
            // For ndk-build, instead use the ndkBuild block.
            cmake {
            	//声明当时Cmake项目运用的Android abi
            	abiFilters "armeabi-v7a"
               	//供给给Cmake的参数信息 可选
                arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
     			//供给给C编译器的一个标志 可选
                cFlags "-D__STDC_FORMAT_MACROS"
               //供给给C++编译器的一个标志 可选
                cppFlags "-fexceptions", "-frtti","-std=c++11"
               	//指定哪个so库会被打包到apk中去 可选
                targets "libexample-one", "my-executible-demo"
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt" //声明cmake装备文件途径
            version "3.10.2" //声明cmake版别
        }
    }
}

3.2:Java层

怎么挑选正确的so?

通常状况下,咱们在编译so的时分就需求确定自己设备类型,依据设备类型挑选对应abiFilters

由于不同CPU指令的向前兼容性,假定咱们只要arm7代处理器,那么只需求挑选armeabi-v7a即可,假如既有7代也有7代之前的,能够一同挑选armeabi和armeabi-v7a,设备会主动挑选运用正确版别,同理关于32位仍是64位处理器也是相同的道理。模拟器一般运用x86的,所以假如该so也需求运转在模拟器上需求加上x86的abi。

留意:运用as编译后的so会主动打包到apk中,假如需求供给给第三方运用,能够到build/intermediates/cmake/debug or release 目录中copy出来。

第三方库一般直接放在main/jniLibs文件夹下,也有放在默许libs目录下的,可是有必要在build.gradle中声明jni库的目录:

sourceSets {
    main {
        jniLibs.srcDirs = ['jniLibs']
    }
}

Java层怎么调用so文件中的函数?

关于Android上层代码来说,在将包正确导入到项目中后,只需求一行代码就能够完结动态库的加载进程。

System.load("/data/local/tmp/libnative_lib.so");
System.loadLibrary("native_lib");

以上两个办法用于加载动态,差异如下:

  • 1.加载途径不同:load是加载so的完好途径,而loadLibrary是加载so的称号,然后加上前缀lib和后缀.so去默许目录下查找。

  • 2.主动加载库的依靠库的不同:load不会主动加载依靠库;而loadLibrary会主动加载依靠库。

动态库加载进程调用栈如下:

System.loadLibrary()
  Runtime.loadLibrary()
    Runtime.doLoad()
      Runtime_nativeLoad()
          LoadNativeLibrary()
              dlopen()
              dlsym()
              JNI_OnLoad()

loadLibrary()和load()都用于加载动态库,loadLibrary()能够便利主动加载依靠库,load()能够便利地指定具体途径的动态库。关于loadLibrary()会将将xxx动态库的名字转换为libxxx.so,再从/data/app/[packagename]-1/lib/arm64,/vendor/lib64,/system/lib64等途径中查询对应的动态库。不管哪种办法,终究都会调用到LoadNativeLibrary()办法,该办法首要操作:

  • 1.经过dlopen翻开动态库文件

  • 2.经过dlsym找到JNI_OnLoad符号所对应的办法地址

  • 3.经过JNI_OnLoad去注册对应的jni办法

3.3:Native层

说到native层就要讲到JNI了。

什么是JNI

JNI(全名Java Native Interface)Java native接口,其能够让一个运转在Java虚拟机中的Java代码被调用或许调用native层的用C/C++编写的依据本机硬件和操作体系的程序。简略了解为便是一个衔接Java层和Native层的桥梁。

开发者能够在native层经过JNI调用到Java层的代码,也能够在Java层声明native办法的调用入口。

JNI注册办法

JNI有静态注册和动态注册两种注册办法:

静态注册

进程1.在Java中声明native办法

package com.android.myapplication;
...
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }
    private native String stringFromJNI();
    static {
        System.loadLibrary("native-lib");
    }
}

进程2.在native层新建一个C/C++文件,并创立对应的办法,

#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_android_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

留意C/C++文件声明的办法名规则为:Java+包名+办法名,千万别搞错了,或许会报找不到对应函数的错。

主张运用AS快捷键主动生成函数名。

【NDK】聊聊NDK开发那些事

动态注册

动态注册其实便是运用到了前面剖析的so加载原理:在最终一步的JNI_OnLoad中注册对应的jni办法

这样在类加载的进程中就能够主动注册native函数。

#include <jni.h>
#include <string>
#include <assert.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_android_myapplication_MainActivity_stringFromJNI1(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
#define JNI_CLASS_NAME "com/android/myapplication/MainActivity" //java途径
static JNINativeMethod gMethods[] = {
        {"stringFromJNI","()Ljava/lang/String;",(void *)Java_com_android_myapplication_MainActivity_stringFromJNI1},
};
int register_dynamic_Methods(JNIEnv *env){
    std::string s = JNI_CLASS_NAME;
    const char* className = s.c_str();
    jclass clazz = env->FindClass(className);
    if(clazz == NULL){
        return JNI_FALSE;
    }
    //注册JNI办法
    if(env->RegisterNatives(clazz,gMethods,sizeof(gMethods)/sizeof(gMethods[0]))<0){
        return JNI_FALSE;
    }
    return JNI_TRUE;
}
//类加载时会调用到这儿
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    if(vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK){
        return JNI_ERR;
    }
    assert(env != NULL);
    if(!register_dynamic_Methods(env)){
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

中心办法:RegisterNatives,jni注册native办法。

动态注册和静态注册终究都能够将native办法注册到虚拟机中,引荐运用动态注册,更不简略写错,静态注册每次增加一个新的办法都需求检查原函数类的包名。

JNI根底语法

1.Java类型以及数据结构
  • 1.根本数据类型

    Java的数据类型能够直接与C/C++的根本类型映射,因而Java的根本类型对开发人员是透明的。

    Java类型 Native类型 描绘
    boolean jboolean unsigned 8 bits
    byte jbyte signed 8 bits
    char jchar unsigned 16 bits
    short jshort signed 16 bits
    int jint signed 32 bits
    long jlong signed 64 bits
    float jfloat 32 bits
    double jdouble 64 bits
    void void

    为了便利运用下面宏界说来表明true和false

    #define JNI_FALSE  0
    #define JNI_TRUE   1
    
  • 2.引证类型

    JNI中供给了一系列的引证类型,这些引证类型和Java中的类型是一一对应的。

    • jobject

      • jclass (java.lang.Class objects)
      • jstring (java.lang.String objects)
      • jarray(arrays)
        • jobjectArray (object arrays)
        • jbooleanArray (boolean arrays)
        • jbyteArray (byte arrays)
        • jcharArray (char arrays)
        • jshortArray (short arrays)
        • jintArray (int arrays)
        • jlongArray (long arrays)
        • jfloatArray (float arrays)
        • jdoubleArray (double arrays)
      • jthrowable (java.lang.Throwable objects)

    留意在C中JNI引证类型是以别号的办法界说的:例如

    typedef jobject jclass;
    

    而在C++中JNI引证一般都是以方针指针的办法界说:如下:

    class _jobject {};
    class _jclass : public _jobject {};
    // ...
    typedef _jobject *jobject;
    typedef _jclass *jclass;
    
  • 3.特点(Field)和办法(Method)的ID

    特点和办法的ID其实是一个C结构体类型的指针:

    struct _jfieldID;              /* opaque structure */
    typedef struct _jfieldID *jfieldID;   /* field IDs */
    struct _jmethodID;              /* opaque structure */
    typedef struct _jmethodID *jmethodID; /* method IDs */
    

    _jfieldID:表明Java层的一个类的特点类型,是一个结构体,而jfieldID是结构体的一个指针类型。native层能够运用jni对这个特点进行赋值操作。

    _jmethodID:表明Java层的某个类的办法类型,也是一个结构体,而jmethodID是结构体的一个指针类型。

  • 4.签名(Signatures)

    JNI运用的是Java虚拟机的签名描绘办法:

    Type Signature Java Type
    Z boolean
    B byte
    C char
    S short
    I int
    J long
    F float
    D double
    L fully-qualified-class ; 类的权约束描绘符:如String -> Ljava.lang.String
    [ type type[] :特点描绘
    ( arg-types ) ret-type method type:办法描绘

    例如:Java代码:

    long f (int n, String s, int[] arr);
    

    那么该办法的签名如下:

    (I;Ljava/lang/String;[I)J
    
2..JavaVM 和 JNIEnv
  • 界说

    • JavaVm

      虚拟机在JNI层的代表,一个进程只要一个JavaVM,一切的线程共用一个JavaVM。

      • JNIEnv

        JNIEnv代表Java调用native层的环境,一个封装了简直一切的JNI办法的指针。

        只在创立它的线程有效,不能跨线程传递,不同的线程的JNIEnv彼此独立。

        native 环境中创立的线程,假如需求拜访JNI,有必要调用AttachCurrentThread 进行相关,然后运用DetachCurrentThread 解除相关。

        JavaVM *jvm; /* already set */
         f()
         {
             JNIEnv *env;
             (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
             ... /* use env */
         }
        

值得留意的是:JNIENV在C言语和C++中调用办法是有差异的:

C风格:(*env)->NewStringUTF(env, “Hellow World!”);
C++风格:env->NewStringUTF(“Hellow World!”);

注:C++风格其实便是对C风格的再次封装,下次碰到这个问题就不要想不通啦、

3.JNI相关函数

前面咱们说过JNI办法一般都是运用JNIEnv去调用,而JNIEnv又是一个指针,所以JNI中有哪些函数,只需求找到JNIEnv的完结体就能够了。找到JNIEnv的完结:

在jni.h中:

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

能够看到假如是C文件中,则JNIEnv是JNINativeInterface结构体的一个指针

在C++文件中是对JNIEnv起的一个别号,定位到_JNIEnv是在哪里界说的。

struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;
#if defined(__cplusplus)
    jint GetVersion()
    { return functions->GetVersion(this); }
    jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
        jsize bufLen)
    { return functions->DefineClass(this, name, loader, buf, bufLen); }
    jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }
    ...
    ...

能够看到_JNIEnv中界说了一个JNINativeInterface指针functions,然后对JNIEnv的一切操作都是运用这个functions指针,相当于_JNIEnv仅仅一个代理。而在C言语中直接运用的是JNINativeInterface指针,这也是为什么JNIEnv在C和C++调用办法不共同的原因

_JNIEnv办法整理如下:

struct JNINativeInterface {
    /*获取当时JNI版别信息:*/
    jint        (*GetVersion)(JNIEnv *);
	/*
	界说一个类:类是从某个字节数组吧buf中读取出来的
	原型:jclass DefineClass(JNIEnv *env, const char *name, jobject loader,
const jbyte *buf, jsize bufLen);
	*/
    jclass      (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
   	                     jsize);
    /*
    找到某个类:
    函数原型:
    jclass FindClass(JNIEnv *env, const char *name);
    参数name:为类的全约束名
    如String类:"java/lang/String"
    如java.lang.Object[] : "[Ljava/lang/Object;"
    */
    jclass      (*FindClass)(JNIEnv*, const char*);
    /*
    获取当时类的父类:
    通常在运用FindClass获取到类之后,再调用这个函数
    */
    jclass      (*GetSuperclass)(JNIEnv*, jclass);
    /*
    函数原型:
    jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1,jclass clazz2);
	界说某个类clazz1是否能够安全的强制转换为别的一个类clazz2
    */
    jboolean    (*IsAssignableFrom)(JNIEnv*, jclass, jclass);
	/*检测是否发生了反常*/
    jboolean    (*ExceptionCheck)(JNIEnv*);
    /*检测是否发生了反常,并回来反常*/
    jthrowable  (*ExceptionOccurred)(JNIEnv*);
    /*打印出反常描绘栈*/
    void        (*ExceptionDescribe)(JNIEnv*);
    /*铲除反常*/
    void        (*ExceptionClear)(JNIEnv*);
    /* 抛出一个反常 成功回来0,失败回来其他值*/
    jint        (*Throw)(JNIEnv*, jthrowable);
    /* 创立一个新的Exception,并制定message,然后抛出*/
    jint        (*ThrowNew)(JNIEnv *, jclass, const char *);
   	/*抛出一个FatalError*/
    void        (*FatalError)(JNIEnv*, const char*);
	/*创立一个大局的引证,需求在不运用的时分调用DeleteGlobalRef解除大局引证*/
    jobject     (*NewGlobalRef)(JNIEnv*, jobject);
    /*删去大局引证*/
    void        (*DeleteGlobalRef)(JNIEnv*, jobject);
    /*删去部分引证*/
    void        (*DeleteLocalRef)(JNIEnv*, jobject);
    /*是否是同一个Object*/
    jboolean    (*IsSameObject)(JNIEnv*, jobject, jobject);
	/*创立一个部分引证*/
    jobject     (*NewLocalRef)(JNIEnv*, jobject);
	/*在不调用结构函数的状况下,给jclass创立一个Java方针,留意该办法不能用在数组的状况*/
    jobject     (*AllocObject)(JNIEnv*, jclass);
    /*创立一个Object,关于jmethodID参数有必要运用GetMethodID获取到结构函数(with <init> as the method name and void (V) as the return type)*/
    jobject     (*NewObject)(JNIEnv*, jclass, jmethodID, ...);
    jobject     (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list);
    jobject     (*NewObjectA)(JNIEnv*, jclass, jmethodID, const jvalue*);
    /*获取到当时方针的class类型*/
    jclass      (*GetObjectClass)(JNIEnv*, jobject);
    /*某个方针是否是某个类的完结方针,和Java中instanceof相似*/
    jboolean    (*IsInstanceOf)(JNIEnv*, jobject, jclass);
    /*获取某个类的办法类型id,非静态办法
    原型:jfieldID GetFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
    clazz:类权约束名
    name:为办法名
    sig:为办法签名描绘
    */
    jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    /*调用某个方针的办法
    jobject:方针
    jmethodID:方针的办法
    回来值:jobject
    */
    jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
    jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
     /*调用某个方针的办法
    jobject:方针
    jmethodID:方针的办法
    回来值:jboolean
    同理后边的CallByteMethod,CallCharMethodV,CallIntMethod仅仅回来值不相同算了。
    */
    jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
    jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
    jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
    jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
    jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
    jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
    jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
    jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
    jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
    jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
    jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
    jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...);
    jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
    jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...);
    jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
    void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
    void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
    void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
	/*	
	回来一个类的非静态特点id
	原型:jfieldID GetFieldID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
	参数name:特点的名字
	sig:特点的签名
	*/
    jfieldID    (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
    /*
    获取当时类的某个特点值    
    同理:关于后边的GetShortField,GetBooleanField,GetByteField等仅仅特点的类型不相同。
	在运用GetFieldID得到jfieldID特点id后,就能够运用Get<type>Field获取特点值。
    */
    jobject     (*GetObjectField)(JNIEnv*, jobject, jfieldID);
    jboolean    (*GetBooleanField)(JNIEnv*, jobject, jfieldID);
    jbyte       (*GetByteField)(JNIEnv*, jobject, jfieldID);
    jchar       (*GetCharField)(JNIEnv*, jobject, jfieldID);
    jshort      (*GetShortField)(JNIEnv*, jobject, jfieldID);
    jint        (*GetIntField)(JNIEnv*, jobject, jfieldID);
    jlong       (*GetLongField)(JNIEnv*, jobject, jfieldID);
    jfloat      (*GetFloatField)(JNIEnv*, jobject, jfieldID);
    jdouble     (*GetDoubleField)(JNIEnv*, jobject, jfieldID);
    /*
    设置当时类的某个特点值    
    同理:关于后边的BooleanField,SetByteField,SetShortField等仅仅特点的类型不相同。
	在运用GetFieldID得到jfieldID特点id后,就能够运用Set<type>Field设置对应特点值。
    */
    void        (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject);
    void        (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean);
    void        (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte);
    void        (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar);
    void        (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort);
    void        (*SetIntField)(JNIEnv*, jobject, jfieldID, jint);
    void        (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong);
    void        (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat);
    void        (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble);
    /*
    获取某个类的静态办法id
    */
    jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);
    /*
    调用某个类的静态办法
    同理:后边的CallStaticBooleanMethod,CallStaticByteMethod等办法仅仅回来类型不相同算了。
    */
    jobject     (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...);
    jobject     (*CallStaticObjectMethodV)(JNIEnv*, jclass, jmethodID, va_list);
    jobject     (*CallStaticObjectMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
    jboolean    (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...);
    jboolean    (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,
                        va_list);
    jboolean    (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
    jbyte       (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);
    jbyte       (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);
    jbyte       (*CallStaticByteMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
    jchar       (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...);
    jchar       (*CallStaticCharMethodV)(JNIEnv*, jclass, jmethodID, va_list);
    jchar       (*CallStaticCharMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
    jshort      (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...);
    jshort      (*CallStaticShortMethodV)(JNIEnv*, jclass, jmethodID, va_list);
    jshort      (*CallStaticShortMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
    jint        (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...);
    jint        (*CallStaticIntMethodV)(JNIEnv*, jclass, jmethodID, va_list);
    jint        (*CallStaticIntMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
    jlong       (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...);
    jlong       (*CallStaticLongMethodV)(JNIEnv*, jclass, jmethodID, va_list);
    jlong       (*CallStaticLongMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
    jfloat      (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...);
    jfloat      (*CallStaticFloatMethodV)(JNIEnv*, jclass, jmethodID, va_list);
    jfloat      (*CallStaticFloatMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
    jdouble     (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...);
    jdouble     (*CallStaticDoubleMethodV)(JNIEnv*, jclass, jmethodID, va_list);
    jdouble     (*CallStaticDoubleMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
    void        (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
    void        (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list);
    void        (*CallStaticVoidMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
    //获取静态特点的id
    jfieldID    (*GetStaticFieldID)(JNIEnv*, jclass, const char*,
                        const char*);
	/*
	获取某个类的静态特点的值:
	同理:GetStaticBooleanField,GetStaticByteField等后续函数都仅仅特点的类型不相同算了
	*/
    jobject     (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);
    jboolean    (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID);
    jbyte       (*GetStaticByteField)(JNIEnv*, jclass, jfieldID);
    jchar       (*GetStaticCharField)(JNIEnv*, jclass, jfieldID);
    jshort      (*GetStaticShortField)(JNIEnv*, jclass, jfieldID);
    jint        (*GetStaticIntField)(JNIEnv*, jclass, jfieldID);
    jlong       (*GetStaticLongField)(JNIEnv*, jclass, jfieldID);
    jfloat      (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID);
    jdouble     (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID);
    /*
    设置某个类的静态特点的值
    同理:SetStaticObjectField,SetStaticBooleanField仅仅设置的值特点类型不同算了*/
    void        (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject);
    void        (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean);
    void        (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte);
    void        (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar);
    void        (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort);
    void        (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint);
    void        (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong);
    void        (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat);
    void        (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble);
    /*
    从一段unicode字符串中创立一个String方针
    原型:jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len);
	*/
    jstring     (*NewString)(JNIEnv*, const jchar*, jsize);
    /*获取String方针的字符串长度,字符串是默许的UNICODE*/
    jsize       (*GetStringLength)(JNIEnv*, jstring);
    /*
    将jstring转换为一个Unicode字符串数组的指针,在调用ReleaseStringChars之前,这个指针都是有效的
    原型:const jchar * GetStringChars(JNIEnv *env, jstring string,jboolean *isCopy);
    */
    const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
    /*释放一个Unicode字符串数组的指针*/
    void        (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
    /*创立一个string方针,运用的字符串是UTF-8类型*/
    jstring     (*NewStringUTF)(JNIEnv*, const char*);
    /*获取UTF-8类型的jstring方针的长度*/
    jsize       (*GetStringUTFLength)(JNIEnv*, jstring);
    /* JNI spec says this returns const jbyte*, but that's inconsistent */
	/*
	回来一个string类型的utf-8类型字符串的指针。生命周期是在调用ReleaseStringUTFChars之前。
	原型:const char * GetStringUTFChars(JNIEnv *env, jstring string,jboolean *isCopy);*/
    const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
    /*释放GetStringUTFChars获取到的指针*/
    void        (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*);
    /*获取一个数组方针的长度*/
    jsize       (*GetArrayLength)(JNIEnv*, jarray);
    /*创立一个Object类型的数组方针
    原型:jobjectArray NewObjectArray(JNIEnv *env, jsize length,jclass elementClass, jobject initialElement);
    elementClass:方针类型
    initialElement:方针初始化元素*/
    jobjectArray (*NewObjectArray)(JNIEnv*, jsize, jclass, jobject);
    /*获取某个数组方针索引上的元素,最终一个参数为索引方位*/
    jobject     (*GetObjectArrayElement)(JNIEnv*, jobjectArray, jsize);
    /*设置某个数组方针索引上的元素,倒数第二个参数为索引方位*/
    void        (*SetObjectArrayElement)(JNIEnv*, jobjectArray, jsize, jobject);
	/*创立一个Boolean类型的数组方针,长度为jsize*/
    jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);
	/*创立一个Byte类型的数组方针,长度为jsize*/
    jbyteArray    (*NewByteArray)(JNIEnv*, jsize);
    jcharArray    (*NewCharArray)(JNIEnv*, jsize);
    jshortArray   (*NewShortArray)(JNIEnv*, jsize);
    jintArray     (*NewIntArray)(JNIEnv*, jsize);
    jlongArray    (*NewLongArray)(JNIEnv*, jsize);
    jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);
    jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);
    /*获取Boolean数组方针的第一个方针的地址指针:留意和ReleaseBooleanArrayElements配合运用
    函数原型:NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env,ArrayType array, 		jboolean *isCopy);
    isCopy:当时回来的数组方针或许是Java数组的一个拷贝方针
    */
    jboolean*   (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);
    /*获取Byte数组方针的第一个方针的地址指针*/
    jbyte*      (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);
    /*同上*/
    jchar*      (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);
    jshort*     (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);
    jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
    jlong*      (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);
    jfloat*     (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);
    jdouble*    (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);
    //是否数组方针内存
    void        (*ReleaseBooleanArrayElements)(JNIEnv*, jbooleanArray,
                        jboolean*, jint);
    void        (*ReleaseByteArrayElements)(JNIEnv*, jbyteArray,
                        jbyte*, jint);
    void        (*ReleaseCharArrayElements)(JNIEnv*, jcharArray,
                        jchar*, jint);
    void        (*ReleaseShortArrayElements)(JNIEnv*, jshortArray,
                        jshort*, jint);
    void        (*ReleaseIntArrayElements)(JNIEnv*, jintArray,
                        jint*, jint);
    void        (*ReleaseLongArrayElements)(JNIEnv*, jlongArray,
                        jlong*, jint);
    void        (*ReleaseFloatArrayElements)(JNIEnv*, jfloatArray,
                        jfloat*, jint);
    void        (*ReleaseDoubleArrayElements)(JNIEnv*, jdoubleArray,
                        jdouble*, jint);
	/*将一个数组区间的值拷贝到一个新的地址空间,然后回来这个地址空间的首地址,最终一个参数为接收首地址用
	函数原型:
	void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,jsize start, jsize 		len, NativeType *buf);
	*/
    void        (*GetBooleanArrayRegion)(JNIEnv*, jbooleanArray,
                        jsize, jsize, jboolean*);
    void        (*GetByteArrayRegion)(JNIEnv*, jbyteArray,
                        jsize, jsize, jbyte*);
    void        (*GetCharArrayRegion)(JNIEnv*, jcharArray,
                        jsize, jsize, jchar*);
    void        (*GetShortArrayRegion)(JNIEnv*, jshortArray,
                        jsize, jsize, jshort*);
    void        (*GetIntArrayRegion)(JNIEnv*, jintArray,
                        jsize, jsize, jint*);
    void        (*GetLongArrayRegion)(JNIEnv*, jlongArray,
                        jsize, jsize, jlong*);
    void        (*GetFloatArrayRegion)(JNIEnv*, jfloatArray,
                        jsize, jsize, jfloat*);
    void        (*GetDoubleArrayRegion)(JNIEnv*, jdoubleArray,
                        jsize, jsize, jdouble*);
    /* spec shows these without const; some jni.h do, some don't */
    /*设置某个数组方针的区间的值*/
    void        (*SetBooleanArrayRegion)(JNIEnv*, jbooleanArray,
                        jsize, jsize, const jboolean*);
    void        (*SetByteArrayRegion)(JNIEnv*, jbyteArray,
                        jsize, jsize, const jbyte*);
    void        (*SetCharArrayRegion)(JNIEnv*, jcharArray,
                        jsize, jsize, const jchar*);
    void        (*SetShortArrayRegion)(JNIEnv*, jshortArray,
                        jsize, jsize, const jshort*);
    void        (*SetIntArrayRegion)(JNIEnv*, jintArray,
                        jsize, jsize, const jint*);
    void        (*SetLongArrayRegion)(JNIEnv*, jlongArray,
                        jsize, jsize, const jlong*);
    void        (*SetFloatArrayRegion)(JNIEnv*, jfloatArray,
                        jsize, jsize, const jfloat*);
    void        (*SetDoubleArrayRegion)(JNIEnv*, jdoubleArray,
                        jsize, jsize, const jdouble*);
	/*注册JNI函数*/
    jint        (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,
                        jint);
    /*反注册JNI函数*/
    jint        (*UnregisterNatives)(JNIEnv*, jclass);
    /*加同步锁*/
    jint        (*MonitorEnter)(JNIEnv*, jobject);
    /*释放同步锁*/
    jint        (*MonitorExit)(JNIEnv*, jobject);
    /*获取Java虚拟机VM*/
    jint        (*GetJavaVM)(JNIEnv*, JavaVM**);
    /*获取uni-code字符串区间的值,并放入到最终一个参数首地址中*/
    void        (*GetStringRegion)(JNIEnv*, jstring, jsize, jsize, jchar*);
     /*获取utf-8字符串区间的值,并放入到最终一个参数首地址中*/
    void        (*GetStringUTFRegion)(JNIEnv*, jstring, jsize, jsize, char*);
	/*
	1.相似Get/Release<primitivetype>ArrayElements这两个对应函数,都是获取一个数组方针的地址,可是回来是void*,所以是范式编程,能够回来任何方针的首地址,而Get/Release<primitivetype>ArrayElements是指定类型的格局。
	2.在调用GetPrimitiveArrayCcritical之后,本机代码在调用ReleasePrimitiveArray Critical之前不该长时刻运转。咱们有必要将这对函数中的代码视为在“关键区域”中运转。在关键区域中,本机代码不得调用其他JNI函数,或任何或许导致当时线程堵塞并等待另一个Java线程的体系调用。(例如,当时线程不能对另一个Java线程正在编写的流调用read。)*/
    void*       (*GetPrimitiveArrayCritical)(JNIEnv*, jarray, jboolean*);
    void        (*ReleasePrimitiveArrayCritical)(JNIEnv*, jarray, void*, jint);
    /*功用相似 Get/ReleaseStringChars,可是功用会有约束:在由Get/ReleaseStringCritical调用包围的代码段中,本机代码不能宣布恣意JNI调用,或导致当时线程堵塞
    函数原型:const jchar * GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy);
    */
    const jchar* (*GetStringCritical)(JNIEnv*, jstring, jboolean*);
    void        (*ReleaseStringCritical)(JNIEnv*, jstring, const jchar*);
    //创立一个弱大局引证
    jweak       (*NewWeakGlobalRef)(JNIEnv*, jobject);
    //删去一个弱大局引证
    void        (*DeleteWeakGlobalRef)(JNIEnv*, jweak);
	/*检查是否有挂起的反常exception*/
    jboolean    (*ExceptionCheck)(JNIEnv*);
    /*
    创立一个ByteBuffer方针,参数address为ByteBuffer方针首地址,且不为空,capacity为ByteBuffe的容量
    函数原型:jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity);*/
    jobject     (*NewDirectByteBuffer)(JNIEnv*, void*, jlong);
    /*获取一个Buffer方针的首地址*/
    void*       (*GetDirectBufferAddress)(JNIEnv*, jobject);
    /*获取一个Buffer方针的Capacity容量*/
    jlong       (*GetDirectBufferCapacity)(JNIEnv*, jobject);
    /* added in JNI 1.6 */
    /*获取jobject方针的引证类型:
    或许为: a local, global or weak global reference等引证类型:
    如下:
    JNIInvalidRefType = 0,
	JNILocalRefType = 1,
	JNIGlobalRefType = 2,
	JNIWeakGlobalRefType = 3*/
    jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
};

看到这儿面办法仍是挺多的,能够总结为下面几类:Class操作,反常Exception操作,方针字段以及办法操作,类的静态字段以及办法操作,字符串操作,锁操作等等。

明细:

  • Interface Function Table
  • 版别信息
    • GetVersion
    • Constants
  • Class 操作
    • DefineClass
    • FindClass
    • GetSuperclass
    • IsAssignableFrom
  • Exceptions
    • Throw
    • ThrowNew
    • ExceptionOccurred
    • ExceptionDescribe
    • ExceptionClear
    • FatalError
    • ExceptionCheck
  • 大局和本地引证
    • Global References
    • NewGlobalRef
    • DeleteGlobalRef
    • Local References
    • DeleteLocalRef
    • EnsureLocalCapacity
    • PushLocalFrame
    • PopLocalFrame
    • NewLocalRef
  • 弱大局引证
    • NewWeakGlobalRef
    • DeleteWeakGlobalRef
  • Object 操作
    • AllocObject
    • NewObject, NewObjectA, NewObjectV
    • GetObjectClass
    • GetObjectRefType
    • IsInstanceOf
    • IsSameObject
  • 方针字段Field 操作(可拜访)
    • GetFieldID
    • GetField Routines
    • SetField Routines
  • 方针办法Method操作
    • GetMethodID
    • CallMethod Routines, CallMethodA Routines, CallMethodV Routines
    • CallNonvirtualMethod Routines, CallNonvirtualMethodA Routines, CallNonvirtualMethodV Routines
  • 静态字段 操作
    • GetStaticFieldID
    • GetStaticField Routines
    • SetStaticField Routines
  • 静态办法 操作
    • GetStaticMethodID
    • CallStaticMethod Routines, CallStaticMethodA Routines, CallStaticMethodV Routines
  • 字符串 操作
    • NewString
    • GetStringLength
    • GetStringChars
    • ReleaseStringChars
    • NewStringUTF
    • GetStringUTFLength
    • GetStringUTFChars
    • ReleaseStringUTFChars
    • GetStringRegion
    • GetStringUTFRegion
    • GetStringCritical, ReleaseStringCritical
  • 数组 操作
    • GetArrayLength
    • NewObjectArray
    • GetObjectArrayElement
    • SetObjectArrayElement
    • NewArray Routines
    • GetArrayElements Routines
    • ReleaseArrayElements Routines
    • GetArrayRegion Routines
    • SetArrayRegion Routines
    • GetPrimitiveArrayCritical, ReleasePrimitiveArrayCritical
  • 注册和反注册native办法
    • RegisterNatives
    • UnregisterNatives
  • Monitor 操作
    • MonitorEnter
    • MonitorExit
  • NIO 支撑
    • NewDirectByteBuffer
    • GetDirectBufferAddress
    • GetDirectBufferCapacity
  • 反射操作
    • FromReflectedMethod
    • FromReflectedField
    • ToReflectedMethod
    • ToReflectedField
  • Java VM Interface
    • GetJavaVM

JNI三种引证

前面在剖析JNI函数的时分有涉及到JNI的三种引证Global引证和Local引证以及weak引证,下面咱们来介绍下JNI中的几种引证。

  • Local引证

    JNI中运用 jobject, jclass, and jstring等来标志一个Java方针,然而在JNI办法在运用的进程中会创立许多引证类型,假如运用进程中不留意就会导致内存走漏。

    Local引证其实便是Java中的部分引证,在声明这个部分变量的办法结束或许退出其效果域后就会被GC回收。

    部分引证能够直接运用:NewLocalRef来创立,虽然部分引证能够在跳出效果域后被回收,可是仍是期望在不运用的时分调用DeleteLocalRef来手动回收掉。

  • Global引证

    大局引证,多个当地需求运用的时分就会创立一个大局的引证(NewGlobalRef办法创立),大局引证只要在显示调用DeleteGlobalRef的时分才会失效,否则会一直存在与内存中,这点必定要留意。

    下面是一段不合法的代码:

    /* This code is illegal */
    static jclass cls = 0;
    static jfieldID fld;
    JNIEXPORT void JNICALL
    Java_FieldAccess_accessFields(JNIEnv *env, jobject obj)
    {
        ...
        if (cls == 0) {
            cls = (*env)->GetObjectClass(env, obj);
            if (cls == 0) {
                ... /* error */
            }
            fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
        }
        /* access the member variable using cls and fid */
        ...
    }
    

    运用GetObjectClass办法创立的是一个部分变量,此时这个部分变量的有效效果域是这个办法,假如出了这个办法,在第二次调用Java_FieldAccess_accessFields的时分,发现cls是一个不合法的本地引证,这个时分就会报错,所以正确的办法就如下:运用NewGlobalRef创立大局变量,而GetObjectClass运用一个本地变量存储。

    /* This code is correct. */
    static jclass cls = 0;
    static jfieldID fld;
    JNIEXPORT void JNICALL
    Java_FieldAccess_accessFields(JNIEnv *env, jobject obj)
    {
        ...
        if (cls == 0) {
            jclass cls1 = (*env)->GetObjectClass(env, obj);
            if (cls1 == 0) {
                ... /* error */
            }
            cls = (*env)->NewGlobalRef(env, cls1);
            if (cls == 0) {
                ... /* error */      
            }
            fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
        }
        /* access the member variable using cls and fid */
        ...
    }
    

    这个办法告诉咱们部分变量出了效果域后就失效了,不能被第二次运用,而大局变量能够被二次运用

    可是留意在大局变量不需求运用后需求手动调用DeleteGlobalRef,防止内存走漏

  • Weak引证

    弱引证能够运用大局声明的办法,差异在于:弱引证在内存不足或许严重的时分会主动回收掉,或许会呈现时间短的内存走漏,可是不会呈现内存溢出的状况,主张不需求运用的时分手动调用DeleteWeakGlobalRef释放引证。

JNI反常处理

Java层反常:

一般处理办法:由Java层抛出然后在native层处理

void updateName(String name) throws Exception {
    this.name = name;
    Log.d("HelloJni","你成功调用了HelloCallBack的办法:updateName");
    throw new Exception("dead");
}

之后操作便是native层反常操作了。

native层反常

处理办法1:native层自行处理

jboolean hasException = env->ExceptionCheck();
if(hasException == JNI_TRUE){
    //打印反常,同Java中的printExceptionStack;
    env->ExceptionDescribe();
    //铲除当时反常
    env->ExceptionClear();
}

处理办法2:native层抛出给Java层处理:

/*检测是否有反常*/
jboolean hasException = env->ExceptionCheck();
if(hasException == JNI_TRUE){
    //打印反常,同Java中的printExceptionStack;
    env->ExceptionDescribe();
    //铲除当时反常
    env->ExceptionClear();
    /*办法2:抛出反常给上面,让Java层去捕获*/
    jclass noFieldClass = env->FindClass("java/lang/Exception");
    std::string msg(_fieldName);
    std::string header = "找不到该字段";
    env->ThrowNew(noFieldClass,header.append(msg).c_str());
    env->ReleaseStringUTFChars(fieldName,_fieldName);
    return;
}

然后java文件中捕获反常:

try{
    jni.callJavaField("com/android/hellojni/HelloCallBack","namesss");
}catch (Exception e){
    e.printStackTrace();
    return;
}

C和C++彼此调用

在解说C和C++彼此调用之前,咱们先来了解C和C++编译和链接进程的差异

C++的编译和链接

咱们都知道C++是一个面向方针的编程办法,而面向方针最中心的特性便是重载,函数重载给咱们带来了很大便利性。假定界说如下函数重载办法:

void log(int i);
void log(char c);
void log(float f);
void log(char* c);

则在编译后:

_log_int
_log_char
_log_float
_log_string

编译后的函数名经过带上参数的类型信息,这样衔接时依据参数就能够找到正确的重载办法。

C++中给的变量编译也是这样一个进程,如大局变量会编译为g_xx,类变量编译为c_xx.衔接时也是依照这种机制去查找对应的变量的。

C的编译和衔接

C言语中并没有重载和类这些特性,故不会像C++相同将log(int i)编译为_log_int,而是直接编译为_log函数,当C++去调用C中的log(int i)办法时,会找不到_log_int办法,此时extern “C”的效果就体现出来了。

下面来看下C和C++是怎么彼此调用的。

C++中调用C的代码

假定一个C的头文件cHeader.h中声明晰一个函数_log(int i),假如C++要调用它,则有必要增加上extern关键字。代码如下:

//cHeader.h
#ifndef C_HEADER
#define C_HEADER
extern void _log(int i);
#endif // !C_HEADER

在对应的cHeader.c文件中完结_log办法:

//cHeader.c
#include "cHeader.h"
#include <stdio.h>
void _log(int i) {
	printf("cHeader %d\n", i);
}

在C++中引证cHeader中的_log办法:

//main.cpp
extern "C" {
	//void _log(int i);
	#include "cHeader.h"
}
int main() {
	_log(100);
}

linux履行上述文件的指令为:

  • 1.首要履行gcc -c cHeader.c,会发生cHeader.o;
  • 2.然后履行g++ -o C++ main.cpp cHeader.o
  • 3.履行程序输出:Header 100

留意: 在main.cpp文件中能够不必包括函数声明的文件,即“extern “C”{#include”cHeader.h”}”,而直接改用extern “C” void _log(int i)的办法。那main.cpp是怎么找到C中的_log函数,并调用的呢?

那是由于首要经过gcc -c cHeader.c生成一个方针文件cHeader.o,然后咱们经过履行g++ -o C++ main.cpp cHeader.o这个指令指明晰需求链接的方针文件cHeader.o。 main.cpp中只需求声明哪些函数需求以C的办法调用,然后去方针文件中查找即可。“.o”为方针文件。相似Windows中的obj文件。

C中调用C++的代码

C中调用C++中的代码和前面的有所不同,首要在cppHeader.h中声明一个_log_i办法。

#pragma once
extern "C" {
	void _log_i(int i);
}

在对应的cppHeader.cpp中完结该办法:

#include "cppHeader.h"
#include <stdio.h>
void _log_i(int i) {
	printf("cppHeader:%d\n", i);
}

界说一个cMain.c文件调用_log_i办法:

extern void _log_i(int i);
int main() {
	_log_i(120);
}

留意点

  • 1.假如直接在.c文件中include “cppHeader.h”是会报错的,由于cppHeader.h中包括了extern “C”,而将cppHeader.h包括进来,会直接打开cppHeader.h内容,而extern “C”在C言语中是不支撑的,所以会报错
  • 2.在.c文件中不加extern void _log_i(int i)也会报错,由于会无法找到对应的函数。

linux履行上述文件的指令为

  • (1)首要履行指令:g++ cppHeader.cpp -fpic -shared -g -o cppHeader.so 该指令是将cppHeader.cpp编译成动态衔接库,其间编译参数的解说如下:

    • -shared 该选项指定生成动态衔接库(让衔接器生成T类型的导出符号表,有时分也生成弱衔接W类型的导出符号),不必该标志外部程序无法衔接。相当于一个可履行文件
    • -fPIC:表明编译为方位独立的代码,不必此选项的话编译后的代码是方位相关的所以动态载入时是经过代码拷贝的办法来满足不同进程的需求,而不能达到真正代码段同享的目的。
    • -g:为调试
  • (2)然后再履行指令:gcc cMain.c cppHeader.so -o cmain 该指令是编译cMain.c文件,一同链接cppHeader.so文件,然后发生cmain的可履行文件。

  • (3)最终履行指令:./cmain 来履行该可履行程序

    成果:cppHeader:120
    

前面已经对NDK开发做了一个比较长的理论常识解说,下面咱们运用一个NDK开发事例来对上面理论进行实战.

4.NDK实战

今天带来的Demo首要完结下面几个功用:

  • 1.native层调用Java层的类的字段和办法
  • 2.native层调用调用第三方so库的api

下面进入正题:先完结第一个需求:

需求1:native层调用Java层的类的字段和办法

  • 1.咱们先创立一个Native C++的Android项目

    【NDK】聊聊NDK开发那些事

    项目整体结构如下:

    【NDK】聊聊NDK开发那些事

  • 2.然后在cpp文件夹下创立两个文件夹:srcinclude,而且在src下创立hellojni.cpp文件,在include文件夹下创立一个hellojni.h文件,用来声明hellojni.cpp中的办法。

    【NDK】聊聊NDK开发那些事

  • 3.做了以上准备后咱们能够开端编写装备文件CmkaeLists.txt了。

    • 运用add_library创立一个新的so库

      add_library(
              hello-jni #库名
              SHARED #设置为so同享库,假如需求创立静态库.a文件,需求声明为STAIC
              src/hellojni.cpp #需求参加编译的src文件,假如有多个,则运用空格或许换行包括进来
      )
      
    • 运用target_link_libraries将loglib链接到hello-jni.so中,这样能够便利咱们在native层打印logcat日志

      target_link_libraries(hello-jni
             ${log-lib}
      

    )

    
    上面两进程就能够成功完结后就能够去编写c++代码了,是不是很简略
    
  • 4.编写hellojni.cpp文件。

    由于要完结native层调用Java层字段和办法,所以这儿界说了两个办法:callJavaField和callJavaMethod

    hellojni.cpp

  //
  // Created by Administrator on 2023/2/22.
  //
  #include "../include/hellojni.h"
  #include <jni.h>
  #include <string>
  #include <assert.h>
  #include <android/log.h>
  #include <string.h>
  static const char* TAG ="HelloJni";
  #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
  #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)
  using namespace std;
  #define JNI_CLASS_NAME "com/android/hellojni/HelloJni" //java途径
  void callJavaField(JNIEnv* env,jobject obj,jstring className,jstring fieldName){
      jboolean iscopy;
      const char* name = env->GetStringUTFChars(fieldName,&iscopy);
      LOGD("invoke method:%s",name);
      /*
       * 进程1:界说类的全约束名:const char* str = "java/lang/String"
       * 进程2:找到类的jclass: env->FindClass()
       * 进程3:读取类的结构函数:env->GetMethodID(c,"<init>","()V");
       * 进程4:依据结构函数创立一个Object方针:env->NewObject(c,constructMethod);
       * 进程5:调用方针的字段和办法:
       * */
      //进程1:界说类的全约束名
      const char* classNameStr = env->GetStringUTFChars(className,&iscopy);
      //进程2:找到类的jclass
      jclass c = env->FindClass(classNameStr);
      //进程3:读取类的结构函数
      jmethodID constructMethod = env->GetMethodID(c,"<init>","()V");
      //进程4:依据结构函数创立一个Object方针
      jobject objCallBack = env->NewObject(c,constructMethod);
      //进程5:调用方针的字段和办法,需求先获取类的字段id和办法id
      /*
       * 获取字段id
       * 参数1:class
       * 参数2:字段称号
       * 参数3:字段签名格局
       * */
      jboolean isCopy;
      const char* _fieldName = env->GetStringUTFChars(fieldName,&isCopy);
      /*
      * 此处假如传入一个找不到的字段会报错,假如不做反常处理,应用直接会溃散,为了更好的知晓问题地点,需求		* 运用jni反常处理机制
       * 此处是native反常,有两种反常处理机制:
       * 办法1:native层处理
       * 办法2:抛出给Java层处理
       * */
      jfieldID field_Name = env->GetFieldID(c,_fieldName,"Ljava/lang/String;");
      /*办法1:native层处理*/
      /*检测是否有反常*/
      jboolean hasException = env->ExceptionCheck();
      if(hasException == JNI_TRUE){
          //打印反常,同Java中的printExceptionStack;
          env->ExceptionDescribe();
          //铲除当时反常
          env->ExceptionClear();
          //办法2:抛出反常给上面,让Java层去捕获
          jclass noFieldClass = env->FindClass("java/lang/Exception");
          std::string msg(_fieldName);
          std::string header = "找不到该字段";
          env->ThrowNew(noFieldClass,header.append(msg).c_str());
          env->ReleaseStringUTFChars(fieldName,_fieldName);
          return;
      }
      //没有反常去获取字段的值
      jstring fieldObj = static_cast<jstring>(env->GetObjectField(objCallBack, field_Name));
      const char* fieldC = env->GetStringUTFChars(fieldObj,&isCopy);
      LOGD("你成功获取了字段%s值:%s",_fieldName,fieldC);
      env->ReleaseStringUTFChars(fieldObj,fieldC);
  }
  jboolean callJavaMethod(JNIEnv* env,jobject obj1,jstring className,jstring methodName){
      /*
       * 1.找到类:FindClass
       * 2.创立一个方针
       * 3.获取这个类对应的办法id
       * 4.经过方针和办法id调用对应办法
       * 5.释放内存
       * */
      jboolean isCopy;
      const char* classNameStr = env->GetStringUTFChars(className,&isCopy);
      //1.找到类:FindClass
      jclass callbackClass = env->FindClass(classNameStr);
      //获取结构函数
      jmethodID constructMethod = env->GetMethodID(callbackClass,"<init>","()V");
  	//2.创立一个方针
      jobject objCallBack = env->NewObject(callbackClass,constructMethod);
      const char* _methodName = env->GetStringUTFChars(methodName,&isCopy);
      //3.获取这个类对应的办法id
      jmethodID _jmethodName = env->GetMethodID(callbackClass,_methodName,"(Ljava/lang/String;)V");
      const char *str = "123";
      /*切记JNI回来类型不能直接运用根底类型,而要用jni语法中界说的类型:如String需求转换为jstring
       * 否则会报错:JNI DETECTED ERROR IN APPLICATION: use of deleted global reference*/
      jstring result = env->NewStringUTF(str);
      //4.经过方针和办法id调用对应办法
      env->CallVoidMethod(objCallBack,_jmethodName,result);
      if(env->ExceptionCheck()){
          env->ExceptionDescribe();
          env->ExceptionClear();
      }
  	//释放字符串内存
      env->ReleaseStringUTFChars(methodName,_methodName);
      env->ReleaseStringUTFChars(className,classNameStr);
      return JNI_TRUE;
  }
  static JNINativeMethod gMethods[] = {
          {"callJavaField","(Ljava/lang/String;Ljava/lang/String;)V",(void *)callJavaField},
          {"callJavaMethod","(Ljava/lang/String;Ljava/lang/String;)Z",(void *)callJavaMethod},
  };
  int register_dynamic_Methods(JNIEnv *env){
      std::string s = JNI_CLASS_NAME;
      const char* className = s.c_str();
      jclass clazz = env->FindClass(className);
      if(clazz == NULL){
          return JNI_FALSE;
      }
      //注册JNI办法
      if(env->RegisterNatives(clazz,gMethods,sizeof(gMethods)/sizeof(gMethods[0]))<0){
          return JNI_FALSE;
      }
      return JNI_TRUE;
  }
  jint JNI_OnLoad(JavaVM *vm, void *reserved) {
      JNIEnv *env = NULL;
      if(vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK){
          return JNI_ERR;
      }
      assert(env != NULL);
      if(!register_dynamic_Methods(env)){
          return JNI_ERR;
      }
      return JNI_VERSION_1_6;
  }

这儿笔者运用了动态注册的办法,办法比较少的时分运用静态注册也是能够的,办法多了之后主张运用动态注册,且动态注册的代码都是有框架的,直接拿过来改下办法名即可。

  • 5.编写Java层的调用代码

    此处要留意的是调用的类的类名以及包名都要和c++文件中声明的共同,否则会报错

    HelloJni.java

  package com.android.hellojni;
  public class HelloJni {
      static {
          System.loadLibrary("hello-jni");
      }
      public native void callJavaField(String className,String fieldName) ;
      public native boolean callJavaMethod(String className,String methodName) ;
  }
  • 以上进程都完结今后,就能够开端测验了。

    这儿咱们写了一个测验类:HelloCallBack.java

    package com.android.hellojni;
    import android.util.Log;
    public class HelloCallBack {
        String name = "HelloCallBack";
        void updateName(String name){
            this.name = name;
            Log.d("HelloJni","你成功调用了HelloCallBack的办法:updateName");
        }
    }
    

调用代码:

  try{
      jni.callJavaField("com/android/hellojni/HelloCallBack","name");
      jni.callJavaMethod("com/android/hellojni/HelloCallBack","updateName");
  }catch (Exception e){
      e.printStackTrace();
      return;
  }

测验成果:

  D/HelloJni: 你成功获取了字段name值:HelloCallBack
  D/HelloJni: 你成功调用了HelloCallBack的办法:updateName

此时假如咱们写错了类的字段名:如jni.callJavaField(“com/android/hellojni/HelloCallBack”,”namesss”);

再运转下:此时

  //1
  W/System.err: java.lang.NoSuchFieldError: no "Ljava/lang/String;" field "namesss" in class "Lcom/android/hellojni/HelloCallBack;" or its superclasses
  W/System.err:     at com.android.hellojni.HelloJni.callJavaField(Native Method)
  W/System.err:     at com.android.hellojni.MainActivity$1.onClick(MainActivity.java:30)
  ...
  //2
  W/System.err: java.lang.Exception: 找不到该字段namesss
  W/System.err:     at com.android.hellojni.HelloJni.callJavaField(Native Method)
  W/System.err:     at com.android.hellojni.MainActivity$1.onClick(MainActivity.java:30)
  ...

注释1的错误日志是咱们在helloJni.cpp中运用了ExceptionCheck和ExceptionDescribe检测和打印出来的错误日志。

注释2的错误日志是咱们在helloJni.cpp中运用了将反常抛出,而且Java层进行了捕获,所以能够打印在Java层打印出报错日志,所以应用也不会溃散。

以上便是一个完好的JNI调用进程。代码已经放在github上,咱们能够自行查阅。

下面咱们再来完结别的一个功用,对第三方库的运用

需求2:native层调用第三方so库的api

便利点,我直接拿前面事例的hellojni.so来测验,可是为了完结三方调用还需求对文件进行改造

  • 1.要完结三方so库调用,咱们在hellojni.h中声明两个和hellojni.cpp中对应的办法:callJavaField和callJavaMethod,一般状况下这个头文件是第三方库一同供给的给外部调用的。

    hellojni.h

#include <jni.h>
#ifndef HELLO_JNI_HELLOJNI_H
#define HELLO_JNI_HELLOJNI_H
#ifdef __cplusplus
extern "C" {
#endif
    void callJavaField(JNIEnv* env,jobject obj,jstring className,jstring fieldName);
    jboolean callJavaMethod(JNIEnv* env,jobject obj1,jstring className,jstring methodName);
 //HELLO_JNI_HELLOJNI_H
#ifdef __cplusplus
};
#endif
#endif

留意:此处对引进的是函数运用了extern “C”对办法进行了包裹,目的便是为了当引证的是cpp文件,extern “C”润饰的函数能够让外部拜访到。

  • 2.CMakeLists装备文件改造
  #增加调用库
  add_library( # Sets the name of the library.
               third-call-lib
               # Sets the library as a shared library.
               SHARED
               # Provides a relative path to your source file(s).
               thirdCall.cpp )
  #将第三方库增加进来,设置第三个参数为IMPORTED
  add_library(hellojni-lib
          SHARED
          IMPORTED)
  #设置第三方库的途径:IMPORTED_LOCATION
  set_target_properties(hellojni-lib PROPERTIES IMPORTED_LOCATION
          ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libhello-jni.so
          )
  #设置调用库的include文件夹
  target_include_directories(
          third-call-lib
          PRIVATE
          ${CMAKE_SOURCE_DIR}/include
  )
  #将第三方库链接到调用库中
  target_link_libraries( 
          third-call-lib
          hellojni-lib)
  • 3.编写thirdCall.cpp文件,在这内部调用第三方库。

    #include <jni.h>
    #include <string>
    //#include "include/hellojni.h"
    #include <hellojni.h>
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_android_thirdsocall_MainActivity_callThirdSoMethod(
            JNIEnv* env,
            jobject obj,jstring className,jstring methodName) {
        std::string hello = "hello c++";
        callJavaMethod(env,obj,className,methodName);
        return env->NewStringUTF(hello.c_str());
    }
    

    这儿需求将第三方头文件导入进来,假如CmakeLists文件中没有声明头文件,就只能老老实实的运用#include "include/hellojni.h" 这种办法导入了.

  • 4.最终测验下:

    callThirdSoMethod("com/android/thirdsocall/HelloCallBack","updateName");
    

    成果:

    D/HelloJni: 你成功调用了HelloCallBack的办法:updateName
    

踩坑记载

笔者写这个demo的进程中也呈现一些比较坑的工作:

比方:关于JNI办法来说,运用如下办法回来或许调用直接溃散了,看了半响也不知道为啥??

env->CallVoidMethod(objCallBack,_jmethodName,"123");

这段代码编译没问题,可是在运转的时分就报错了:

报错:JNI DETECTED ERROR IN APPLICATION: use of deleted global reference

终究定位到是最终一个参数需求运用jstring而不能直接运用字符串表明。下面这个办法就没啥问题了。。咱们切记哦。

env->CallVoidMethod(objCallBack,_jmethodName,env->NewStringUTF("123"));

又比方:头文件导入一直报找不到头文件,最终发现是运用target_include_directories相关Include文件的文件运用了文件作为目录,而非文件夹,正确做法是相关文件夹,如下:

#设置调用库的include文件夹
target_include_directories(
        third-call-lib
        PRIVATE
        ${CMAKE_SOURCE_DIR}/include
)

项目已经发到github,有需求自己去下载看吧。

总结

本文章简直涵盖关于NDK开发的一切层面,首要是建立在三个层面:构建层,Java层以及Native层

对每一层都进行了较为具体的解说,其间构建层首要担任构建装备so文件的装备,而真正做工作的是Native层。最终运用了一个Demo对前面理论进行了实践,真正做到活学活用。

最终仍是要说一句:纸上得来终觉浅,绝知此事要躬行,多练才能熟能生巧

我是小余,咱们下期见。