1.简介
NDK:
- Native Development Kit ,
Android
的一个工具开发包,NDK是属于Android
的,与java
并无直接关系
作用:
- 快速开发
C
、C++
的动态库,并自动将.so
和应用一起打包成APK
- 通过
NDK
在Android
中 使用JNI
与本地代码(如C、C++)交互
NDK与JNI的关系:
jni是实现最终目的 , 而 ndk 是实现 jni的功能调用。
JNI
是java native interface , java本地接口。如果java代码需要调用底层的c/c++代码,就需要通过jni来实现。android的底层是linux,linux之上是c/c++代码,而我们app是java代码。有些需要高效率的事情,比如音视频编解码,比如3d绘图等就需要用c/c++来实现了。
JNI实现步骤
- 在Java代码中声明Native方法(即需要调用的本地方法)
- 编译上述 Java源文件javac(得到 .class文件)
- 通过 javah 命令对.class文件导出JNI的头文件(.h文件)
- 使用.c文件引入.h头文件 并且实现在 Java代码中声明的Native方法
- 如 Java 需要与 C++ 交互,那么就用C++实现 Java的Native方法
- 配置app模块的 build.gradle (ndk配置产生的.so的名字,不同的系统)
- sync project
- 运行程序
2.步骤详解
以下操作都是在AndroidStudio中进行。
1.首先要准备一个ndk,不要从androidstudio里面直接下载,具体怎么下载配置看另一篇文章:
https://blog.csdn.net/qq_38261174/article/details/83210458 这里讲了如何下载及配置ndk。
然后在androidstudio中配置ndk:
2.androidstudio新建一个工程,不引入c++ Support。
我建的工程名字是 MyJniStudy
包名是 com.liuyan.myjnistudy
3.没有在androidstudio中配置ndk的先按照上面图片配置ndk。
4.在 main 目录下新建一个JNI目录。
上面图片中没有打钩的,别慌,可以在 build.gralde中配置:
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
}
buildTypes {
...
}
//这里配置
sourceSets { main { jni.srcDirs = ['src/main/jni', 'src/main/jni/'] } }
}
dependencies {
...
}
5. 在java目录下,新建一个包
我的包名是 my
6. 在 my包下面,新建一个java文件,在java文件中写一个 native方法。
我的java文件名字是 NdkJniTest
方法名字是 mytest
package my;
public class NdkJniTest {
//要先加载so库,才能调用native方法
//so库的名字(NdkJniSoName)
//下面步骤会在 build.gralde中配置so库的名字是这个
static {
System.loadLibrary("NdkJniSoName");
}
public native String mytest();
}
方法名字是红色的,不要管,接下来可能方法一直是红色的,不要管,按照步骤来就行了。
7. cmd命令 进入到 my包
执行命令 : javac NdkJniTest.java (编译 .java 产生 .class)
这里我产生了一个错误:
D:\androidstudio-xiangmu\MyJniStudy\app\src\main\java\my>javac NdkJniTest.java
NdkJniTest.java:7: 错误: 编码GBK的不可映射字符
//涓嬮潰姝ラ浼氬湪 build.gralde涓厤缃畇o搴撶殑鍚嶅瓧鏄繖涓?
^
1 个错误
我把注释全删 ,然后重新执行一次命令就好了。
这时会在my包下面多出一个文件
8.cmd命令进入到java 目录,不进入任何包里面
执行命令 javah -jni my.NdkJniTest ( .class产生 .h文件)
这时会在java目录下产生一个 my_NdkJniTest.h 文件。
9.将 .h 文件移动到 jni文件夹下面。
.h 文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class my_NdkJniTest */
#ifndef _Included_my_NdkJniTest
#define _Included_my_NdkJniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: my_NdkJniTest
* Method: mytest
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_my_NdkJniTest_mytest
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
10.在 jni 文件夹下面新建一个 . C 文件 ,不是 .cpp文件
名字自己取。
我的 c文件名字是 jnitest.c
创建好 .c文件之后,编写这个 .c文件。
.c文件 要引入之前的 .h头文件,并实现头文件其中的方法。
我的 .c文件其中的代码:
#include "my_NdkJniTest.h"
JNIEXPORT jstring JNICALL Java_my_NdkJniTest_mytest
(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "This is my Jni test!!!");
}
11.到这一步,库文件和 jni接口文件都已经在上面全部准备好了
接下来
配置 gradle.properties 文件,在末尾加入:
android.useDeprecatedNdk=true
配置ndk 修改app模块的 build.gradle文件,如下:
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
ndk {
moduleName "NdkJniSoName" //生成的so名字
abiFilters "armeabi", "armeabi-v7a", "x86" //输出指定三种abi体系结构下的so库。
}
}
buildTypes {
...
}
sourceSets { main { jni.srcDirs = ['src/main/jni', 'src/main/jni/'] } }
}
dependencies {
...
}
12.所有准备工作都做好了
sync project
13.开始使用
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NdkJniTest ndkJniTest = new NdkJniTest();
String s = ndkJniTest.mytest(); //native方法
Log.i("TAG",s);
}
}
14.结果,打印如下:
15.是不是还有一个疑惑?
我们产生的.so库在哪儿呢?自动生成的哦!!!
想要了解更多的,可以自己研究下 Android.mk文件,其实就是我们在 build.gradle 中配置的 ndk。
3. 过瘾
按照上面的流程,相信你已经成功了!
是不是还不过瘾?好,我们接着来
我们在 NdkJniTest.java 中,再加入一个 native 方法。
package my;
public class NdkJniTest {
static {
System.loadLibrary("NdkJniSoName");
}
public native String mytest();
//新加入的方法
public native int AddAB(int a , int b);
}
这个native方法将要 实现两数之和 ,并将这个和返回。
重新 生成 .class 文件,重新生成 .h文件。
如果你不想要重新生成,你可以自己直接修改 .h文件,在其中加入:
JNIEXPORT jint JNICALL Java_my_NdkJniTest_AddAB
(JNIEnv *, jobject, jint, jint);
如果你的包名和我的不一样,自己根据情况修改上面的代码内容。
所以现在的.h文件所有内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class my_NdkJniTest */
#ifndef _Included_my_NdkJniTest
#define _Included_my_NdkJniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: my_NdkJniTest
* Method: mytest
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_my_NdkJniTest_mytest
(JNIEnv *, jobject);
JNIEXPORT jint JNICALL Java_my_NdkJniTest_AddAB
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
编写 .c文件,一定要引入 .h头文件。
之前我们实现了头文件的方法,新加入的方法还没有实现,所以我们要去实现新加入的方法。
所以.c文件所有内容如下:
#include "my_NdkJniTest.h"
JNIEXPORT jstring JNICALL Java_my_NdkJniTest_mytest
(JNIEnv *env, jobject obj) {
return (*env)->NewStringUTF(env, "This is my Jni test!!!");
}
//实现新的方法
JNIEXPORT jint JNICALL Java_my_NdkJniTest_AddAB
(JNIEnv *env, jobject obj, jint a, jint b) {
return (a+b);
}
sync project
开始使用新方法,并查看结果:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NdkJniTest ndkJniTest = new NdkJniTest();
String s = ndkJniTest.mytest(); //native方法
Log.i("TAG",s);
//测试新方法
int result = ndkJniTest.AddAB(2018,10);
Log.i("TAG",""+result);
}
}
到此为止了!!!