android JNI学习(三):jni开发接口的编写:静态和动态方式

本文主要讲:

  1. jni开发接口的编写:静态和动态方式介绍

Java 的native 方法如何与so中的方法关联,相互调用呢?

这就需要用到注册的概念了,通过注册,将指定的 native 方法和 so 中对应的方法绑定起来(函数映射表),这样就能够找到相应的方法了。

注册分为 静态注册动态注册 两种。

静态注册
原理:根据函数名来建立 java 方法与 JNI 函数的一一对应关系;

实现流程:

编写 java 代码;
利用 javah 指令生成对应的 .h 文件;
对 .h 中的声明进行实现;

弊端:

编写不方便,JNI 方法名字必须遵循规则且名字很长;
编写过程步骤多,不方便;
程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时;

在AS中新建一个Java Library名为media,写一个简单的MediaRecorder.java,如下所示。
1.编写一个java类,在里面加载对应的so库并且通过native关键字定义需要调用的函数

package com.example;
public class MediaRecorder {
    static {
        System.loadLibrary("media_jni");
        native_init();
    }
 
    private static native final void native_init();
    public native void start() ;
}


2.接着进入项目的media/src/main/java目录中执行如下命令

javac com.example.MediaRecorder.java
javah com.example.MediaRecorder
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_MediaRecorder */
 
#ifndef _Included_com_example_MediaRecorder
#define _Included_com_example_MediaRecorder
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_MediaRecorder
 * Method:    native_init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_MediaRecorder_native_1init
  (JNIEnv *, jclass);//1
 
/*
 * Class:     com_example_MediaRecorder
 * Method:    start
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_MediaRecorder_start
  (JNIEnv *, jobject);
 
#ifdef __cplusplus
}
#endif

返回值 + Java前缀+全路径类名+方法名+参数1JNIEnv+参数2jobject+其他参数

3.将头文件拷贝到jni目录下(在src同级目录建立文件夹,Android studio 在java同级目录建立文件夹)
4.编写C/C++源代码 并把刚拷贝的头文件包含进去 ,复制头文件中函数的定义部分,并实现其中的你想要的功能

然后编写Android.mk Application.mk(Application.mk主要用来定义适应的平台,x86 arm等)

Android.mk如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := HelloJni
LOCAL_SRC_FILES := HelloJni.cpp

include $(BUILD_SHARED_LIBRARY)

Application.mk如下:

#支持的CPU架构
APP_ABI := arm64-v8a

include $(BUILD_SHARED_LIBRARY)

其中LOCAL_MODULE定义的名字就是生成的so库名字,so库前面都会有个lib前缀,上面生产的so应该为 libHelloJni.so

5.在命令行中进入jni目录,输入ndk-build 即可生产对应so库,会自动放在libs文件夹下 至此就可以运行程序了

注意事项:

注意分隔符:

Java前缀与类名以及类名之间的包名和方法名之间使用"_“进行分割; 注意静态:
如果在Java中声明的方法是"静态的”,则native方法也是static。否则不是
如果你的JNI的native方法不是通过静态注册方式来实现的,则不需要符合上面的这些规范,可以格局自己习惯随意命名

动态注册
原理:利用 RegisterNatives 方法来注册 java 方法与 JNI 函数的一一对应关系;

实现流程:

原理:在JNI_Onload()函数中通过JNI中提供的RegisterNatives()方法来将C/C++方法和java方法对应起来(注册), 我们在调用 System.loadLibrary的时候,会在C/C++文件中回调一个名为 JNI_OnLoad ()的函数,在这个函数中一般是做一些初始化相关操作, 我们可以在这个方法里面注册函数, 注册整体流程如下:

  1. 编写Java端的相关native方法
  2. 编写C/C++代码, 实现JNI_Onload()方法
  3. 将Java 方法和 C/C++方法通过签名信息一一对应起来
  4. 通过JavaVM获取JNIEnv, JNIEnv主要用于获取Java类和调用一些JNI提供的方法
  5. 使用类名和对应起来的方法作为参数, 调用JNI提供的函数RegisterNatives()注册方法
// jni头文件 
#include <jni.h>
 
#include <cassert>
#include <cstdlib>
#include <iostream>
using namespace std;
 
 
//native 方法实现
jint get_random_num(){
    return rand();
}
/*需要注册的函数列表,放在JNINativeMethod 类型的数组中,
以后如果需要增加函数,只需在这里添加就行了
参数:
1.java中用native关键字声明的函数名
2.签名(传进来参数类型和返回值类型的说明) 
3.C/C++中对应函数的函数名(地址)
*/
static JNINativeMethod getMethods[] = {
        {"getRandomNum","()I",(void*)get_random_num},
};
//此函数通过调用RegisterNatives方法来注册我们的函数
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){
    jclass clazz;
    //找到声明native方法的类
    clazz = env->FindClass(className);
    if(clazz == NULL){
        return JNI_FALSE;
    }
   //注册函数 参数:java类 所要注册的函数数组 注册函数的个数
    if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){
        return JNI_FALSE;
    }
    return JNI_TRUE;
}
 
static int registerNatives(JNIEnv* env){
    //指定类的路径,通过FindClass 方法来找到对应的类
    const char* className  = "com/example/wenzhe/myjni/JniTest";
    return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0]));
}
//回调函数
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
    JNIEnv* env = NULL;
   //获取JNIEnv
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    //注册函数 registerNatives ->registerNativeMethods ->env->RegisterNatives
    if(!registerNatives(env)){
        return -1;
    }
    //返回jni 的版本 
    return JNI_VERSION_1_6;
}

优点:

流程更加清晰可控;
效率更高;

查看类中的方法的签名
可以使用 javap命令:
举例:javap -s -p MainActivity.class

public class com.example.hellojni.MainActivity extends android.app.Activity {
  static {};
    Signature: ()V

  public com.example.hellojni.MainActivity();
    Signature: ()V

  protected void onCreate(android.os.Bundle);
    Signature: (Landroid/os/Bundle;)V


  public native java.lang.String stringFromJNI(); //native 方法
    Signature: ()Ljava/lang/String;  //签名

  public native int max(int, int); //native 方法
    Signature: (II)I    //签名
}

参考资料:
Android JNI(一)——NDK与JNI基础

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值