zoukankan      html  css  js  c++  java
  • Android JNI总结

    @Dlive

    0x01 JNI介绍

    JNI是Java Native Interface的缩写,JNI不是Android专有的东西,它是从Java继承而来,但是在Android中,JNI的作用和重要性大大增强。

    JNI在Android中起着连接Java和C/C++层的作用,现在APP的许多重要的逻辑,算法以及和底层的交互功能都是通过JNI调用C/C++来实现。

    简单来说,JNI提供了一种可以让Java代码调用C/C++代码的接口。   

    0x02 JNI中的类型/数据结构

    JNI的类型/数据结构,以及函数的声明都放在jni.h的头文件中,这个头文件可以在NDK的platforms文件夹下android对应版本的对应架构文件夹下找到。比如

    platformsandroid-21arch-arm64usrincludejni.h

    一些比较重要的类型如下:

    1. 基本类型(都是一些C类型的重命名)

    clip_image001

    2. 常见的对象类型

    clip_image002

    jobject 代表Java中的对象

    jstring 代表Java中的String类型

    jclass 代表Java中的类

    jobjectArray/jbooleanArray 等都是数组类型

    3. JNIEnv代表JNI环境的结构体

    clip_image003

    可以看到JNIEnv结构体中有大量函数指针,我们可以通过这个结构体来调用jni.h中声明的函数,之后会有代码来说明这结构体的作用

    0x03 Native函数的静态注册与动态注册

    前面的数据类型比较无聊,其实我们主要关注的还是Java native函数是怎么和C/C++关联起来的,关联起来后,C/C++函数的编写和正常的C/C++代码基本相同。

    Native函数的注册有两种方式:静态注册和动态注册。

    关于使用Android Studio编写Native代码的相关配置可以参考:

    http://tools.android.com/tech-docs/new-build-system/gradle-experimental

    1. 静态注册

    静态注册较为简单,所以先来用一个例子来说明一下静态注册。

    使用AndroidStdio新建项目,我们把项目命名为HelloNative

    在项目中新建一个文件夹jni

    clip_image004

    在MainActiviy中声明两个native属性函数,第一个函数为static, AS提供alt+enter快捷键可以新建mainactivity并快速生成这两个函数对应的Native层函数框架

    public static native String Hello1();
    public native String Hello2(int vint);

    在MainActivity开始处加载so库

    static {
        System.loadLibrary("mainactivity");
    }
    

    mainactivity.c:

    #include <jni.h>
    
    JNIEXPORT jstring JNICALL
    Java_dlive_hellonative_MainActivity_Hello1(JNIEnv *env, jclass type) {
        return (*env)->NewStringUTF(env, "natvie hello1");
    }
    
    JNIEXPORT jobject JNICALL
    Java_dlive_hellonative_MainActivity_Hello2(JNIEnv *env, jobject instance, jint vint) {
    
        // TODO
        return (*env)->NewStringUTF(env, "native hello2");
    }
    

    mainactiviy.c中的函数就是我们要编写的native函数

    Java_dlive_hellonative_MainActivity_Hello1对应Java中声明的Hello1函数

    Java_dlive_hellonative_MainActivity_Hello2对应Java中声明的Hello2函数

    这就是静态注册,即Native函数的函数名由Hello1的packagename和Hello1的函数名组成,Java层调用Hello1时,会调用mainactivity.so中对应的

    Java_dlive_hellonative_MainActivity_Hello1函数。

    这里还有几点要注意:

    1. Java层函数对应的Native层函数的第一个参数为JNIEnv*

    2. 被声明为static的函数Hello1对应的Native函数

    Java_dlive_hellonative_MainActivity_Hello1 第二个参数为jclass,表示Hello1函数所在的类

    3. 没有static声明的函数对应的Native函数的第二个参数为jobject,表示调用Hello2的对象

    4. Native函数从第三个参数开始才对应Java层函数的参数

    2. 动态注册

    动态注册与静态注册的区别在于Native的函数名可以自定义,然后使用JNI提供的

    RegisterNatives动态将Java层函数和Native层函数绑定起来即可。

    动态注册主要依赖两个函数JNI_OnLoad和RegisterNatives

    JNI_Onload会在so加载的时候自动被调用,在JNI_Onload中调用RegisterNatives将Java层函数和Native层函数关联起来。

    MainActivity.java中native属性的函数:

    public native String Hello3(MainActivity main);

    在jni目录下新建hello.cpp和hello.h,在hello.cpp中实现native函数

    jstring helloNative(JNIEnv* env, jobject jobj, jobject jobj1)
    {
        return env->NewStringUTF("hello native 3");
    }
    

    可以看到实现的函数名称为helloNative,而不是静态注册时很长的函数名。

    const char* gClassName = "dlive/hellonative2/MainActivity";
    //JNINativeMethod是一个结构体
    JNINativeMethod gMethods[] = {
            {"Hello3", "(Ldlive/hellonative2/MainActivity;)Ljava/lang/String;", (void*)helloNative},
    };
    

    gClassName : Java层类名

    gMethods:{java层函数名,java层函数签名【(参数)返回值】,native层函数指针}

    //reserved 保留参数
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void* reserved) {
        JNIEnv* env = NULL;
    //    jint result = -1;
        if(vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
            return -1;
        }
        if (registerNativeMethods(env, gClassName, gMethods,
                                  sizeof(gMethods) / sizeof(gMethods[0])) == JNI_FALSE)
        {
            return -1;
        }
        LOGD("So load success");
        return JNI_VERSION_1_6;
    }
    

    registerNativeMethods是找了一个网上的实现,就是封装了一下RegisterNatives

    int registerNativeMethods(JNIEnv* env, const char* className,
                                    JNINativeMethod *gMethods, int numMethods) {
        jclass clazz;
        clazz = env->FindClass(className);
        if(clazz == NULL) {
            return JNI_FALSE;
        }
        if(env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
            return JNI_FALSE;
        }
        return JNI_TRUE;
    }
    

    0x04 JNI中提供的函数

    JNI中提供了哪些函数可以在jni.h中找到,这里只讲一下常用的几个函数。

    讲解顺序是按照jni.h中声明的顺序排序的。

    //根据packagename找到并返回java class

    //比如 jclass stringClass = env -> FindClass("java/lang/String");

    //jobjectArray array = env -> NewObjectArray(count, stringClass, NULL);

    jclass FindClass(const char *name)

    //生成一个Java对象

    //clazz为FindClass的返回结果

    //methodID指Java类的构造函数ID

    //调用一个Java对象的方法或者存取一个Java对象的域变量前,要先知道这个方法或域变量的ID

    //取得方法ID和域ID的函数:

    //GetMethodID

    //GetFieldID

    //例子:

    //jclass clazz = env->FindClass("android/content/Intent")

    //jmethodID method = env->GetMethodID(clazz, "<init>", "Ljava/lang/String")

    //jstring action = env -> NewStringUTF("android.intent.action.MAIN")

    //jobject intent = env -> NewObject(clazz, method, action)

    jobject NewObject(jclass clazz, jmethodID methodID, ...)

    //clazz为FindClass返回值

    //name为函数名字,如构造函数为 "<init>"

    //sig为函数签名, 如Content类<init>的签名为,"Ljava/lang/String" (参数)

    jmethodID GetMethodID(jclass clazz, const char *name, const char *sig)

    //获取字符串内容,对应ReleaseStringUTFChars,该函数返回的指针要使用ReleaseStringUTFChars释放

    const char* GetStringUTFChars(jstring str, jboolean *isCopy)

    //创建clazz的对象数组,len为长度,init表示将数组元素初始化为什么一般为NULL

    //jobjectArray array = env -> NewObjectArray(count, stringClass, NULL);S

    jobjectArray NewObjectArray(jsize len, jclass clazz, jobject init)

    //获取对象数组里的元素,第二个参数为数组下标

    //如果一次只取一个元素,可以使用下面的函数,也不用释放内存,更加方便,参考《深入解析Android5.0系统》

    jobject GetObjectArrayElement(jobjectArray array, jsize index)

    //给对象数组元素赋值

    // 例:

    // for(int i=0; i< count; i++)

    // {

    // jstring str = env -> NewStringUTF("HELLO");

    // if(str == NULL)

    // {

    // return NULL;

    // }

    // env -> SetObjectArrayElement(array, i, str);

    // }

    // return array;

    void SetObjectArrayElement(jobjectArray array, jsize index,jobject val)

    //返回int数组指针,第二个参数值是否拷贝一份数组出来,数组用完后要使用ReleaseIntArrayElements释放数组内存

    jint * GetIntArrayElements(jintArray array, jboolean *isCopy)

    注:本博客文章转载需带上原文链接
  • 相关阅读:
    《分析服务从入门到精通读书笔记》第四章、维度部署浏览篇(3)
    集群(cluster)原理(转)
    Atitit 前端测试最简化内嵌web服务器 php 与node.js 目录 1.1. php内置Web Server 1 1.2. Node的 2 Node的比较麻烦些。。Php更加简单
    Atitit mybatis使用简明教程 目录 1.1. Mybatis.xml 配置文件 1 1.2. Datamp.xml mapper文件主要作用是分模块放sql语句 2 1.3. 查询
    Atitit 业务流程执行引擎的实现 目录 1.1. 引擎实现语言java js php等 1 1.2. 流程语言 xml sql js等 业务流程定义语言规范总结 1 1.3. 实体方法定义 2
    Atitit 常见软件设计图纸总结 目录 1.1. ui原型图与html 2 1.2. 业务逻辑 伪代码 各种uml图 2 1.3. 总体设计图纸 结构图 层次图 架构图 2 1.4. 业务逻辑
    Atitit velocity 模板引擎使用法 目录 1.1. 1.4 Context 1 1.1.1. 1.4.1 Context 基本概念 1 1.2. .3不同模式下使用velocity 1
    Atitit mysql存储过程编写指南 1. 定义变量 1 1.1. 变量名以@开头用户变量 会话变量 1 1.2. 以declare关键字声明 存储过程变量 2 1.3. @是用户自定义变量,
    Atitit 算法的艺术 attilax艾提拉著v7 t88.docx Atitit 算法之道 attilax著 1. 文本方面的编码算法 3 1.1. Base64 htmlencode url
    Atitt 常见的拖欠费用的公司与个人黑名单 陕西西部安全网 20072008 西安 一个月 25000 西安盛世涅槃 2007 西安 1m 25000 Sicyi msc 2007 n
  • 原文地址:https://www.cnblogs.com/dliv3/p/5423243.html
Copyright © 2011-2022 走看看