1阵翎、前言
Java JNI
的本意是Java Native Interface
(Java
本地接口),它是為了方便Java
調(diào)用C
殃恒、C++
等本地代碼所封裝的一層接口模蜡。通過Java JNI
,用戶可以調(diào)用用C
佑菩、C++
所編寫的本地代碼
NDK
是Android
所提供的一個工具集合盾沫,通過NDK
可以在Android
中更加方便地通過JNI
來訪問本地代碼。
2殿漠、優(yōu)勢
- 提高代碼的安全性赴精。由于
so
庫反編譯比較困難,因為NDK
提高了Android
程序的安全性绞幌。 - 可以很方便地使用目前已有的
C/C++
開源庫 - 便于平臺間的移植蕾哟。
- 提高程序在某些特定情形下的執(zhí)行效率,但是并不能提升
Android
程序性能
注:
JNI
和NDK
開發(fā)所用到的動態(tài)庫的格式是以.so
為后綴的文件莲蜘,JNI
和NDK
主要用于底層和嵌入式開發(fā)谭确,在Android
應(yīng)用層開發(fā)中使用比較少。
3票渠、JNI開發(fā)流程
3.1逐哈、在Android Studio配置NDK環(huán)境
打開SDKManager-tools
下載NDK
插件,下載后到SDK Location
里面檢查里面的NDK
路徑
3.2问顷、在Java中聲明native方法**
創(chuàng)建一個類昂秃,叫做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
中實現(xiàn)的方法。在JniTest
的頭部有一個加載動態(tài)庫的靜態(tài)代碼塊肠骆,其中jni-test
是so
庫的標(biāo)識算途,so
庫完整的名稱為libjni-test.so
,這是加載so
庫的規(guī)范
3.3哗戈、編譯Java源文件得到class文件郊艘,然后通過javah命令導(dǎo)出JNI的頭文件
進入cmd
,(cd /d
任意目錄),選擇進入項目文件目錄唯咬,具體的命令為:
javac com/qinkl/JNITest.java
javah com.qinkl.JNITest
JDK10
以前用:javah com.qinkl.JNITest
JDK10
以后是沒有提供javah
的纱注,要用javac
代替javah
命令:進入cmd
,切換到文件所在的根目錄,執(zhí)行命令javac -encoding utf8 -h . JNITest.java
在當(dāng)前目錄下,會產(chǎn)生com_qinkl_JNITest.h
的頭文件胆胰,它是上述命令生成的狞贱,內(nèi)容如下:
/* 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
環(huán)境的指針,可以通過它來訪問JNI
提供的接口方法
jobect
:表示Java
對象中的this
JNIEXPORT
和JNICALL
:他們是JNI
中所定義的宏蜀涨,可以在JNI.h
這個頭文件中查找到
3.4瞎嬉、實現(xiàn)JNI方法
JNI
方法是指Java
中聲明的native
方法,這里可以選擇用c++
和c
來實現(xiàn)厚柳。
首先氧枣,在工程的主目錄下創(chuàng)建一個名為jni
的子目錄,把之前生成的頭文件com_qinkl_JNITest
復(fù)制進來别垮,接著創(chuàng)建test.cpp
文件,內(nèi)容如下:
#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
節(jié)點下,添加代碼:
ndk{
moduleName "jni-test"http://指定生成的so文件名
abiFilters "armeabi","armeabi-v7a","x86"http://CPU的類型
}
這兩步就可以運行生成so
庫了
但如果在gradle 3.0
之后碳想,已經(jīng)不支持這樣的生成方式了烧董,會報錯,內(nèi)容如下
Error: Flag android.useDeprecatedNdk is no longer supported and will be removed in the next version of Android Studio.
3.6胧奔、在gradle3.0以上的構(gòu)建方式
- 首先先到
SDKManager->SDK Tools
逊移,下載CMake
和LLDB
- 在項目
build.gradle
的defaultConfig
節(jié)點下,添加代碼
externalNativeBuild{
cmake{
cppFlags ""
//生成多個版本的so庫
abiFilters 'armeabi-v7a','arm64-v8a'
}
}
- 在項目
build.gradle
的android
節(jié)點下,添加代碼
externalNativeBuild{
cmake{
path "CMakeLists.txt"http://編譯后so文件的名字
}
}
- 在項目
app
目錄下新建CMakeLists.txt
文件龙填,內(nèi)容如下
# 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.
# 設(shè)置so文件名稱.
jni-test
# Sets the library as a shared library.
SHARED
# 設(shè)置這個so文件為共享.
# Provides a relative path to your source file(s).
# 設(shè)置這個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.
# 制定目標(biāo)庫.
jni-test
# Links the target library to the log library
# included in the NDK.
${log-lib} )
- 點擊
build
構(gòu)建一下胳泉,可能會出現(xiàn)一下問題
executing external native build for cmake
解決辦法:將
gradle
的版本3.1.2
改成3.2.1
,我這邊就解決了岩遗,再點擊build
構(gòu)建一下扇商,就可以在app/build/cmake/debug/obj
路徑下看到生成的so
庫了,大功告成!