前言
之前一直是用Eclipse開發(fā)的想邦,后來轉(zhuǎn)AndroidStudio的時(shí)候遇到了一些坑支鸡,其中比較麻煩的就是NDK的編譯。
代碼已上傳至GitHub
myExample是我創(chuàng)建專門用來放小例子的,以后我寫文章需要的例子就都放這里面了垂攘。
正文
1.原理
之前用Eclipse的時(shí)候都是手動(dòng)創(chuàng)建一個(gè)jni文件夾菠劝,然后自己添加Android.mk
和Application.mk
2個(gè)文件赊舶,最后通過執(zhí)行ndk-build
命令生成so庫。
但是AndroidStudio自帶編譯so庫功能赶诊,它會(huì)通過build.gradle里面的android.ndk
項(xiàng)自動(dòng)生成Android.mk
文件然后生成so庫笼平,我們需要做的就是禁止它自帶的功能,使用我們自己的mk文件舔痪。
實(shí)現(xiàn)步驟
- 禁止AndroidStudio自帶的ndk功能寓调。
- 添加gradle task自動(dòng)調(diào)用
ndk-build
命令。
首先看下文件目錄
2.配置
1.禁止AndroidStudio自帶的ndk功能
完整配置請(qǐng)看build.gradle sourceSets
android {
//... 省略其他配置
sourceSets {
main {
jni.srcDirs = [] //禁止自帶的ndk功能
jniLibs.srcDir 'src/main/libs' //重定向so目錄為src/main/libs
}
}
}
2.添加gradle task自動(dòng)調(diào)用ndk-build命令
完整配置請(qǐng)看build.gradle task
android {
//... 省略其他配置
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}
task ndkBuild(type: Exec, description: 'Compile JNI source with NDK') {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def ndkDir = properties.getProperty('ndk.dir')//獲得ndk目錄
//執(zhí)行ndk-build命令
if (Os.isFamily(Os.FAMILY_WINDOWS)) {//如果是win系統(tǒng)
//commandLine "$ndkDir/ndk-build.cmd", 'NDK_DEBUG=1', '-C', file('src/main/jni').absolutePath //debug命令辙喂,如果需要做調(diào)試可以使用這條命令
commandLine "$ndkDir/ndk-build.cmd", '-C', file('src/main/jni').absolutePath//這里指定了jni的目錄位置
} else {
//commandLine "$ndkDir/ndk-build", 'NDK_DEBUG=1', '-C', file('src/main/jni').absolutePath
commandLine "$ndkDir/ndk-build", '-C', file('src/main/jni').absolutePath
}
}
task ndkClean(type: Exec, description: 'Clean NDK Binaries') {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def ndkDir = properties.getProperty('ndk.dir')//獲得ndk目錄
//執(zhí)行ndk-build clear命令
if (Os.isFamily(Os.FAMILY_WINDOWS)) {//如果是win系統(tǒng)
commandLine "$ndkDir/ndk-build.cmd", 'clean', '-C', file('src/main/jni').absolutePath
} else {
commandLine "$ndkDir/ndk-build", 'clean', '-C', file('src/main/jni').absolutePath
}
}
clean.dependsOn 'ndkClean'
}
- 在生成so庫后可以把它注釋了捶牢,不然jni文件多了生成會(huì)比較慢。
- 千萬別忘了在
local.properties
里配置ndk.dir
不然會(huì)找不到ndk巍耗。
3.實(shí)現(xiàn)
在build.gradle
配置了上述語句后就可以自動(dòng)生成so庫了秋麸。
接下來讓我們來實(shí)現(xiàn)一個(gè)HelloJni試下。
1.創(chuàng)建Application.mk和Android.mk文件
Application.mk文件非常簡(jiǎn)單炬太,就是一句APP_ABI := all
灸蟆,它的意思是生成所有架構(gòu)的so庫,在平常使用中我們一般會(huì)有選擇性的設(shè)置為APP_ABI := armeabi armeabi-v7a x86
亲族。
Android.mk文件比較復(fù)雜炒考,推薦看《Android.mk、Application.mk》寫的比較詳細(xì)霎迫,我這里寫的比較簡(jiǎn)單斋枢。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := HelloJni#模塊的名稱纱耻,會(huì)在生成的so庫前面加上lib斧蜕,最終名稱就是libHelloJni.so
LOCAL_SRC_FILES := HelloJni.cpp#要打包的源碼
include $(BUILD_SHARED_LIBRARY)
2.創(chuàng)建Java文件
我們創(chuàng)建一個(gè)HelloJni.java文件,用于ndk交互松捉,這個(gè)文件也特別簡(jiǎn)單涩赢,要注意的是我這里的hello()
方法用的static
戈次,在jni里對(duì)應(yīng)第二個(gè)參數(shù)為jclass
,如果不是靜態(tài)方法筒扒,就會(huì)對(duì)應(yīng)jobject
怯邪。
public class HelloJni {
public static native String hello();
}
3.創(chuàng)建C++文件
首先需要?jiǎng)?chuàng)建.h
文件,有二種創(chuàng)建方法花墩,第一種就是手動(dòng)創(chuàng)建悬秉,第二種就是利用javah.exe
這個(gè)工具創(chuàng)建澄步,我們這里就利用第二種方法來創(chuàng)建這個(gè).h
文件。
javah.exe
這個(gè)工具在jdk的bin
目錄下搂捧,我們打開AndroidStudio的Terminal
窗口然后輸入javah.exe -d app/src/main/jni -cp app/src/main/java com.xiuyukeji.ndk_config.HelloJni
回車后我們就能生成com_xiuyukeji_ndk_config_HelloJni.h文件驮俗。
這里有點(diǎn)需要注意,函數(shù)名對(duì)應(yīng)java層的包名+類名+方法名
允跑,如果不是自動(dòng)生成王凑,要檢查下函數(shù)名是否對(duì)應(yīng),不然會(huì)報(bào)UnsatisfiedLinkError
錯(cuò)誤聋丝。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xiuyukeji_ndk_config_HelloJni */
#ifndef _Included_com_xiuyukeji_ndk_config_HelloJni
#define _Included_com_xiuyukeji_ndk_config_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_xiuyukeji_ndk_config_HelloJni
* Method: hello
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_xiuyukeji_ndk_1config_HelloJni_hello
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
我來解釋下javah.exe
這條命令的意思索烹,-d
代表輸出目錄在app/src/main/jni
,-cp
代表加載目錄在app/src/main/java
弱睦,最后的com.xiuyukeji.ndk_config.HelloJni
就是文件名了百姓。
javah.exe
這個(gè)工具可以看下這篇文章《超級(jí)簡(jiǎn)單的Android Studio jni 實(shí)現(xiàn)(無需命令行)》
- 如果提示找不到
javah.exe
就說明你的環(huán)境變量需要在path
里多加一句%JAVA_HOME%\bin
。
輸出.h
文件后我們就可以開始寫HelloJni.cpp了况木,代碼特別簡(jiǎn)單垒拢,返回一個(gè)字符串Hello jni
,我這里加了個(gè)extern "C"
火惊,意思是告訴編譯器按照C進(jìn)行編譯求类,不加也可以,具體解釋看這里《extern "c"用法解析》屹耐。
#include "com_xiuyukeji_ndk_config_HelloJni.h"
extern "C" {
JNIEXPORT jstring JNICALL Java_com_xiuyukeji_ndk_1config_HelloJni_hello(JNIEnv * env, jclass jc){
return (env)->NewStringUTF("Hello jni");
}
}
然后我們可以開始生成so庫了尸疆,執(zhí)行Build->Build APK命令。
最終會(huì)在lib下生成各個(gè)架構(gòu)的so庫惶岭。
4.調(diào)用So庫
上面我們已經(jīng)生成了so庫寿弱,下面我們就需要在java層調(diào)用libHelloJni.so
里面的hello
函數(shù)。
- 在MainActivity里動(dòng)態(tài)加載so庫按灶,一般加載so庫都是利用
static {}
症革,這種寫法叫靜態(tài)代碼塊優(yōu)先于其他代碼執(zhí)行。
static {
System.loadLibrary("HelloJni");
}
- 調(diào)用HelloJni.hello()語句鸯旁。
public class MainActivity extends AppCompatActivity {
//... 省略其他代碼
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, HelloJni.hello(), Snackbar.LENGTH_LONG).show();
}
});
}
}
結(jié)尾
如果它有解決你的問題的話地沮,請(qǐng)下點(diǎn)個(gè)贊,謝謝羡亩。
這是我個(gè)人的第三篇文章,寫于2017年4月19日危融。