在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 <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>
// 指定要注册的类
#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. 指定依赖动态库时,要求单行不能换行;
- 2. 各个依赖链接库名称用空格隔开;
- 3. 如果是自定义库,通过add_library添加进来,直接添加其名称即可;
- 4. 如果是NDK预置库,通过find_library添加进来,需要通过${lib-name}的索引方式;
3. 其他说明
- 涉及CMakeLists.txt的编写方法会在其他文章详细说明,此处仅做简要说明;
- 由于CMakeLists.txt 方式更具通用性,并且也是Android Studio 自2.2以后重点推荐使用,建议用其替换以往的Android.mk编译方式;
- 关于使用Gradle选用CMakeLists.txt进行NDK构建配置说明也将在其他文章中详细讨论;
4. 参考
- Google官方文档 : 添加 NDK API
- 使用Gradle及CMakeLists构建NDK : CSDN 博客