JNI 与 NDK 入门(一)


概念

JNI是Java Native Interface的简写,它可以使Java与其他语言(如C、C++)进行交互。

它是Java调用Native语言的一种特性,属于Java语言的范畴,与Android无关。

为何需要JNI

  • Java的源文件非常容易被反编译,而通过Native语言生成的.so库文件则不容易被反编译。

  • 有时我们使用Java时需要使用到一些库来实现功能,但这些库仅仅提供了一些Native语言的接口。

  • 使用Native语言编写的代码运行效率高,尤其体现在音频视频图片的处理等需要大量复杂运算的操作上。充分利用了硬件的性能。

由于上述原因,此时我们就需要让Java与Native语言交互。而由于Java的特点,与Native语言的交互能力很弱。因此在此时,我们就需要用到JNI特性增强Java与Native方法的交互能力。

实现的步骤

  1. 在Java中声明Native方法(需要调用的本地方法)

  2. 通过 javac 编译 Java源文件( 生成.class文件)

  3. 通过 javah 命令生成JNI头文件(生成.h文件)

  4. 通过Native语言实现在Java源码中声明的Native方法

  5. 编译成.so库文件

  6. 通过Java命令执行 Java程序,最终实现Java调用本地代码(借助so库文件)

NDK


概念

Native是Native Development Kit的简写,是Android的开发工具包,属于Android,与Java无关系。

它可以快速开发C/C++的动态库,自动将.so和应用一起打包为APK。因此我们可以通过NDK来在Android开发中通过JNI与Native方法交互。

使用方式

  1. 配置 Android NDK环境(在SDK Manager中下载NDK、CMake、LLDB)

  2. 创建 Android 项目,与 NDK进行关联(创建项目时选择C++ support)

  3. 在 Android 项目中声明所需要调用的 Native方法

  4. 用Native语言实现在Android中声明的Native方法

  5. 通过 ndk-bulid 命令编译产生.so库文件

将Android项目与NDK关联


配置NDK路径

local.properties中加入如下一行即可

ndk.dir=<ndk路径>

添加配置

在Gradle的 gradle.properties中加入如下一行,目的是对旧版本的NDK支持

android.useDeprecatedNdk=true

添加ndk节点

在build.gradle中的defaultConfigandroid中加入如下的externalNativeBuild节点

apply plugin: ‘com.android.application’

android {

compileSdkVersion 27

defaultConfig {

applicationId “com.n0texpecterr0r.ndkdemo”

minSdkVersion 19

targetSdkVersion 27

versionCode 1

versionName “1.0”

testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner”

externalNativeBuild {

cmake {

cppFlags “”

}

}

}

buildTypes {

release {

minifyEnabled false

proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’

}

}

externalNativeBuild {

cmake {

path “CMakeLists.txt”

}

}

}

dependencies {

implementation fileTree(dir: ‘libs’, include: [‘*.jar’])

implementation ‘com.android.support:appcompat-v7:27.1.1’

implementation ‘com.android.support.constraint:constraint-layout:1.1.3’

testImplementation ‘junit:junit:4.12’

androidTestImplementation ‘com.android.support.test1.0.2’

androidTestImplementation ‘com.android.support.test.espresso:espresso-core:3.0.2’

}

开发Native代码


在Java文件中声明native方法

我们首先需要在Java代码的类中通过static块来加载我们的Native库。可以通过如下代码,其中loadLibrary的参数是在CMakeList.txt中定义的Native库的名称

static {

System.loadLibrary(“native-lib”);

}

之后,我们便可以在这个类中声明Native方法

public native String getStringFromJNI();

创建CMakeList.txt

我们还需要在src中创建一个CMakeList.txt文件,这个文件约束了Native语言源文件的编译规则。比如下面

cmake_minimum_required(VERSION 3.4.1)

add_library(native-lib SHARED src/main/cpp/native-lib.cpp)

find_library(log-lib log)

target_link_libraries(native-lib ${log-lib})

add_library方法中定义了一个so库,它的名称是native-lib,也就是我们在Java文件中用到的字符串,而后面则跟着这个库对应的Native文件的路径

find_library则是定义了一个路径变量,经过了这个方法,log-lib这个变量中的值就是Android中log库的路径

target_link_libraries则是将native-lib这个库和log库连接了起来,这样我们就能在native-lib中使用log库的方法。

创建Native方法文件

在前面的CMake文件中可以看到,我们把文件放在了src/main/cpp/,因此我们创建cpp这个目录,在里面创建C++源文件native-lib.cpp。

然后, 我们便可以开始编写如下的代码:

#include <jni.h>

#include

extern “C”{

JNIEXPORT jstring JNICALL

Java_com_n0texpecterr0r_ndkdemo_MainActivity_getStringFromJNI(

JNIEnv* env,

jobject) {

std::string hello = “IG牛逼”;

return env->NewStringUTF(hello.c_str());

}

}

此处我们使用的是C++语言,让我们来看看具体的代码。

首先我们引入了jni需要的jni.h,这个头文件中声明了各个jni需要用到的函数。同时我们引入了C++中的string.h。

然后我们看到extern “C”。为了了解这里为什么使用了extern “C”,我们首先需要知道下面的知识:

在C中,编译时的函数签名仅仅是包含了函数的名称,因此不同参数的函数都是同样的签名。这也就是为什么C不支持重载。

而C++为了支持重载,在编译的时候函数的签名除了包含函数的名称,还携带了函数的参数及返回类型等等。

试想此时我们有个C的函数库要给C++调用,会因为签名的不同而找不到对应的函数。因此,我们需要使用extern "C"来告诉编译器使用编译C的方式来连接。

接下来我们看看JNIEXPORT和JNICALL关键字,这两个关键字是两个宏定义,他主要的作用就是说明该函数为JNI函数。

而jstring则对应了Java中的String类,JNI中有很多类似jstring的类来对应Java中的类,下面是Java中的类与JNI类型的对照表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值