1、前言
Java JNI
的本意是Java Native Interface
(Java
本地接口),它是为了方便Java
调用C
、C++
等本地代码所封装的一层接口。通过Java JNI
,用户可以调用用C
、C++
所编写的本地代码
NDK
是Android
所提供的一个工具集合,通过NDK
可以在Android
中更加方便地通过JNI
来访问本地代码。
2、优势
- 提高代码的安全性。由于
so
库反编译比较困难,因为NDK
提高了Android
程序的安全性。 - 可以很方便地使用目前已有的
C/C++
开源库 - 便于平台间的移植。
- 提高程序在某些特定情形下的执行效率,但是并不能提升
Android
程序性能
注:
JNI
和NDK
开发所用到的动态库的格式是以.so
为后缀的文件,JNI
和NDK
主要用于底层和嵌入式开发,在Android
应用层开发中使用比较少。
3、JNI开发流程
3.1、在Android Studio配置NDK环境
打开SDKManager-tools
下载NDK
插件,下载后到SDK Location
里面检查里面的NDK
路径
3.2、在Java中声明native方法**
创建一个类,叫做JNITest.java
package com.qinkl;
public class JNITest{
static{
System.loadLibrary("jni-test");
}
public static void main(String args[]){
JNITest jniTest = new JNITest();
System.out.println(jniTest.jniGet());
jniTest.jniSet("hello world");
}
public native String jniGet();
public native void jniSet(String str);
}
可以看到上面的代码中,声明了两个native
方法:jniGet
和jniSet
,这两个就是需要在JNI
中实现的方法。在JniTest
的头部有一个加载动态库的静态代码块,其中jni-test
是so
库的标识,so
库完整的名称为libjni-test.so
,这是加载so
库的规范
3.3、编译Java源文件得到class文件,然后通过javah命令导出JNI的头文件
进入cmd
,(cd /d
任意目录),选择进入项目文件目录,具体的命令为:
javac com/qinkl/JNITest.java
javah com.qinkl.JNITest
JDK10
以前用:javah com.qinkl.JNITest
JDK10
以后是没有提供javah
的,要用javac
代替javah
命令:进入cmd
,切换到文件所在的根目录,执行命令javac -encoding utf8 -h . JNITest.java
在当前目录下,会产生com_qinkl_JNITest.h
的头文件,它是上述命令生成的,内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_qinkl_JNITest */
#ifndef _Included_com_qinkl_JNITest
#define _Included_com_qinkl_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_qinkl_JNITest
* Method: jniGet
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_qinkl_JNITest_jniGet
(JNIEnv *, jobject);
/*
* Class: com_qinkl_JNITest
* Method: jniSet
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_qinkl_JNITest_jniSet
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
JNIEnv*
:表示一个指向JNI
环境的指针,可以通过它来访问JNI
提供的接口方法
jobect
:表示Java
对象中的this
JNIEXPORT
和JNICALL
:他们是JNI
中所定义的宏,可以在JNI.h
这个头文件中查找到
3.4、实现JNI方法
JNI
方法是指Java
中声明的native
方法,这里可以选择用c++
和c
来实现。
首先,在工程的主目录下创建一个名为jni
的子目录,把之前生成的头文件com_qinkl_JNITest
复制进来,接着创建test.cpp
文件,内容如下:
#include "com_qinkl_JNITest.h"
JNIEXPORT jstring JNICALL Java_com_qinkl_JNITest_jniGet(JNIEnv *env,jobject thiz){
printf("invoke get in c++\n");
return (*env)->NewStringUTF(env,"Hello from JNI!");
}
JNIEXPORT void JNICALL Java_com_qinkl_JNITest_jniSet(JNIEnv *env,jobject thiz,jstring string){
printf("invoke set in c++\n");
char* str = (char*)(*env)->GetStringUTFChars(env,string,NULL);
printf("%s\n",str);
(*env)->ReleaseStringUTFChars(env,string,str);
}
3.5、生成so库
在gradle3.0
以前生成的方式是,在根目录gradle.properties
下面加上:
android.useDeprecatedNdk=true
然后在项目build.gradle
的defaultConfig
节点下,添加代码:
ndk{
moduleName "jni-test"//指定生成的so文件名
abiFilters "armeabi","armeabi-v7a","x86"//CPU的类型
}
这两步就可以运行生成so
库了
但如果在gradle 3.0
之后,已经不支持这样的生成方式了,会报错,内容如下
Error: Flag android.useDeprecatedNdk is no longer supported and will be removed in the next version of Android Studio.
3.6、在gradle3.0以上的构建方式
- 首先先到
SDKManager->SDK Tools
,下载CMake
和LLDB
- 在项目
build.gradle
的defaultConfig
节点下,添加代码
externalNativeBuild{
cmake{
cppFlags ""
//生成多个版本的so库
abiFilters 'armeabi-v7a','arm64-v8a'
}
}
- 在项目
build.gradle
的android
节点下,添加代码
externalNativeBuild{
cmake{
path "CMakeLists.txt"//编译后so文件的名字
}
}
- 在项目
app
目录下新建CMakeLists.txt
文件,内容如下
# For more information about using CMake with Android Studio, read the
#documentation: https://d.android.com/studio/projects/add-native-code.html
#Sets the minimum version of CMake required to build the native library.
#CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
#Creates and names a library, sets it as either STATIC
#or SHARED, and provides the relative paths to its source code.
#You can define multiple libraries, and CMake builds them for you.
#Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
# 设置so文件名称.
jni-test
# Sets the library as a shared library.
SHARED
# 设置这个so文件为共享.
# Provides a relative path to your source file(s).
# 设置这个so文件为共享.
src/main/jni/test.c)
#Searches for a specified prebuilt library and stores the path as a
#variable. Because CMake includes system libraries in the search path by
#default, you only need to specify the name of the public NDK library
#you want to add. CMake verifies that the library exists before
#completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
#Specifies libraries CMake should link to your target library. You
#can link multiple libraries, such as libraries you define in this
#build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
# 制定目标库.
jni-test
# Links the target library to the log library
# included in the NDK.
${log-lib} )
- 点击
build
构建一下,可能会出现一下问题
executing external native build for cmake
解决办法:将
gradle
的版本3.1.2
改成3.2.1
,我这边就解决了,再点击build
构建一下,就可以在app/build/cmake/debug/obj
路径下看到生成的so
库了,大功告成!