美文网首页android NDK
Android NDK 编程指南(一)

Android NDK 编程指南(一)

作者: Sivin | 来源:发表于2018-02-18 23:49 被阅读26次

    文章测试案例提交到Github:learnNdk

    前言

    一般的我们在开发Android应用程序时都是用Java语言开发, 但是随着业务要求的提高,纯Java平台可能会达到瓶颈,不同的功能模块如果考虑用其他语言实现可能会有更好的表现,甚至可以做到Java语言达不到的效果.

    本篇我们最开始部分来探讨Android平台下如何使用C/C++语言来开发Android应用。本篇的一系列文章都是自己在开发,学习过程中一点一滴的积累,也希望和广大技术爱好者一起交流,共同促进技术的提升,如有不正确的地方,或者有欠缺的地方欢迎大家不吝赐教。

    总体分两部分:

    • Android NDK 基础编程
    • Android 常用库的NDK实现

    什么是JNI?

    JNIJava编程语言中的一个很强大有用的特性,它的存在使得Java类中的某个方法能够用native code实现,同时也能像普通的Java方法一样在Java的类中被调用。Native中的函数也能像Java中一样使用Java中的对象,同时也能创建一个新的Java对象,并且使用这些对象去调用,修改,查询

    使用JNI开发的流程

    1. 在Java类中声明一个native方法
    2. 运行javah命令获取包含该方法的C声明的头文件.
    3. C/C++实现native方法.
    4. 将C/C++代码编译成共享库
    5. 在Java程序中加载该类库.
    6. java里面调用这个native方法

    下面我们写一个案例:我们计算两个数的和,我们想使用C语言来实现这个函数的功能。
    当然这个功能我们可以使用Java代码实现。为了学习JNI我们首先应该从最简单的案例入手。

    1.1声明本地方法

    /**
     * 将参数传从Java层传递给JNI层,在jni层处理完之后,在返回给Java层
     * @param a,b
     * @return 处理之后的结果
     */
     public native int add(int a, int b);
    

    1.2获取头文件

    使用JDK中的javah命令,注意Javah的目标是字节码文件,因此我们要提前将Java文件编译成Class文件,然后进入到字节码文件的工程目录使用命令生成头文件.

    // 直接在Android studio 命令窗口下进行如下操作
    cd \app\build\intermediates\classes\debug.
    // Javah 类的完整类名
    javah com.sivin.ndkdemo.NormalJni
    

    如果没有什么错误就可以发现在debug目录下生成了一个NormalJni.h的头文件,下面我可以就可以使用该头文件了。

    那么为什么要这个头文件呢?事实上也可以不要这个头文件,我们知道在C/C++中,头文件就是声明函数用的,其实,如果想让Java可以调用native层代码,函数需要特定的命名才行(Java_包名_类名_方法名)和一些特定的参数而已,这些如果让开发这手写,未免有点繁琐,同时也容易写错,因此Jdk直接提供了javah的命令帮助我们生成了这个头文件,这样就不用我们自己写了。

    如果你使用的是Android studio 2.2以上版本,可以新建项目的时候勾选上support c++,这样就可以在书写native方法的时候,直接自动生成对应的对应的函数规范,不需要在做上面的工作了,这也是工具的方便。但是原理我觉得还是有必要搞清楚。

    1.3编写Native代码

    通过上一步我们得到了Javanative方法对应的C层的方法,下面我们就来具体实现以下这个方法

    JNIEXPORT jint JNICALL
    Java_com_sivin_ndkdemo_NormalJni_add(JNIEnv *env, jobject instance, jint a ,jint b) {
        return a +b;
    }
    

    下面我们具体的任务就是实现native里面的代码,实现部分就是C/C++里面的知识了,这里我并不想过多的讲解如何使用C/C++来去处整数的功能。如果你对C/C++不是太了解,建议你先去学习一下。

    1.4 Java代码中加载共享库

    static{
        System.loadLibrary("共享库名称");
    }
    

    为什么使用静态代码块加载?

    当类加载的时候就应该去加载共享库.
    

    1.5 Java代码中使用这个native 方法

    经过上面的几部,我们已经将C/C++代码打包成动态库,并且已经加载进虚拟机,此时我们可以向使用普通的java方法一样使用这个native函数。

    2.知识点梳理

    上面的基本过程,其实就是很基本的JNI的基本知识,相信了解过JNI的人都清楚。相信大家实践一下,就可以实现一个demo了。下面我们来讲解一下这个案例所设计的基本知识点。

    几个很重要的参数JNIEnv *env,jobject jobject,jclass jcls

    Java代码如何与C/C++代码进行通信,这正是我们的主角--->JNI要解决的问题,我们发现每一个对应Java层方法的C 函数第一个参数都是JNIEnv *env这个参数,那么这个参数到底是什么呢?打开jni.h这个头文件,有人想问,这个头文件在哪?这个问题。。。好吧刚接触JNI的小白确实不知道,首先Java想要与C/C++通信,肯定是Java的工作,难不成还是C/C++的工作吗?知道了这个,我们很自然的想到JDK开发包,我们打开我们的JDK安装目录,里面有一个include目录,这个目录下就可以找到。
    我们打开可以看到这样的代码:

    struct JNINativeInterface_;
    struct JNIEnv_;
    
    #ifdef __cplusplus
    typedef JNIEnv_ JNIEnv;
    #else
    typedef const struct JNINativeInterface_ *JNIEnv;
    #endif
    

    文件首先声明了两个结构体类型:然后使用宏定义来重新定义JNIEnv,我们可以看到CC++JNIEnv是不同的。有什么不同呢?这里我们先不讨论,我们目前是以C语法学习,后面我们会讲解和C++不同。
    我们看到JNIEnvC里面定义的原型是一个JNINativeInterface_ *是一个结构体指针,下面我们来简单的看看这个结构体到底是什么。

    struct JNINativeInterface_ {
        void *reserved0;
        void *reserved1;
        void *reserved2;
    
        void *reserved3;
        jint (JNICALL *GetVersion)(JNIEnv *env);
        
        jclass (JNICALL *DefineClass)
        (JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
           jsize len);
            
            ...
    }
    

    这个结构体定义的东西很多,对C不熟悉的人,咋一看感觉,有点复杂啊,其实,仔细想想,C的结构体里能翻什么大浪,无非就是定义一些变量嘛。前三个是指针变量,后面的呢?认真一瞅,唉。。。也是指针变量,是函数指针变量
    这么一来我们就似乎懂了,通过这个结构体,我们可以得到里面的函数指针,通过这个函数指针我们可以调用相关的函数。那么到底是什么函数呢?我就不卖关子了,这些函数正是我们JavaC相互调用的桥梁。是开发java虚拟机这帮大牛们帮我们实现的。

    我们发现,这里面提供很很多方法接口,它给我们提供了很多从CJava或者从javaC的转换,通俗的来讲这就是一个纽带

    这样我们就搞定了第一个参数:

    `JNIEnv *env`:通过这个对象我们可以获取到很多从`C`到`Java`或者从`java`到`C`的转换
    

    第二个参数:jobjectjclass: 我们注意到这两个参数一般并不是同时出现的,那么这两个参数代表什么呢?我们仔细观察发现,当我们的java native方法是对象方法时,我们生成的对应的native code第二个参数就是jobject 如果我们的方法是static时我们生成的native code就是jclass,想必大家其实已经猜到了,jobject就是我们调用这个方法时所属的对象。jclass其实就是这个静态方法所属的Class类。
    这两个参数会十分有用,我们在后面会更加具体的讨论这个两个参数的作用和意义。

    本篇内容就到此为止了。接下来我们会具体的讨论,JNIEnv *里具体有哪些转换函数,以及JNInative codejava code之间数据传递是如何转换的,学习这些的前提是先把本篇内容写一个demo出来,弄懂这demo所表达出来的知识内容。建议初学者使用android studio 2.2以上的实现这个demo,至于如何使用Cmake我们会有后续文章讲解。

    相关文章

      网友评论

        本文标题:Android NDK 编程指南(一)

        本文链接:https://www.haomeiwen.com/subject/opfitftx.html