JNI学习笔记:NDK入门(附Demo)

一、JNI简介

JNI,Java native interface,即Java本地接口。JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C++机型交互。即可以在Java代码中调用C/C++等语言的代码或者在C/C++代码中调用Java代码。

实现步骤:

  • 1、在Java中先声明一个native方法
  • 2、编译Java源文件javac得到.class文件
  • 3、通过javah -jni命令导出JNI的.h头文件
  • 4、使用Java需要交互的本地代码,实现在Java中声明的Native方法(如果Java需要与C++交互,那么就用C++实现Java的Native方法。)
  • 5、将本地代码编译成动态库(Windows系统下是.dll文件,如果是Linux系统下是.so文件,如果是Mac系统下是.jnilib)
  • 6、通过Java命令执行Java程序,最终实现Java调用本地代码。

二、NDK简介

NDK ,即Native Develop Kit,是Android的一个开发工具包。

作用:快速开发C、 C++的动态库,并自动将so和应用一起打包成 APK ,即可通过 NDK在 Android中 使用 JNI与本地代码(如C、C++)交互。

实现步骤:

  • 1、配置 Android NDK环境
  • 2、创建 Android 项目,并与 NDK进行关联
  • 3、在 Android 项目中声明所需要调用的 Native方法
  • 4、使用 Android需要交互的本地代码 实现在Android中声明的Native方法
  • 5、通过 ndk - bulid 命令编译产生.so库文件
  • 6、编译 Android Studio 工程,从而实现 Android 调用本地代码

三、JNI和NDK关系

JNI是实现目的,NDK是在Android中实现JNI的手段。

从上图这个Android系统框架来看,上层是通过JNI来调用NDK层的,使用这个工具可以很方便的编写和调试JNI的代码。因为C语言的不跨平台,在Mac系统的下使用NDK编译在Linux下能执行的函数库——so文件。其本质就是一堆C、C++的头文件和实现文件打包成一个库。目前Android系统支持以下七种不用的CPU架构,每一种对应着各自的应用程序二进制接口ABI:(Application Binary Interface)定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。对应关系如下:

ARMv5——armeabi
ARMv7 ——armeabi-v7a
ARMv8——arm64- v8a
x86——x86
MIPS ——mips
MIPS64——mips64
x86_64——x86_64

关于JNI和NDK的详细解读可以参考 Android JNI(一)——NDK与JNI基础  

四、一个简单的Demo

1、前期准备

给 Android Studio 配置 NDK:点击SDK Manager,选择 SDK Tools

勾选 NDK 和一个 LLDB 版本,点击 apply,下载安装

打开 File -> Project Structure -> SDK Location,设置NDK路径,在环境变量中path添加此NDK路径

2、代码编写

1)创建一个JNITest类

public class JNITest {
    
    //创建一个 native 方法
    public native static String get();
}

这时候如果get方法提示:Cannot resolve corresponding JNI function..

解决办法:File -> Settings -> Plugins,搜索 NDK,然后将 Android NDK Support 后面的勾去掉,点击 OK,然后 Restart

 2)创建 C 语言文件

点击 Make Project,生成 JNITest.class 文件

在Project 下查看当前项目,可以看到已经有了 JNITest.class 文件

打开 Android Studio 的 Terminal ,通过cd指令,移动到 app/src/main 目录下

cd app
cd src
cd main

再使用以下命令创建.h头文件

javah -d jni -classpath D:\AndroidProgramming\gitprojects\MyOwn\FirstNDKProject\app\build\intermediates\classes\debug com.kwmax.firstndkproject.JNITest

说明: 

javah:生成头文件

-d jni:当前目录下创建一个 jni 文件夹

-classpath .../debug 指定要生成头文件的字节码文件目录,即我们刚刚的 JNITest.class 的目录

com.kwmax.firstndkproject.JNITest 是 JNITest 文件的包名加上字节码文件的名称

(这里的debug路径目录比较长,可以找到debug文件夹,右键copy path即可复制路径)

这时可以看到app/src/main 目录下有一个 jni 文件夹,里面有一个.h 头文件,就是我们生成的头文件,头文件命名规范是包名加字节码名,以下划线连接。

接着,在 jni 目录下创建一个 c/c++ resource 文件 test.c,要选择 c 为后缀:

#include<jni.h>
#include<stdio.h>
//导入我们创建的头文件
#include "com_kwmax_firstndkproject_JNITest.h"

JNIEXPORT jstring JNICALL Java_com_kwmax_firstndkproject_JNITest_get
  (JNIEnv *env, jclass jclass){

      //返回一个字符串
      return (*env)->NewStringUTF(env,"This is my first NDK Application");
  }

关于JNIEXPORT,JNIEnv可以看以上博客链接里的介绍,此处不明白暂时不影响,先了解实现流程。

然后在在 jni 目录下创建Android.mk ,Application.mk文件

Android.mk文件

//设置工作目录,而my-dir则会返回Android.mk文件所在的目录
LOCAL_PATH := $(call my-dir)
//清除几乎所有以LOCAL——PATH开头的变量(不包括LOCAL_PATH)
include $(CLEAR_VARS)

//设置模块的名称,即编译出来.so文件名
LOCAL_MODULE := JNITest
//指定参与模块编译的C/C++源文件名
LOCAL_SRC_FILES := test.c
//指定生成的静态库或者共享库在运行时依赖的共享库模块列表
include $(BUILD_SHARED_LIBRARY)

Application.mk文件

APP_ABI := all
// 最常用的APP_ABI字段:指定需要基于哪些CPU平台的.so文件
// 常见的平台有armeabi x86 mips,其中移动设备主要是armeabi平台
// 默认情况下,Android平台会生成所有平台的.so文件,即同APP_ABI := armeabi x86 mips
// 指定CPU平台类型后,就只会生成该平台的.so文件,即上述语句只会生成armeabi平台的.so文件

3)创建so文件

打开 Terminal,到 app/src/main/jni 目录下,使用 ndk-build 命令生成 so 库

若此时ndk-build报错,请检查ndk环境变量是否配置正确!

之后打开 app/src/main/libs 就可以看见生成的 so 库了

  为了防止 so 库兼容错误,在 gradle.properties 最后一行添加:

android.useDeprecatedNdk=true

为了让项目能够找到我们的 so 库,在 build.gradle 文件夹的 android 下添加

sourceSets {
     main() {
         jniLibs.srcDirs = ['src/main/libs']
         jni.srcDirs = [] //屏蔽掉默认的jni编译生成过程
     }
}

然后在 JNITest.java 中动态导入 so 库

public class JNITest {
 
    // 动态导入 so 库
    static {
        System.loadLibrary("JNITest");
    }
 
    //创建一个 native 方法
    public native static String get();
}

4)调用,Run!

在MainActivity中调用JNITest.get方法:


public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //打印信息
        Log.e("Message",JNITest.get());
    }
}

运行,查看日志,搜索 Message:

以上demo,感谢 使用 Android Studio 写出第一个 NDK 程序(超详细)

猜你喜欢

转载自blog.csdn.net/yaojie5519/article/details/88605537