Android NDK - JNI 函数注册

在JNI中定义native函数,所有的native函数均需要注册之后才能在动态链接库加载后被索引到,函数注册有两种方式:

  • 静态注册:采用规范命名函数名称,并生成对应的头文件;
  • 动态注册:采用 JNINativeMethod 结构体进行动态注册,注册时即声明其签名/native函数对应关系/java层的native对应函数声明类。

当前Android本身即推荐使用动态注册的方式,因此对于较早期的静态注册方法就不再赘述,仅罗列对比两者的优缺点:

- 需要编译所有声明了native函数的Java类,每个所生成的class文件都得用javah命令生成一个头文件;
- JNI层函数名特别长,书写不方便不美观;
- 初次调用native函数时要根据函数名字搜索对应的JNI层函数来建立关联关系,这样会影响运行效率;

基于此下文详细说明动态注册的方法。

1. 项目结构

最终项目中涉及JNI及Java的文件结构应该如下:

TestNativeFunctions.Java

public class TestNativeFunctions{
    static {
        System.loadLibrary("testlib.so");
    }
    
    public void native testFunction();
    public int  native testFunction2(int val);
}

TestNativeFunctionsJNI.cpp

#include 
#include 
#include 
#include 
#include 

// 指定要注册的类
#define JNIREG_CLASS "com/test/TestNativeFunctions"

JNIEXPORT void JNICALL testFunction(JNIEnv *env, jclass clazz)
{
    printf("hello in c native code./n");
    return;
}

JNIEXPORT jint JNICALL testFunction2(JNIEnv *env, jclass clazz, jint val)
{
    printf("hello in c native code, val = %d /n", val);
    return val;
}


// 定义一个JNINativeMethod数组,其中的成员就是Java代码中对应的native方法
static JNINativeMethod nativeMethods[] = {
    { "testFunction",  "()V",  (void*)testFunction  },
    { "testFunction2", "(I)I", (void*)testFunction2 },
};


//最后将native函数进行动态注册,当然可放在JNI_OnLoad函数中当加载库是自动执行
extern "C" JNIEXPORT jnit JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm-> GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    
    //首先需要找到在java层对应的类建立映射
    jclass clazz = env->FindClass(JNIREG_CLASS);
    if(clazz == NULL){
     printf("error in finding java class!\n");
        return JNI_FALSE;
    }
    
    //调用接口进行注册
    env->RegisterNatives(clazz, nativeMethods, sizeof(nativeMethods) / sizeof(nativeMethods[0]));
    if(env->ExceptionCheck()){
        printf("error in registering!\n");
        return JNI_FALSE;
    }
    //注册函数成功
    return JNI_TRUE;
}

至此,所有的JNI和java函数就均写好了,接下来就是需要对应的CMake或者Android.mk即可完成编译运行。

2. 编译方式(Android.mk / CMakeLists.txt)

如果当前项目采用的是Android.mk的编译方式,那么在工程路径的根目录下也要有对应的Android.mk 及 Application.mk, 对于JNI下面的每个子模块也需要有一个Android.mk(因此一个工程中可能会包含多个JNI的module).

如果采用的是Android.mk的编译方式,那么在该JNI module中的Android.mk 可以参考为:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JNIDemo
LOCAL_SRC_FILES := TestNativeFunctionsJNI.cpp

include $(BUILD_SHARED_LIBRARY)

根目录下的Android.mk作用是用于声明需要编译的JNI模块,如编译所有模块:

include $(call all_subdir_makefiles)

表示会在该级路径下搜索所有的Android.mk进行编译,当然Application.mk是为了声明一些编译参数。

如果采用CMake的编译方式,与Android.mk拥有同样的结构,即在JNI的根目录下会有一个CMakeLists.txt,其目的是为了声明参与编译的module,其格式为:

cmake_minimum_required(VERSION 3.4.1)
add_subdirectory(src/main/cpp/test_module)
# 可以添加其他的module

而在各个module 的路径下,也会有一个CMakeLists.txt,其作用是声明该module的编译依赖/编译配置等,举例:

# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.

cmake_minimum_required(VERSION 3.4.1)

# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add.library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.

add_library( # Specifies the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/test_module/TestNativeFunctionsJNI.cpp )

以上配置可以将其编译为libnative-lib.so的动态链接库文件并打包到apk之中,当然如果有其他的依赖,还需要如下内容:

#添加头文件 依赖
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)

#添加 动态链接库
add_library(lib_name SHARED IMPORTED)
set_target_properties( lib_name
                       PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/armeabi-v7a/libnative-lib.so)
#添加 静态链接库
add_library(lib_name STATIC IMPORTED)
set_target_properties( lib_name
                       PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/armeabi-v7a/libnative-lib.a)

#在NDK中寻找预置 动态链接库
find_library( # Defines the name of the path variable that stores the
              # location of the NDK library.
              log-lib

              # Specifies the name of the NDK library that
              # CMake needs to locate.
              log )

在声明完以上依赖之后,则需要将其添加到目标module的编译中:

# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the log library to the target library.
                       libnative-lib ${log-lib} )

在此声明中,有如下需要注意:

    1. 指定依赖动态库时,要求单行不能换行;
    1. 各个依赖链接库名称用空格隔开;
    1. 如果是自定义库,通过add_library添加进来,直接添加其名称即可;
    1. 如果是NDK预置库,通过find_library添加进来,需要通过${lib-name}的索引方式;

3. 其他说明

  1. 涉及CMakeLists.txt的编写方法会在其他文章详细说明,此处仅做简要说明;
  2. 由于CMakeLists.txt 方式更具通用性,并且也是Android Studio 自2.2以后重点推荐使用,建议用其替换以往的Android.mk编译方式;
  3. 关于使用Gradle选用CMakeLists.txt进行NDK构建配置说明也将在其他文章中详细讨论;

4. 参考

  1. Google官方文档 : 添加 NDK API
  2. 使用Gradle及CMakeLists构建NDK : CSDN 博客
    CSDN 同步发布地址

你可能感兴趣的:(Android NDK - JNI 函数注册)