【NDK】- JNI方法动态注册

简介

虚拟机在加载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"
          }
      }
    
    1. -fexceptions
      对整个应用启用异常
    2. -frtti
      对整个应用启用 RTTI

    更多请查看官网C++ 库支持

  • 编写一个有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中的方法签名


    签名列表.png

    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;
    }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容