简介
虚拟机在加载so库的时候,会调用JNI_OnLoad方法,所以可以在这JNI_OnLoad完成JNI方法动态注册。不清楚为什么系统会调用JNI_OnLoad方法的,请查看上一篇文章System.loadLibrary源码分析
分类
加载so会有这样一条逻辑:
JVM_NativeLoad
//art/runtime/openjdkjvm/OpenjdkJvm.cc
---->LoadNativeLibrary()
---->sym = library->FindSymbol("JNI_OnLoad", nullptr)
--->调用JNI_OnLoad方法
在我们要加载so库中查找JNI_OnLoad方法,如果没有系统就认为是静态注册方式进行的,直接返回true,代表so库加载成功,如果找到JNI_OnLoad就会调用JNI_OnLoad方法,JNI_OnLoad方法中一般存放的是方法注册的函数,所以如果采用动态注册就必须要实现JNI_OnLoad方法,否则调用java中申明的native方法时会抛出异常。
- 静态注册:
缺点:方法名很长,不便于书写,初次调用时需要依据名字搜索对应的JNI层函数来建立关联关系,会影响运行效率。 - 动态注册:
使用一种数据结构JNINativeMethod来记录java native函数和JNI函数的对应关系移植方便(一个java文件中有多个native方法,java文件的包名更换后)。
JNI开发简单流程
main目录下创建cpp目录
module目录下创建CMakeLists.txt
-
build.gradle加上ndk相关配置
externalNativeBuild { cmake { cppFlags "-frtti -fexceptions" } } ndk{ moduleName "app" abiFilters "armeabi","x86" //指定平台 cFlags "-DANDROID_NDK" } externalNativeBuild { cmake { path "CMakeLists.txt" } }
- -fexceptions
对整个应用启用异常 - -frtti
对整个应用启用 RTTI
更多请查看官网C++ 库支持
- -fexceptions
-
编写一个有native方法的kotlin类
比如我这里新建了一个CSocket.java,里面定义了一个本地方法:external fun connect(ip: String, port: Int,time: Int): Int
-
cpp目录新建C/C++文件
在cpp文件里创建对应的jni函数,方法名规则是:
Java + kotlin文件的包路径 + kotlin类名 + native方法名,之间全部用"_"隔开。当然也可以用javah来生成jni头文件,然后头文件里面会生成对应的方法名。写好jni方法后,添加打印代码。extern "C" JNIEXPORT int JNICALL Java_blog_pds_com_socket_core_client_CSocket_connect( JNIEnv *env, jobject instance,jstring ip, int port,int time){ LOGI("start connect!!!!!!"); }
-
编写CMakeLists.txt文件内容。
cmake_minimum_required(VERSION 3.4.1) add_library( socket_client-lib SHARED src/main/cpp/CSocket.cpp) find_library( log-lib log) target_link_libraries( # Specifies the target library. socket_client-lib ${log-lib})
编译工程
如果编译不通过,请仔细查看控制台日志,确保NDK库路径设置正确,CMakeLists.txt配置配置正确,特别是里面文件的路径。-
加载库文件
System.loadLibrary("socket_client-lib")
-
运行查看效果
控制台打印日志:2019-06-20 14:53:33.123 28244-28259/? I/socket:: start connect!!!!!!
动态注册jni方法
那么接下来就是最总要的部分了,那么怎么进行JNI动态注册呢?其实系统很多地方都用到的动态注册,我们参考MediaPlayer.java里面的native方法的动态注册。看一下MediaPlayer.java的native方法:
private native void _setDataSource(FileDescriptor fd, long offset, long length)
throws IOException, IllegalArgumentException, IllegalStateException;
定位到/frameworks/base/media/jni/android_media_MediaPlayer.cpp里面的JNI_OnLoad方法。
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed\n");
goto bail;
}
...
if (register_android_media_MediaPlayer(env) < 0) {
ALOGE("ERROR: MediaPlayer native registration failed\n");
goto bail;
}
...
}
看一下register_android_media_MediaPlayer(env)方法
static int register_android_media_MediaPlayer(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
- env:JNI运行环境,对应线程
- className:class类名
- gMethods:JNI和native方法对应表
- NELEM(gMethods):gMethods数组长度
gMethods
static const JNINativeMethod gMethods[] = {
...
{"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD},
...
}
来分析一下数组的写法:
第一个元素:java文件里面的native函数名
-
第二个元素:函数的参数和返回类型
()里面是参数类型,后面是返回类型先看一下Java中的方法签名
L全类名; 引用类型 以L开头、;结尾,中间是引用类型的全类名
第三个参数:函数指针,指向C函数。
即Java调用_setDataSource native方法,会执行JNI函数名为android_media_MediaPlayer_setDataSourceFD的函数。即用android_media_MediaPlayer_setDataSourceFD替代了之前静态注册使用的函数名。
static void
android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
...
}
NELEM(gMethods)
看一下宏定义:
#define NELEM(m) (sizeof(m) / sizeof((m)[0]))
返回的就是数组的长度。
实现自己的动态注册
- cpp文件添加如下方法,该方法在加载库的时候就会调用,所以可以在该方法里面进行jni方法动态注册。至
注册#define NELEM(m) (sizeof(m) / sizeof((m)[0])) static const JNINativeMethod gMethods[] = { {"videoSplit","(Ljava/lang/String;Ljava/lang/String;I)V",(void*)native_video_split}, {"videoMerge","(Ljava/lang/String;Ljava/lang/String;I)V",(void*)native_video_merge}, }; JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { LOGI("开始动态注册JNI"); JNIEnv* env = NULL; jint result = -1; if ((*vm)->GetEnv(vm,(void**) &env , JNI_VERSION_1_4) != JNI_OK){ LOGI("ERROR: GetEnv failed\n"); return -1; } assert(env != NULL); registerNatives(env); return JNI_VERSION_1_4; }
- registerNatives
static int registerNatives(JNIEnv* engv){ LOGI("registerNatives begin"); jclass clazz; clazz = (*engv)>FindClass(engv,"kt/edu/pds/kt/mrp/ndk/CVideoSplitAndMerge"); if (clazz == NULL){ LOGI("clazz is null"); return JNI_FALSE; } if ((*engv)->RegisterNatives(engv,clazz,gMethods,NELEM(gMethods)) < 0){ LOGI("RegisterNatives error"); return JNI_FALSE; } return JNI_TRUE; }