Android平台上的JNI开发之Hello JNI

作者: Lshare_Blog | 来源:发表于2016-03-08 11:16 被阅读998次

    先有个概念

    JNI,洋名全称是Java Native Interface,翻译过来就是Java本地接口。我们都知道所谓接口就是一套规范,可以用来定义接入方法。那么JNI定义了什么接入方法呢?答案是:它定义了一套Java接入C/C++的方法。

    我们知道C/C++语言编译后的文件是可以直接在本地系统中运行的,而Java文件编译后生成的是字节码文件,需要依赖Java虚拟机(JVM)来运行,显然在效率上C/C++更高效;另一方面,我们知道字节码文件是可以很容易反编译的,存在不安全性;再者,硬件相关的驱动、许多知名的音视频解码库也是C/C++编写的。Java程序想要执行高性能的代码、想要高安全性或者需要调用系统驱动和重用已有的音视频解码库,那么就不得不使用JNI了。

    看下我当初简陋的笔记:

    JNI笔记-概念

    原理

    大致的过程是这样的:

    1. 我们把C/C++源程序编译打包成动态库(dll或so,具体看运行的环境),存放在我们程序的lib目录下;
    2. 在java程序中我们调用带native修饰的代理方法,它的实现在C/C++程序中,系统会调用动态库中相应的实现方法,如果需要的话,还会返回处理结果。
    JNI笔记-原理

    JNI开发环境搭建

    听你这么一说,原理似乎懂了些了,那现在是不是该开工了??别急,“工欲善其事,必先利其器”,让我们先搭建起开发环境。

    1. 下载NDK

    What ? NDK是什么鬼?别急嘛,这就说。NDK洋名全称是Native Development Kit,就是Google给咱们开发者提供的一套方便地进行JNI开发的工具集,可以到官网下载,也可以用我准备好的资源(里面还包含了适用于eclipse开发环境的NDK插件,这个在ADT-Bundle中是集成的)。

    2. 解压NDK并查看参考文档

    将下载后的压缩包解压到任意分区,注意文件路径不能包含中文和空白字符(比如空格、tab),否则会导致NDK不可用。注:可以通过查看参考文档和导入示例代码到工程学习。

    NDK目录

    3. 安装NDK插件和绑定NDK路径

    对Eclipse IDE:放到eclipse安装目录下的plugins目录就对了,然后依次点击Window-->Preference-->Android-->NDK 进行NDK路径的绑定

    绑定步骤1 绑定步骤2 JNI笔记-搭建开发环境

    Hello JNI

    如果上面的步骤顺利,下面我们就要正式跟JNI打个招呼了。Hello JNI项目源码:下载

    1. 新建一个普通的Android项目

    简单,略。

    2. 添加本地动态库支持

    右击Android项目-->Android Tools-->Add Native Support...,然后输入动态库的名称(系统会自动加上前缀"lib"和后缀".so",以jni为例)

    添加本地动态库支持1 添加本地动态库支持2

    完成后会发现,工程会转到C/C++视图,并且项目中多了一些文件/文件夹

    多了的文件

    3. 创建两个对应的文件

    一个是Java源文件,代理动态库中方法;另一个是C/C++源文件,是对代理方法的实现。
    最佳实践是:先创建Java代理文件,静态导入动态库并编写好代理方法,然后利用javah命令生成C语言的方法签名;复制方法签名到C/C++源文件中,补充参数名和方法体就OK了。

    3.1 创建Java源文件--Jni.java
    package com.example.hello_jni;
    public class Jni {
        //静态加载我们即将生成的so库
        static{
            System.loadLibrary("jni");
        }
        //代理函数,具体实现在C
        public native String getMsgFromC();
    }
    
    3.2 生成Jni.java的标头文件

    复制工程的src目录的位置,当前为:D:\Android\jni\Hello-JNI\src
    CMD进入

    C:\Users\Lshare>cd /d D:\Android\jni\Hello-JNI\src
    D:\Android\jni\Hello-JNI\src>
    

    复制Jni.java的全路径名,当前为:com.example.hello_jni.Jni
    javah生成标头文件

    D:\Android\jni\Hello-JNI\src>javah com.example.hello_jni.Jni
    

    刷一下工程,可以看到

    标头文件
    3.3 复制标头文件内容到jni.cpp,并删除标头文件(com_example_hello_jni_Jni.h)
    jni-未绑定.png
    3.4 绑定头文件路径
    绑定头文件1 绑定头文件2 绑定头文件3 绑定头文件4 绑定头文件5

    4.完成函数体

    #include <jni.h>
    /**
     * env:包含了jni.h提供了许多便捷的函数,查看头文件源码发现
     * #if defined(__cplusplus)
     * typedef _JNIEnv JNIEnv;
     * typedef _JavaVM JavaVM;
     * #else
     * typedef const struct JNINativeInterface* JNIEnv;
     * typedef const struct JNIInvokeInterface* JavaVM;
     * thiz:包含对应的代理函数的java类的对象
     */
    #ifndef _Included_com_example_hello_jni_Jni
    #define _Included_com_example_hello_jni_Jni
    #ifdef __cplusplus
    extern "C" {
    #endif
    JNIEXPORT jstring JNICALL Java_com_example_hello_1jni_Jni_getMsgFromC
      (JNIEnv * env, jobject thiz){
        //当为cpp文件时用“env->”,为c文件时用“(*env)->”
        // 该函数用于将“C-String”转换为jstring
        return env->NewStringUTF("Hello JNI!");
    }
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    5. 完善逻辑实现

    //activity_main.xml
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <Button
            android:onClick="sayHello"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="C快快请安" />
    </RelativeLayout>
    
    //MainActivity.java
    public class MainActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
        public void sayHello(View view){
            String msg = new Jni().getMsgFromC();
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
        }
    }
    

    6. 修改Android.mk和Application.mk适应需求

    Android.mk的任务:描述源文件该如何编译

    #包含Android.mk自身的路径
    LOCAL_PATH := $(call my-dir)
    #清除LOCAL_XXX变量,除了LOCAL_PATH
    include $(CLEAR_VARS)
    #要生成的动态库名称,系统会自动加上“lib”前缀和“.so”后缀
    LOCAL_MODULE    := jni
    #要编译的C/C++文件
    LOCAL_SRC_FILES := jni.cpp
    #指向构建脚本,搜集定义的LOCAL_XXX变量信息,生成lib$(LOCAL_MODULE).so
    include $(BUILD_SHARED_LIBRARY)
    

    Application.mk的任务:描述哪一个动态库是项目需要的
    默认不生成这个文件,我们需要自己在/jni目录下新建一个。一般情况下,我们使用两个变量就够了

    #定义要生成的CPU架构的so文件,all代表所有
    APP_ABI := all
    #目标安卓版本,18表示api level,即Android 4.3
    APP_PLATFORM := android-8
    

    7. run一下看看

    运行结果

    Java果真通过JNI技术调用了C代码,而C真的返回问候给Java了,nice!!!


    最后不妨看看我当初的笔记(仅供参考,估计只有我自己看得懂,捂脸走。。。)

    JNI笔记- Hello JNI

    相关文章

      网友评论

        本文标题:Android平台上的JNI开发之Hello JNI

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