NDK
NDK全稱(chēng):Native Development Kit。
關(guān)于NDK顷牌,360百科是這么說(shuō)的:
1.NDK是一系列工具的集合湘今。
NDK提供了一系列的工具己单,幫助開(kāi)發(fā)者快速開(kāi)發(fā)C(或C++)的動(dòng)態(tài)庫(kù)唉窃,并能自動(dòng)將so和java應(yīng)用一起打包成apk。這些工具對(duì)開(kāi)發(fā)者的幫助是巨大的纹笼。
NDK集成了交叉編譯器纹份,并提供了相應(yīng)的mk文件隔離平臺(tái)、CPU廷痘、API等差異蔓涧,開(kāi)發(fā)人員只需要簡(jiǎn)單修改mk文件(指出“哪些文件需要編譯”、“編譯特性要求”等)笋额,就可以創(chuàng)建出so元暴。
NDK可以自動(dòng)地將so和Java應(yīng)用一起打包,極大地減輕了開(kāi)發(fā)人員的打包工作兄猩。
2.NDK提供了一份穩(wěn)定茉盏、功能有限的API頭文件聲明。
Google明確聲明該API是穩(wěn)定的枢冤,在后續(xù)所有版本中都穩(wěn)定支持當(dāng)前發(fā)布的API鸠姨。從該版本的NDK中看出,這些API支持的功能非常有限淹真,包含有:C標(biāo)準(zhǔn)庫(kù)(libc)讶迁、標(biāo)準(zhǔn)數(shù)學(xué)庫(kù)(libm)、壓縮庫(kù)(libz)核蘸、Log庫(kù)(liblog)巍糯。
NDK產(chǎn)生的背景
Android平臺(tái)從誕生起,就已經(jīng)支持C值纱、C++開(kāi)發(fā)。眾所周知坯汤,Android的SDK基于Java實(shí)現(xiàn)虐唠,這意味著基于Android SDK進(jìn)行開(kāi)發(fā)的第三方應(yīng)用都必須使用Java語(yǔ)言。但這并不等同于“第三方應(yīng)用只能使用Java”惰聂。在Android SDK首次發(fā)布時(shí)疆偿,Google就宣稱(chēng)其虛擬機(jī)Dalvik支持JNI編程方式,也就是第三方應(yīng)用完全可以通過(guò)JNI調(diào)用自己的C動(dòng)態(tài)庫(kù)搓幌,即在Android平臺(tái)上杆故,“Java+C”的編程方式是一直都可以實(shí)現(xiàn)的。
不過(guò)溉愁,Google也表示处铛,使用原生SDK編程相比Dalvik虛擬機(jī)也有一些劣勢(shì),Android SDK文檔里,找不到任何JNI方面的幫助撤蟆。即使第三方應(yīng)用開(kāi)發(fā)者使用JNI完成了自己的C動(dòng)態(tài)鏈接庫(kù)(so)開(kāi)發(fā)奕塑,但是so如何和應(yīng)用程序一起打包成apk并發(fā)布?這里面也存在技術(shù)障礙家肯。比如程序更加復(fù)雜龄砰,兼容性難以保障,無(wú)法訪問(wèn)Framework API讨衣,Debug難度更大等换棚。開(kāi)發(fā)者需要自行斟酌使用。
于是NDK就應(yīng)運(yùn)而生了,2011發(fā)布NDK反镇。NDK全稱(chēng)是Native Development Kit固蚤。NDK的發(fā)布,使“Java+C”的開(kāi)發(fā)方式終于轉(zhuǎn)正愿险,成為官方支持的開(kāi)發(fā)方式颇蜡。NDK將是Android平臺(tái)支持C開(kāi)發(fā)的開(kāi)端。
NDK作用
代碼的保護(hù)辆亏。由于apk的java層代碼很容易被反編譯风秤,而C/C++庫(kù)反匯難度較大。
可以方便地使用現(xiàn)存的開(kāi)源庫(kù)扮叨。大部分現(xiàn)存的開(kāi)源庫(kù)都是用C/C++代碼編寫(xiě)的缤弦。
提高程序的執(zhí)行效率。將要求高性能的應(yīng)用邏輯使用C開(kāi)發(fā)彻磁,從而提高應(yīng)用程序的執(zhí)行效率碍沐。(在前面性能優(yōu)化中有提及)
便于移植。用C/C++寫(xiě)得庫(kù)可以方便在其他的嵌入式平臺(tái)上再次使用衷蜓。
具體可參考:NDK 入門(mén)指南
JNI
JNI是Java Native Interface的縮寫(xiě)累提,它提供了若干的API實(shí)現(xiàn)了Java和其他語(yǔ)言的通信(主要是C&C++)。從Java1.1開(kāi)始磁浇,JNI標(biāo)準(zhǔn)成為java平臺(tái)的一部分斋陪,它允許Java代碼和其他語(yǔ)言寫(xiě)的代碼進(jìn)行交互。JNI一開(kāi)始是為了本地已編譯語(yǔ)言置吓,尤其是C和C++而設(shè)計(jì)的无虚,但是它并不妨礙你使用其他編程語(yǔ)言,只要調(diào)用約定受支持就可以了衍锚。使用java與本地已編譯的代碼交互友题,通常會(huì)喪失平臺(tái)可移植性。但是戴质,有些情況下這樣做是可以接受的度宦,甚至是必須的踢匣。例如,使用一些舊的庫(kù)斗埂,與硬件符糊、操作系統(tǒng)進(jìn)行交互,或者為了提高程序的性能呛凶。JNI標(biāo)準(zhǔn)至少要保證本地代碼能工作在任何Java 虛擬機(jī)環(huán)境男娄。
通俗點(diǎn)的意思就是用JAVA調(diào)用C或者C++。在實(shí)際開(kāi)發(fā)過(guò)程中很可能會(huì)使用到C或者C++開(kāi)發(fā)的DLL(windows平臺(tái))漾稀,或者so(Linux平臺(tái))模闲,這個(gè)時(shí)候就需要用JAVA來(lái)調(diào)用DLL或者so文件。
開(kāi)發(fā)NDK時(shí)崭捍,需要用到JNI尸折。
接口分析
JNIEXPORT void JNICALL Java_com_test01_Test_firstTest (JNIEnv * env, jobject obj);
JNIEXPORT :在Jni編程中所有本地語(yǔ)言實(shí)現(xiàn)Jni接口的方法前面都有一個(gè)"JNIEXPORT",這個(gè)可以看做是Jni的一個(gè)標(biāo)志,至今為止沒(méi)發(fā)現(xiàn)它有什么特殊的用處殷蛇。
void :這個(gè)學(xué)過(guò)編程的人都知道实夹,當(dāng)然是方法的返回值了。
JNICALL :這個(gè)可以理解為Jni 和Call兩個(gè)部分粒梦,和起來(lái)的意思就是 Jni調(diào)用XXX(后面的XXX就是JAVA的方法名)亮航。
Java_com_test01_Test_firstTest:這個(gè)就是被上一步中被調(diào)用的部分,也就是Java中的native 方法名匀们,這里起名字的方式比較特別缴淋,是:包名+類(lèi)名+方法名。
JNIEnv * env:這個(gè)env可以看做是Jni接口本身的一個(gè)對(duì)象泄朴,jni.h頭文件中存在著大量被封裝好的函數(shù)重抖,這些函數(shù)也是Jni編程中經(jīng)常被使用到的,要想調(diào)用這些函數(shù)就需要使用JNIEnv這個(gè)對(duì)象祖灰。例如:env->GetObjectClass()钟沛。
Jni中的數(shù)據(jù)類(lèi)型
每一個(gè)Java的數(shù)據(jù)類(lèi)型在Jni中都一個(gè)和它相對(duì)應(yīng)的數(shù)據(jù)類(lèi)型,這樣才能保證Java調(diào)用C或者C++的過(guò)程中數(shù)據(jù)的正確性局扶。jni.h頭文件中定義的類(lèi)型:
NDK環(huán)境搭建
下載NDK恨统,下載具體文件解壓即可,也可使用studio的sdk manager下載安裝
Android-Studio配置
配置NDK路徑
- 右擊Module->Open Module Setting->SDK Location->Android NDK Location
- 或者直接修改local.properties文件
創(chuàng)建Library項(xiàng)目详民,定義模板類(lèi)延欠,此類(lèi)主要為了生成so文件用陌兑,so文件生成后可刪除
// 包名和類(lèi)名要和.cpp或者.c文件中一致
package com.ndkdemo;
public class MathKit
{
// 定義native本地方法沈跨,和普通方法相同,加上native關(guān)鍵字
public static native int square(int num);
}
創(chuàng)建jni目錄兔综,默認(rèn)為src/main/jni
javah命令生成.h文件
在Android Studio找到View->Tool Windows->Terminal 打開(kāi)命令行:
執(zhí)行如下命令:
javah -d NDKDemo/src/main/jni/ -classpath D:/AndroidStudioProjects/MyWork/NDKDemo/build/intermediates/classes/debug -jni com.ndkdemo.MathKit
或者
cd D:/AndroidStudioProjects/MyWork/NDKDemo/build/intermediates/classes/debug
javah com.ndkdemo.MathKit
-d . 表示將在當(dāng)前目錄下生成一個(gè)當(dāng)前命令行文件夾饿凛,產(chǎn)生的頭文件就在這里面了狞玛;
-classpath < PATH> 指明class文件所在的位置(目錄)
-jni com.ndkdemo.MathKit 指定類(lèi)名
javah命令主要用于在JNI開(kāi)發(fā)的時(shí),把java代碼聲明的JNI方法轉(zhuǎn)化成C\C++ 頭文件涧窒,以便進(jìn)行JNI的C\C++ 端程序的開(kāi)發(fā)心肪。
但是需要注意的是javah命令對(duì)Android編譯生成的類(lèi)文件并不能正常工作。如果對(duì)于Android的JNI要想生成C\C++ 頭文件的話纠吴,可能只有先寫(xiě)個(gè)純的java代碼來(lái)進(jìn)行JNI定義硬鞍,接著用JDK編譯,然后再用javah命令生成JNI的C\C++ 頭文件戴已。當(dāng)然你也可以不用javah命令固该,直接手寫(xiě)JNI的C\C++ 頭文件。
創(chuàng)建cpp文件糖儡,文件名最好和.h文件同名伐坏,便于管理編輯.cpp文件
#include <com_ndkdemo_MathKit.h>
JNIEXPORT jintJNICALL Java_com_ndkdemo_MathKit_square
(JNIEnv *env, jclass cls, jint num){
return num * num;
}
在app module目錄下的build.gradle配置ndk選項(xiàng)
defaultConfig {
......
ndk{
moduleName "ndklib" //生成的so名字,實(shí)際為 libndklib.so
abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86" //輸出指定三種abi體系結(jié)構(gòu)下的so庫(kù)
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles 'proguard-rules.pro'
ndk {
moduleName "jnimain"
abiFilters "armeabi", "armeabi-v7a"
}
}
}
Make上述Library項(xiàng)目,生成so文件
先在gradle.properties中添加:android.useDeprecatedNdk=true
生成的so文件在如下目錄握联,生成的so文件為 =lib+ 配置生成名 .so
<Module主目錄>/build/intermediates/ndk/debug/lib/arm64-v8a/libndklib.so
<Module主目錄>/build/intermediates/ndk/debug/lib/armeabi/libndklib.so
<Module主目錄>/build/intermediates/ndk/debug/lib/armeabi-v7a/libndklib.so
<Module主目錄>/build/intermediates/ndk/debug/lib/x86/libndklib.so
在其他Application項(xiàng)目中引用
1.直接引用Library Module
定義和模板類(lèi)相同類(lèi)桦沉,包名+類(lèi)名和此前jni中一致
// 包名和類(lèi)名要和.cpp或者.c文件中一致
package com.ndkdemo;
public class MathKit{
static{
// 對(duì)應(yīng)庫(kù)文件名稱(chēng),要一致金闽。生成的.so文件名為libndklib.so纯露,
// 那么loadLibrary為ndklib,去掉前面的lib及后面的.so
System.loadLibrary("NDKDemo");
}
// 定義native本地方法呐矾,和普通方法相同苔埋,加上native關(guān)鍵字
public static native int square(int num);
}
在應(yīng)用中使用靜態(tài)方式調(diào)用native方法
例如: MathKit.square(10)
2.創(chuàng)建src/main/jniLibs目錄,把生成的so文件拷貝進(jìn)去調(diào)用natvie方法方式同上
自定義jni路徑和so文件路徑
1.jni編輯路徑自定義
android {
sourceSets.main {
jni.srcDirs 'src/main/source'
}
}
2.so文件路徑自定義
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
由于Android Studio以強(qiáng)大的方式集成了NDK, 所以上面很多配置都不需要寫(xiě). 方便了很多..mk文件不用自己寫(xiě)蜒犯。